Skip to content

Commit 8337eb0

Browse files
committed
Re-architecture the staging phase.
Fix #6140, fix #6772, fix #7030, fix #7892, fix #7997, fix #8651 and improve #8100.
1 parent a7ef3e2 commit 8337eb0

20 files changed

+269
-171
lines changed

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

Lines changed: 105 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,40 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
4141
override def transform(tree: Tree)(implicit ctx: Context): Tree =
4242
if (tree.source != ctx.source && tree.source.exists)
4343
transform(tree)(ctx.withSource(tree.source))
44+
else if !isInQuoteOrSplice then
45+
checkAnnotations(tree)
46+
super.transform(tree)
4447
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)
5478
}
5579

5680
/** Transform quoted trees while maintaining phase correctness */
@@ -83,188 +107,122 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
83107
case Apply(fun @ TypeApply(_, _ :: Nil), _) if splice.isTerm =>
84108
// Type of the splice itsel must also be healed
85109
// 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)
87111
cpy.Apply(splice)(cpy.TypeApply(fun)(fun.fun, tpd.TypeTree(tp) :: Nil), body1 :: Nil)
88112
case Apply(f @ Apply(fun @ TypeApply(_, _), qctx :: Nil), _) if splice.isTerm =>
89113
// Type of the splice itsel must also be healed
90114
// 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)
92116
cpy.Apply(splice)(cpy.Apply(f)(cpy.TypeApply(fun)(fun.fun, tpd.TypeTree(tp) :: Nil), qctx :: Nil), body1 :: Nil)
93117
case splice: Select =>
94118
val tagRef = getQuoteTypeTags.getTagRef(splice.qualifier.tpe.asInstanceOf[TermRef])
95119
ref(tagRef).withSpan(splice.span)
96120
}
97121
}
98122

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.
102142
*
103143
* 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`.
105147
*/
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)
153162
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, "")
160166
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))
162169
case _ =>
163170
mapOver(tp)
164-
}
165-
}
166171
}
167172

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 =
200176
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, "")
204183
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)
229186
}
230187

231188
/** 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`.
235192
*/
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 = {
237194
val reqType = defn.QuotedTypeClass.typeRef.appliedTo(tp)
238195
val tag = ctx.typer.inferImplicitArg(reqType, pos.span)
239-
240196
tag.tpe match
197+
241198
case tp: TermRef =>
242199
checkStable(tp, pos, "type witness")
243-
Some(ref(getQuoteTypeTags.getTagRef(tp)))
200+
getQuoteTypeTags.getTagRef(tp)
244201
case _: SearchFailureType =>
245202
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, "")}""")
250207
case _ =>
251208
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""")
255212
}
256213

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 = {
258215
def symStr =
259216
if (!tp.isInstanceOf[ThisType]) sym.show
260217
else if (sym.is(ModuleClass)) sym.sourceModule.show
261218
else i"${sym.name}.this"
262-
summon[Context].error(
219+
ctx.error(
263220
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)},
265222
| - but the access is at level $level.$errMsg""", pos)
266-
None
223+
tp
267224
}
225+
268226
}
269227

270228
object PCPCheckAndHeal {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ class ReifyQuotes extends MacroTransform {
301301

302302
/** Returns true if this tree will be captured by `makeLambda`. Checks phase consistency and presence of capturer. */
303303
private def isCaptured(sym: Symbol, level: Int)(implicit ctx: Context): Boolean =
304-
level == 1 && levelOf(sym).contains(1) && capturers.contains(sym)
304+
level == 1 && levelOf(sym) == 1 && capturers.contains(sym)
305305

306306
/** Transform `tree` and return the resulting tree and all `embedded` quotes
307307
* or splices as a pair.

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,19 +43,19 @@ class Staging extends MacroTransform {
4343
tree match {
4444
case PackageDef(pid, _) if tree.symbol.owner == defn.RootClass =>
4545
val checker = new PCPCheckAndHeal(freshStagingContext) {
46-
override protected def tryHeal(sym: Symbol, tp: TypeRef, pos: SourcePosition)(implicit ctx: Context): Option[tpd.Tree] = {
46+
override protected def tryHeal(sym: Symbol, tp: TypeRef, pos: SourcePosition)(implicit ctx: Context): TypeRef = {
4747
def symStr =
4848
if (sym.is(ModuleClass)) sym.sourceModule.show
4949
else i"${sym.name}.this"
5050
val errMsg = s"\nin ${ctx.owner.fullName}"
5151
assert(
5252
ctx.owner.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot) ||
53-
(sym.isType && levelOf(sym).getOrElse(0) > 0),
53+
(sym.isType && levelOf(sym) > 0),
5454
em"""access to $symStr from wrong staging level:
55-
| - the definition is at level ${levelOf(sym).getOrElse(0)},
55+
| - the definition is at level ${levelOf(sym)},
5656
| - but the access is at level $level.$errMsg""")
5757

58-
None
58+
tp
5959
}
6060
}
6161
checker.transform(tree)

0 commit comments

Comments
 (0)