@@ -38,209 +38,133 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
38
38
39
39
private val InAnnotation = Property .Key [Unit ]()
40
40
41
+ private [this ] var inQuoteOrSplice = false
42
+
41
43
override def transform (tree : Tree )(implicit ctx : Context ): Tree =
42
44
if (tree.source != ctx.source && tree.source.exists)
43
45
transform(tree)(ctx.withSource(tree.source))
46
+ else if ! inQuoteOrSplice then
47
+ checkAnnotations(tree)
48
+ super .transform(tree)
44
49
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))
54
- }
55
-
56
- /** Transform quoted trees while maintaining phase correctness */
57
- override protected def transformQuotation (body : Tree , quote : Tree )(implicit ctx : Context ): Tree = {
58
- val taggedTypes = new PCPCheckAndHeal .QuoteTypeTags (quote.span)(using ctx)
59
-
60
- if (ctx.property(InAnnotation ).isDefined)
61
- ctx.error(" Cannot have a quote in an annotation" , quote.sourcePos)
62
50
63
- val contextWithQuote =
64
- if level == 0 then contextWithQuoteTypeTags(taggedTypes)(quoteContext)
65
- else quoteContext
66
- val body1 = transform(body)(contextWithQuote)
67
- val body2 =
68
- taggedTypes.getTypeTags match
69
- case Nil => body1
70
- case tags => tpd.Block (tags, body1).withSpan(body.span)
51
+ // Type tress
71
52
72
- super .transformQuotation(body2, quote)
73
- }
53
+ case _ : TypTree =>
54
+ if needsHealing(tree.tpe) then TypeTree (healType(tree.sourcePos)(tree.tpe)).withSpan(tree.span)
55
+ else tree
56
+ case _ : RefTree if tree.isType =>
57
+ if needsHealing(tree.tpe) then TypeTree (healType(tree.sourcePos)(tree.tpe)).withSpan(tree.span)
58
+ else tree
74
59
75
- /** Transform splice
76
- * - If inside a quote, transform the contents of the splice.
77
- * - If inside inlined code, expand the macro code.
78
- * - If inside of a macro definition, check the validity of the macro.
79
- */
80
- protected def transformSplice (body : Tree , splice : Tree )(implicit ctx : Context ): Tree = {
81
- val body1 = transform(body)(spliceContext)
82
- splice match {
83
- case Apply (fun @ TypeApply (_, _ :: Nil ), _) if splice.isTerm =>
84
- // Type of the splice itsel must also be healed
85
- // internal.Quoted.expr[F[T]](... T ...) --> internal.Quoted.expr[F[$t]](... T ...)
86
- val tp = checkType(splice.sourcePos).apply(splice.tpe.widenTermRefExpr)
87
- cpy.Apply (splice)(cpy.TypeApply (fun)(fun.fun, tpd.TypeTree (tp) :: Nil ), body1 :: Nil )
88
- case Apply (f @ Apply (fun @ TypeApply (_, _), qctx :: Nil ), _) if splice.isTerm =>
89
- // Type of the splice itsel must also be healed
90
- // internal.Quoted.expr[F[T]](... T ...) --> internal.Quoted.expr[F[$t]](... T ...)
91
- val tp = checkType(splice.sourcePos).apply(splice.tpe.widenTermRefExpr)
92
- cpy.Apply (splice)(cpy.Apply (f)(cpy.TypeApply (fun)(fun.fun, tpd.TypeTree (tp) :: Nil ), qctx :: Nil ), body1 :: Nil )
93
- case splice : Select =>
94
- val tagRef = getQuoteTypeTags.getTagRef(splice.qualifier.tpe.asInstanceOf [TermRef ])
95
- ref(tagRef).withSpan(splice.span)
96
- }
97
- }
60
+ // Term trees
98
61
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:
102
- *
103
- * 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]]}`.
105
- */
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
62
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)
63
+ if level != levelOf(tree.symbol) then
64
+ levelError(tree.symbol, tree.tpe, tree.sourcePos, " " )
129
65
tree
66
+ case _ : Ident =>
67
+ checkTermLevel(tree).withType(healTermType(tree.sourcePos)(tree.tpe))
68
+
69
+ // Remove inline defs in quoted code. Already fully inlined.
70
+ case tree : DefDef if tree.symbol.is(Inline ) && level > 0 => EmptyTree
71
+
72
+ case tree : ValOrDefDef =>
73
+ checkAnnotations(tree)
74
+ healInfo(tree, tree.tpt.sourcePos)
75
+ super .transform(tree)
76
+ case tree : Bind =>
77
+ checkAnnotations(tree)
78
+ healInfo(tree, tree.sourcePos)
79
+ super .transform(tree)
80
+
130
81
case _ =>
131
- tree
82
+ super .transform( tree)
132
83
}
133
- }
134
84
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
85
+ /** Check that annotations do not contain quotes and and that splices are valid */
86
+ private def checkAnnotations (tree : Tree )(using Context ): Unit =
87
+ tree match
88
+ case tree : DefTree =>
89
+ lazy val annotCtx = ctx.fresh.setProperty(InAnnotation , true ).withOwner(tree.symbol)
90
+ for (annot <- tree.symbol.annotations) annot match
91
+ case annot : BodyAnnotation => annot // already checked in PrepareInlineable before the creation of the BodyAnnotation
92
+ case annot => transform(annot.tree)(using annotCtx)
93
+ case _ =>
94
+
95
+ /** Heal types in the info of the given tree */
96
+ private def healInfo (tree : Tree , pos : SourcePosition )(using Context ): Unit =
97
+ if needsHealing(tree.symbol.info) then
98
+ tree.symbol.info = healType(pos)(tree.symbol.info)
99
+
100
+ /** Does this type contain a phase inconsistent reference? */
101
+ private def needsHealing (tp : Type )(using Context ) = new ExistsAccumulator (
102
+ _ match
103
+ case tp @ TypeRef (NoPrefix , _) =>
104
+ level > levelOf(tp.symbol)
105
+ case tp @ TermRef (NoPrefix , _) =>
106
+ level != levelOf(tp.symbol)
107
+ case tp : ThisType =>
108
+ level != levelOf(tp.cls)
109
+ case tp =>
110
+ tp.typeSymbol.isSplice && level > 0
111
+ ).apply(false , tp)
112
+
113
+ private def healType (pos : SourcePosition )(using Context ) = new TypeMap {
114
+ def apply (tp : Type ): Type =
115
+ tp match
116
+ case tp : TypeRef =>
117
+ tp.prefix match
118
+ case NoPrefix if level > levelOf(tp.symbol) =>
119
+ tryHeal(tp.symbol, tp, pos).getOrElse(tp)
120
+ case prefix : ThisType if ! tp.symbol.isStatic && level > levelOf(prefix.cls) =>
121
+ tryHeal(tp.symbol, tp, pos).getOrElse(tp)
122
+ case prefix : TermRef if tp.symbol.isSplice =>
123
+ // Heal explice type splice in the code
124
+ if level > 0 then getQuoteTypeTags.getTagRef(prefix) else tp
125
+ case prefix : TermRef if ! prefix.symbol.isStatic && level != levelOf(prefix.symbol) =>
126
+ tryHeal(tp.symbol, tp, pos).getOrElse(tp)
153
127
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)
128
+ mapOver(tp)
129
+ case tp : ThisType if level != levelOf(tp.cls) =>
130
+ if level != - 1 then // Ok after inlined
131
+ assert(levelError(tp.cls, tp, pos, " " ).isEmpty)
132
+ tp
160
133
case tp : AnnotatedType =>
161
- derivedAnnotatedType(tp, apply(tp.parent), tp.annot)
134
+ val newAnnotTree = transform(tp.annot.tree)
135
+ derivedAnnotatedType(tp, apply(tp.parent), tp.annot.derivedAnnotation(newAnnotTree))
162
136
case _ =>
163
137
mapOver(tp)
164
- }
165
- }
166
138
}
167
139
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)
140
+ private def healTermType (pos : SourcePosition )(using Context ) = new TypeMap {
141
+ def apply (tp : Type ): Type =
200
142
tp match
201
- case tp : TypeRef =>
202
- if levelOf(sym) < level then tryHeal(sym, tp, pos)
203
- else None
143
+ case tp @ TypeRef (NoPrefix , _) if level > levelOf(tp.symbol) =>
144
+ tryHeal(tp.symbol, tp, pos).getOrElse(tp)
204
145
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
146
+ if tp.typeSymbol.is(Package ) then tp
147
+ else mapOver(tp)
210
148
}
211
149
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 = levelOfOpt(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)
150
+ private def checkTermLevel (tree : Tree )(using Context ): tree.type = {
151
+ new TypeTraverser {
152
+ def traverse (tp : Type ): Unit =
153
+ tp match
154
+ case tp @ TermRef (NoPrefix , _) if ! tp.symbol.isStatic && level != levelOf(tp.symbol) =>
155
+ levelError(tree.symbol, tree.tpe, tree.sourcePos, " " )
156
+ case _ => traverseChildren(tp)
157
+ }.traverse(tree.tpe)
158
+ tree
229
159
}
230
160
231
- /** 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.
235
- */
236
- protected def tryHeal (sym : Symbol , tp : TypeRef , pos : SourcePosition )(implicit ctx : Context ): Option [Tree ] = {
161
+ protected def tryHeal (sym : Symbol , tp : TypeRef , pos : SourcePosition )(implicit ctx : Context ): Option [TypeRef ] = {
237
162
val reqType = defn.QuotedTypeClass .typeRef.appliedTo(tp)
238
163
val tag = ctx.typer.inferImplicitArg(reqType, pos.span)
239
-
240
164
tag.tpe match
241
165
case tp : TermRef =>
242
166
checkStable(tp, pos, " type witness" )
243
- Some (ref( getQuoteTypeTags.getTagRef(tp) ))
167
+ Some (getQuoteTypeTags.getTagRef(tp))
244
168
case _ : SearchFailureType =>
245
169
levelError(sym, tp, pos,
246
170
i """
@@ -259,12 +183,60 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
259
183
if (! tp.isInstanceOf [ThisType ]) sym.show
260
184
else if (sym.is(ModuleClass )) sym.sourceModule.show
261
185
else i " ${sym.name}.this "
262
- summon[ Context ] .error(
186
+ ctx .error(
263
187
em """ access to $symStr from wrong staging level:
264
188
| - the definition is at level ${levelOf(sym)},
265
189
| - but the access is at level $level. $errMsg""" , pos)
266
190
None
267
191
}
192
+
193
+ /** Transform quoted trees while maintaining phase correctness */
194
+ override protected def transformQuotation (body : Tree , quote : Tree )(implicit ctx : Context ): Tree = {
195
+ val taggedTypes = new PCPCheckAndHeal .QuoteTypeTags (quote.span)(using ctx)
196
+ if (ctx.property(InAnnotation ).isDefined)
197
+ ctx.error(" Cannot have a quote in an annotation" , quote.sourcePos)
198
+
199
+ val contextWithQuote =
200
+ if level == 0 then contextWithQuoteTypeTags(taggedTypes)(quoteContext)
201
+ else quoteContext
202
+ val inQuoteOrSpliceOld = inQuoteOrSplice
203
+ inQuoteOrSplice = true
204
+ val body1 = transform(body)(contextWithQuote)
205
+ inQuoteOrSplice = inQuoteOrSpliceOld
206
+ val body2 =
207
+ taggedTypes.getTypeTags match
208
+ case Nil => body1
209
+ case tags => tpd.Block (tags, body1).withSpan(body.span)
210
+
211
+ super .transformQuotation(body2, quote)
212
+ }
213
+
214
+ /** Transform splice
215
+ * - If inside a quote, transform the contents of the splice.
216
+ * - If inside inlined code, expand the macro code.
217
+ * - If inside of a macro definition, check the validity of the macro.
218
+ */
219
+ protected def transformSplice (body : Tree , splice : Tree )(implicit ctx : Context ): Tree = {
220
+ val inQuoteOrSpliceOld = inQuoteOrSplice
221
+ inQuoteOrSplice = true
222
+ val body1 = transform(body)(spliceContext)
223
+ inQuoteOrSplice = inQuoteOrSpliceOld
224
+ splice match {
225
+ case Apply (fun @ TypeApply (_, _ :: Nil ), _) if splice.isTerm =>
226
+ // Type of the splice itsel must also be healed
227
+ // internal.Quoted.expr[F[T]](... T ...) --> internal.Quoted.expr[F[$t]](... T ...)
228
+ val tp = healType(splice.sourcePos)(splice.tpe.widenTermRefExpr)
229
+ cpy.Apply (splice)(cpy.TypeApply (fun)(fun.fun, tpd.TypeTree (tp) :: Nil ), body1 :: Nil )
230
+ case Apply (f @ Apply (fun @ TypeApply (_, _), qctx :: Nil ), _) if splice.isTerm =>
231
+ // Type of the splice itsel must also be healed
232
+ // internal.Quoted.expr[F[T]](... T ...) --> internal.Quoted.expr[F[$t]](... T ...)
233
+ val tp = healType(splice.sourcePos)(splice.tpe.widenTermRefExpr)
234
+ cpy.Apply (splice)(cpy.Apply (f)(cpy.TypeApply (fun)(fun.fun, tpd.TypeTree (tp) :: Nil ), qctx :: Nil ), body1 :: Nil )
235
+ case splice : Select =>
236
+ val tagRef = getQuoteTypeTags.getTagRef(splice.qualifier.tpe.asInstanceOf [TermRef ])
237
+ ref(tagRef).withSpan(splice.span)
238
+ }
239
+ }
268
240
}
269
241
270
242
object PCPCheckAndHeal {
0 commit comments