@@ -41,16 +41,40 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
41
41
override def transform (tree : Tree )(implicit ctx : Context ): Tree =
42
42
if (tree.source != ctx.source && tree.source.exists)
43
43
transform(tree)(ctx.withSource(tree.source))
44
+ else if ! isInQuoteOrSplice then
45
+ checkAnnotations(tree)
46
+ super .transform(tree)
44
47
else tree match {
45
- case tree : DefDef if tree.symbol.is(Inline ) && level > 0 => EmptyTree
46
- case tree : DefTree =>
47
- lazy val annotCtx = ctx.fresh.setProperty(InAnnotation , true ).withOwner(tree.symbol)
48
- for (annot <- tree.symbol.annotations) annot match {
49
- case annot : BodyAnnotation => annot // already checked in PrepareInlineable before the creation of the BodyAnnotation
50
- case annot => transform(annot.tree)(using annotCtx)
51
- }
52
- checkLevel(super .transform(tree))
53
- case _ => checkLevel(super .transform(tree))
48
+
49
+ case _ : TypeTree | _ : RefTree if tree.isType =>
50
+ val healedType = healType(tree.sourcePos)(tree.tpe)
51
+ if healedType == tree.tpe then tree
52
+ else TypeTree (healedType).withSpan(tree.span)
53
+ case _ : AppliedTypeTree =>
54
+ super .transform(tree) match
55
+ case tree1 : AppliedTypeTree if tree1 ne tree =>
56
+ // propagate healed types
57
+ tree1.withType(tree1.tpt.tpe.appliedTo(tree1.args.map(_.tpe)))
58
+ case tree1 => tree1
59
+
60
+ case _ : Ident | _ : This =>
61
+ tree.withType(healTermType(tree.sourcePos)(tree.tpe))
62
+
63
+ // Remove inline defs in quoted code. Already fully inlined.
64
+ case tree : DefDef if tree.symbol.is(Inline ) && level > 0 =>
65
+ EmptyTree
66
+
67
+ case tree : ValOrDefDef =>
68
+ checkAnnotations(tree)
69
+ healInfo(tree, tree.tpt.sourcePos)
70
+ super .transform(tree)
71
+ case tree : Bind =>
72
+ checkAnnotations(tree)
73
+ healInfo(tree, tree.sourcePos)
74
+ super .transform(tree)
75
+
76
+ case _ =>
77
+ super .transform(tree)
54
78
}
55
79
56
80
/** Transform quoted trees while maintaining phase correctness */
@@ -83,188 +107,122 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
83
107
case Apply (fun @ TypeApply (_, _ :: Nil ), _) if splice.isTerm =>
84
108
// Type of the splice itsel must also be healed
85
109
// internal.Quoted.expr[F[T]](... T ...) --> internal.Quoted.expr[F[$t]](... T ...)
86
- val tp = checkType (splice.sourcePos).apply (splice.tpe.widenTermRefExpr)
110
+ val tp = healType (splice.sourcePos)(splice.tpe.widenTermRefExpr)
87
111
cpy.Apply (splice)(cpy.TypeApply (fun)(fun.fun, tpd.TypeTree (tp) :: Nil ), body1 :: Nil )
88
112
case Apply (f @ Apply (fun @ TypeApply (_, _), qctx :: Nil ), _) if splice.isTerm =>
89
113
// Type of the splice itsel must also be healed
90
114
// internal.Quoted.expr[F[T]](... T ...) --> internal.Quoted.expr[F[$t]](... T ...)
91
- val tp = checkType (splice.sourcePos).apply (splice.tpe.widenTermRefExpr)
115
+ val tp = healType (splice.sourcePos)(splice.tpe.widenTermRefExpr)
92
116
cpy.Apply (splice)(cpy.Apply (f)(cpy.TypeApply (fun)(fun.fun, tpd.TypeTree (tp) :: Nil ), qctx :: Nil ), body1 :: Nil )
93
117
case splice : Select =>
94
118
val tagRef = getQuoteTypeTags.getTagRef(splice.qualifier.tpe.asInstanceOf [TermRef ])
95
119
ref(tagRef).withSpan(splice.span)
96
120
}
97
121
}
98
122
99
- /** If `tree` refers to a locally defined symbol (either directly, or in a pickled type),
100
- * check that its staging level matches the current level. References to types
101
- * that are phase-incorrect can still be healed as follows:
123
+ /** Check that annotations do not contain quotes and and that splices are valid */
124
+ private def checkAnnotations (tree : Tree )(using Context ): Unit =
125
+ tree match
126
+ case tree : DefTree =>
127
+ lazy val annotCtx = ctx.fresh.setProperty(InAnnotation , true ).withOwner(tree.symbol)
128
+ for (annot <- tree.symbol.annotations) annot match
129
+ case annot : BodyAnnotation => annot // already checked in PrepareInlineable before the creation of the BodyAnnotation
130
+ case annot => transform(annot.tree)(using annotCtx)
131
+ case _ =>
132
+
133
+ /** Heal types in the info of the given tree */
134
+ private def healInfo (tree : Tree , pos : SourcePosition )(using Context ): Unit =
135
+ tree.symbol.info = healType(pos)(tree.symbol.info)
136
+
137
+ /** If the refers to a locally defined symbol (either directly, or in a pickled type),
138
+ * check that its staging level matches the current level.
139
+ * - Static types and term are allowed at any level.
140
+ * - If a type reference is used a higher level, then it is insosistent. Will atempt to heal before failing.
141
+ * - If a term reference is used a different level, then it is insosistent.
102
142
*
103
143
* If `T` is a reference to a type at the wrong level, try to heal it by replacing it with
104
- * `${implicitly[quoted.Type[T]]}`.
144
+ * a type tag of type `quoted.Type[T]`.
145
+ * The tag is generated by an instance of `QuoteTypeTags` directly if the splice is explicit
146
+ * or indirectly by `tryHeal`.
105
147
*/
106
- protected def checkLevel (tree : Tree )(implicit ctx : Context ): Tree = {
107
- def checkTp (tp : Type ): Type = checkType(tree.sourcePos).apply(tp)
108
- tree match {
109
- case Quoted (_) | Spliced (_) =>
110
- tree
111
- case _ : This =>
112
- assert(checkSymLevel(tree.symbol, tree.tpe, tree.sourcePos).isEmpty)
113
- tree
114
- case Ident (name) =>
115
- if (name == nme.WILDCARD )
116
- untpd.Ident (name).withType(checkType(tree.sourcePos).apply(tree.tpe)).withSpan(tree.span)
117
- else
118
- checkSymLevel(tree.symbol, tree.tpe, tree.sourcePos) match {
119
- case Some (tpRef) => tpRef
120
- case _ => tree
121
- }
122
- case _ : TypeTree | _ : AppliedTypeTree | _ : Apply | _ : TypeApply | _ : UnApply | Select (_, OuterSelectName (_, _)) =>
123
- tree.withType(checkTp(tree.tpe))
124
- case _ : ValOrDefDef | _ : Bind =>
125
- tree.symbol.info = checkTp(tree.symbol.info)
126
- tree
127
- case _ : Template =>
128
- checkTp(tree.symbol.owner.asClass.givenSelfType)
129
- tree
130
- case _ =>
131
- tree
132
- }
133
- }
134
-
135
- /** Check and heal all named types and this-types in a given type for phase consistency. */
136
- private def checkType (pos : SourcePosition )(implicit ctx : Context ): TypeMap = new TypeMap {
137
- def apply (tp : Type ): Type = reporting.trace(i " check type level $tp at $level" ) {
138
- tp match {
139
- case tp : TypeRef if tp.symbol.isSplice =>
140
- if (tp.isTerm)
141
- mapCtx.error(i " splice outside quotes " , pos)
142
- if level > 0 then getQuoteTypeTags.getTagRef(tp.prefix.asInstanceOf [TermRef ])
143
- else tp
144
- case tp : TypeRef if tp.symbol == defn.QuotedTypeClass .typeParams.head =>
145
- if level > 0 then
146
- // Adapt direct references to the type of the type parameter T of a quoted.Type[T].
147
- // Replace it with a properly encoded type splice. This is the normal form expected for type splices.
148
- getQuoteTypeTags.getTagRef(tp.prefix.asInstanceOf [TermRef ])
149
- else tp
150
- case tp : NamedType =>
151
- checkSymLevel(tp.symbol, tp, pos) match {
152
- case Some (tpRef) => tpRef.tpe
148
+ private def healType (pos : SourcePosition )(using Context ) = new TypeMap {
149
+ def apply (tp : Type ): Type =
150
+ tp match
151
+ case tp : TypeRef =>
152
+ tp.prefix match
153
+ case NoPrefix if level > levelOf(tp.symbol) =>
154
+ tryHeal(tp.symbol, tp, pos)
155
+ case prefix : ThisType if ! tp.symbol.isStatic && level > levelOf(prefix.cls) =>
156
+ tryHeal(tp.symbol, tp, pos)
157
+ case prefix : TermRef if tp.symbol.isSplice =>
158
+ // Heal explice type splice in the code
159
+ if level > 0 then getQuoteTypeTags.getTagRef(prefix) else tp
160
+ case prefix : TermRef if ! prefix.symbol.isStatic && level > levelOf(prefix.symbol) =>
161
+ tryHeal(prefix.symbol, tp, pos)
153
162
case _ =>
154
- if (tp.symbol.is(Param )) tp
155
- else mapOver(tp)
156
- }
157
- case tp : ThisType =>
158
- assert(checkSymLevel(tp.cls, tp, pos).isEmpty)
159
- mapOver(tp)
163
+ mapOver(tp)
164
+ case tp : ThisType if level != - 1 && level != levelOf(tp.cls) =>
165
+ levelError(tp.cls, tp, pos, " " )
160
166
case tp : AnnotatedType =>
161
- derivedAnnotatedType(tp, apply(tp.parent), tp.annot)
167
+ val newAnnotTree = transform(tp.annot.tree)
168
+ derivedAnnotatedType(tp, apply(tp.parent), tp.annot.derivedAnnotation(newAnnotTree))
162
169
case _ =>
163
170
mapOver(tp)
164
- }
165
- }
166
171
}
167
172
168
- /** Check reference to `sym` for phase consistency, where `tp` is the underlying type
169
- * by which we refer to `sym`. If it is an inconsistent type try construct a healed type for it.
170
- *
171
- * @return `None` if the phase is correct or cannot be healed
172
- * `Some(tree)` with the `tree` of the healed type tree for `${implicitly[quoted.Type[T]]}`
173
- */
174
- private def checkSymLevel (sym : Symbol , tp : Type , pos : SourcePosition )(implicit ctx : Context ): Option [Tree ] = {
175
- /** Is a reference to a class but not `this.type` */
176
- def isClassRef = sym.isClass && ! tp.isInstanceOf [ThisType ]
177
-
178
- /** Is this a static path or a type porjection with a static prefix */
179
- def isStaticPathOK (tp1 : Type ): Boolean =
180
- tp1.stripTypeVar match
181
- case tp1 : TypeRef => tp1.symbol.is(Package ) || isStaticPathOK(tp1.prefix)
182
- case tp1 : TermRef =>
183
- def isStaticTermPathOK (sym : Symbol ): Boolean =
184
- (sym.is(Module ) && sym.isStatic) ||
185
- (sym.isStableMember && isStaticTermPathOK(sym.owner))
186
- isStaticTermPathOK(tp1.symbol)
187
- case tp1 : ThisType => tp1.cls.isStaticOwner
188
- case tp1 : AppliedType => isStaticPathOK(tp1.tycon)
189
- case tp1 : SkolemType => isStaticPathOK(tp1.info)
190
- case _ => false
191
-
192
- /* Is a reference to an `<init>` method on a class with a static path */
193
- def isStaticNew (tp1 : Type ): Boolean = tp1 match
194
- case tp1 : TermRef => tp1.symbol.isConstructor && isStaticPathOK(tp1.prefix)
195
- case _ => false
196
-
197
- if (! sym.exists || levelOK(sym) || isStaticPathOK(tp) || isStaticNew(tp))
198
- None
199
- else if (! sym.isStaticOwner && ! isClassRef)
173
+ /** Check phase consistency of terms and heal incosistent type references. */
174
+ private def healTermType (pos : SourcePosition )(using Context ) = new TypeMap {
175
+ def apply (tp : Type ): Type =
200
176
tp match
201
- case tp : TypeRef =>
202
- if levelOf(sym).getOrElse(0 ) < level then tryHeal(sym, tp, pos)
203
- else None
177
+ case tp @ TypeRef (NoPrefix , _) if level > levelOf(tp.symbol) =>
178
+ tryHeal(tp.symbol, tp, pos)
179
+ case tp @ TermRef (NoPrefix , _) if ! tp.symbol.isStatic && level != levelOf(tp.symbol) =>
180
+ levelError(tp.symbol, tp, pos, " " )
181
+ case tp : ThisType if level != - 1 && level != levelOf(tp.cls) =>
182
+ levelError(tp.cls, tp, pos, " " )
204
183
case _ =>
205
- levelError(sym, tp, pos, " " )
206
- else if (! sym.owner.isStaticOwner) // non-top level class reference that is phase inconsistent
207
- levelError(sym, tp, pos, " " )
208
- else
209
- None
210
- }
211
-
212
- /** Does the level of `sym` match the current level?
213
- * An exception is made for inline vals in macros. These are also OK if their level
214
- * is one higher than the current level, because on execution such values
215
- * are constant expression trees and we can pull out the constant from the tree.
216
- */
217
- private def levelOK (sym : Symbol )(implicit ctx : Context ): Boolean = levelOf(sym) match {
218
- case Some (l) =>
219
- l == level ||
220
- level == - 1 && (
221
- // here we assume that Splicer.checkValidMacroBody was true before going to level -1,
222
- // this implies that all arguments are quoted.
223
- sym.isClass // reference to this in inline methods
224
- )
225
- case None =>
226
- sym.is(Package ) || sym.owner.isStaticOwner ||
227
- (sym.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot ) && level > 0 ) ||
228
- levelOK(sym.owner)
184
+ if tp.typeSymbol.is(Package ) then tp
185
+ else mapOver(tp)
229
186
}
230
187
231
188
/** Try to heal reference to type `T` used in a higher level than its definition.
232
- * @return None if successful
233
- * @return Some(msg) if unsuccessful where `msg` is a potentially empty error message
234
- * to be added to the "inconsistent phase" message .
189
+ * Returns a reference to a type tag generated by `QuoteTypeTags` that contains a
190
+ * refercence to a type alias containing the equivalent of `${summon[quoted.Type[T]]}`.
191
+ * Emits and error if `T` cannot be healed and returns `T` .
235
192
*/
236
- protected def tryHeal (sym : Symbol , tp : TypeRef , pos : SourcePosition )(implicit ctx : Context ): Option [ Tree ] = {
193
+ protected def tryHeal (sym : Symbol , tp : TypeRef , pos : SourcePosition )(implicit ctx : Context ): TypeRef = {
237
194
val reqType = defn.QuotedTypeClass .typeRef.appliedTo(tp)
238
195
val tag = ctx.typer.inferImplicitArg(reqType, pos.span)
239
-
240
196
tag.tpe match
197
+
241
198
case tp : TermRef =>
242
199
checkStable(tp, pos, " type witness" )
243
- Some (ref( getQuoteTypeTags.getTagRef(tp)) )
200
+ getQuoteTypeTags.getTagRef(tp)
244
201
case _ : SearchFailureType =>
245
202
levelError(sym, tp, pos,
246
- i """
247
- |
248
- | The access would be accepted with the right type tag, but
249
- | ${ctx.typer.missingArgMsg(tag, reqType, " " )}""" )
203
+ i """
204
+ |
205
+ | The access would be accepted with the right type tag, but
206
+ | ${ctx.typer.missingArgMsg(tag, reqType, " " )}""" )
250
207
case _ =>
251
208
levelError(sym, tp, pos,
252
- i """
253
- |
254
- | The access would be accepted with a given $reqType""" )
209
+ i """
210
+ |
211
+ | The access would be accepted with a given $reqType""" )
255
212
}
256
213
257
- private def levelError (sym : Symbol , tp : Type , pos : SourcePosition , errMsg : String )(using Context ) = {
214
+ private def levelError (sym : Symbol , tp : Type , pos : SourcePosition , errMsg : String )(using Context ): tp. type = {
258
215
def symStr =
259
216
if (! tp.isInstanceOf [ThisType ]) sym.show
260
217
else if (sym.is(ModuleClass )) sym.sourceModule.show
261
218
else i " ${sym.name}.this "
262
- summon[ Context ] .error(
219
+ ctx .error(
263
220
em """ access to $symStr from wrong staging level:
264
- | - the definition is at level ${levelOf(sym).getOrElse( 0 ) },
221
+ | - the definition is at level ${levelOf(sym)},
265
222
| - but the access is at level $level. $errMsg""" , pos)
266
- None
223
+ tp
267
224
}
225
+
268
226
}
269
227
270
228
object PCPCheckAndHeal {
0 commit comments