Skip to content

Commit 1329e3a

Browse files
committed
Remove old implementation of PCPCheckAndHeal
1 parent f1ec93a commit 1329e3a

File tree

5 files changed

+148
-451
lines changed

5 files changed

+148
-451
lines changed

compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala

Lines changed: 146 additions & 174 deletions
Original file line numberDiff line numberDiff line change
@@ -38,209 +38,133 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
3838

3939
private val InAnnotation = Property.Key[Unit]()
4040

41+
private[this] var inQuoteOrSplice = false
42+
4143
override def transform(tree: Tree)(implicit ctx: Context): Tree =
4244
if (tree.source != ctx.source && tree.source.exists)
4345
transform(tree)(ctx.withSource(tree.source))
46+
else if !inQuoteOrSplice then
47+
checkAnnotations(tree)
48+
super.transform(tree)
4449
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)
6250

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
7152

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
7459

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
9861

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
11162
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, "")
12965
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+
13081
case _ =>
131-
tree
82+
super.transform(tree)
13283
}
133-
}
13484

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)
153127
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
160133
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))
162136
case _ =>
163137
mapOver(tp)
164-
}
165-
}
166138
}
167139

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 =
200142
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)
204145
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)
210148
}
211149

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
229159
}
230160

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] = {
237162
val reqType = defn.QuotedTypeClass.typeRef.appliedTo(tp)
238163
val tag = ctx.typer.inferImplicitArg(reqType, pos.span)
239-
240164
tag.tpe match
241165
case tp: TermRef =>
242166
checkStable(tp, pos, "type witness")
243-
Some(ref(getQuoteTypeTags.getTagRef(tp)))
167+
Some(getQuoteTypeTags.getTagRef(tp))
244168
case _: SearchFailureType =>
245169
levelError(sym, tp, pos,
246170
i"""
@@ -259,12 +183,60 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
259183
if (!tp.isInstanceOf[ThisType]) sym.show
260184
else if (sym.is(ModuleClass)) sym.sourceModule.show
261185
else i"${sym.name}.this"
262-
summon[Context].error(
186+
ctx.error(
263187
em"""access to $symStr from wrong staging level:
264188
| - the definition is at level ${levelOf(sym)},
265189
| - but the access is at level $level.$errMsg""", pos)
266190
None
267191
}
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+
}
268240
}
269241

270242
object PCPCheckAndHeal {

0 commit comments

Comments
 (0)