Skip to content

Commit 2acb6f7

Browse files
committed
WIP Reimplement staging
1 parent 38cb5e3 commit 2acb6f7

File tree

6 files changed

+353
-3
lines changed

6 files changed

+353
-3
lines changed

compiler/src/dotty/tools/dotc/Compiler.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ class Compiler {
3838
protected def frontendPhases: List[List[Phase]] =
3939
List(new FrontEnd) :: // Compiler frontend: scanner, parser, namer, typer
4040
List(new YCheckPositions) :: // YCheck positions
41-
List(new Staging) :: // Check PCP, heal quoted types and expand macros
41+
List(new Staging2) :: // Check PCP, heal quoted types and expand macros
42+
// List(new Staging) :: // Check PCP, heal quoted types and expand macros
4243
List(new sbt.ExtractDependencies) :: // Sends information on classes' dependencies to sbt via callbacks
4344
List(new semanticdb.ExtractSemanticDB) :: // Extract info into .semanticdb files
4445
List(new PostTyper) :: // Additional checks and cleanups after type checking
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
package dotty.tools.dotc
2+
package transform
3+
4+
import dotty.tools.dotc.ast.Trees._
5+
import dotty.tools.dotc.ast.{TreeTypeMap, tpd, untpd}
6+
import dotty.tools.dotc.core.Annotations.BodyAnnotation
7+
import dotty.tools.dotc.core.Constants._
8+
import dotty.tools.dotc.core.Contexts._
9+
import dotty.tools.dotc.core.Decorators._
10+
import dotty.tools.dotc.core.Flags._
11+
import dotty.tools.dotc.core.quoted._
12+
import dotty.tools.dotc.core.NameKinds._
13+
import dotty.tools.dotc.core.StagingContext._
14+
import dotty.tools.dotc.core.StdNames._
15+
import dotty.tools.dotc.core.Symbols._
16+
import dotty.tools.dotc.core.Types._
17+
import dotty.tools.dotc.util.SourcePosition
18+
import dotty.tools.dotc.util.Spans._
19+
import dotty.tools.dotc.transform.SymUtils._
20+
import dotty.tools.dotc.transform.TreeMapWithStages._
21+
import dotty.tools.dotc.typer.Checking
22+
import dotty.tools.dotc.typer.Implicits.SearchFailureType
23+
import dotty.tools.dotc.typer.Inliner
24+
import dotty.tools.dotc.core.Annotations._
25+
26+
import scala.collection.mutable
27+
import dotty.tools.dotc.util.SourcePosition
28+
import dotty.tools.dotc.util.Property
29+
30+
import scala.annotation.constructorOnly
31+
32+
/** Checks that the Phase Consistency Principle (PCP) holds and heals types.
33+
*
34+
* Type healing consists in transforming a phase inconsistent type `T` into a splice of `implicitly[Type[T]]`.
35+
*/
36+
class PCPCheckAndHeal2(@constructorOnly ictx: Context) extends TreeMapWithStages(ictx) with Checking {
37+
import tpd._
38+
39+
private val InAnnotation = Property.Key[Unit]()
40+
41+
private var inQuoteOrSplice = false
42+
43+
override def transform(tree: Tree)(implicit ctx: Context): Tree =
44+
if (tree.source != ctx.source && tree.source.exists)
45+
transform(tree)(ctx.withSource(tree.source))
46+
else tree match {
47+
case tree: DefDef if tree.symbol.is(Inline) && level > 0 => EmptyTree
48+
case _ if !inQuoteOrSplice => super.transform(tree)
49+
50+
// Types
51+
52+
case _: TypTree =>
53+
if needsHealing(tree.tpe) then TypeTree(healType(tree.sourcePos)(tree.tpe)).withSpan(tree.span)
54+
else tree
55+
case _: RefTree if tree.isType =>
56+
if needsHealing(tree.tpe) then TypeTree(healType(tree.sourcePos)(tree.tpe)).withSpan(tree.span)
57+
else tree
58+
59+
// Terms
60+
61+
case _: This =>
62+
if level != levelOf(tree.symbol).getOrElse(0) then
63+
levelError(tree.symbol, tree.tpe, tree.sourcePos, "")
64+
tree
65+
case _: Ident =>
66+
checkTermLevel(tree).withType(healTermType(tree.sourcePos)(tree.tpe))
67+
case _: ValDef | _: Bind =>
68+
if needsHealing(tree.symbol.info) then
69+
tree.symbol.info = healType(tree.sourcePos)(tree.symbol.info)
70+
super.transform(tree)
71+
case _: DefDef =>
72+
lazy val annotCtx = ctx.fresh.setProperty(InAnnotation, true).withOwner(tree.symbol)
73+
for (annot <- tree.symbol.annotations) annot match {
74+
case annot: BodyAnnotation => annot // already checked in PrepareInlineable before the creation of the BodyAnnotation
75+
case annot => transform(annot.tree)(using annotCtx)
76+
}
77+
super.transform(tree)
78+
case _ =>
79+
super.transform(tree)
80+
}
81+
82+
83+
private def needsHealing(tp: Type)(using Context) = new ExistsAccumulator(
84+
_ match
85+
case tp @ TypeRef(NoPrefix, _) => level > levelOf(tp.symbol).getOrElse(0)
86+
case tp: TypeRef if tp.symbol.isSplice && level > 0 => true
87+
case tp => false
88+
).apply(false, tp)
89+
90+
private def healType(pos: SourcePosition)(using Context) = new TypeMap {
91+
def apply(tp: Type): Type =
92+
tp match
93+
case tp @ TypeRef(prefix: TermRef, _) if tp.symbol.isSplice =>
94+
if level > 0 then getQuoteTypeTags.getTagRef(prefix)
95+
else tp
96+
case tp @ TypeRef(NoPrefix, _) if level > levelOf(tp.symbol).getOrElse(0) =>
97+
tryHeal(tp.symbol, tp, pos).getOrElse(tp)
98+
case _ => mapOver(tp)
99+
}
100+
101+
private def healTermType(pos: SourcePosition)(using Context) = new TypeMap {
102+
def apply(tp: Type): Type =
103+
tp match
104+
case tp @ TypeRef(NoPrefix, _) if level > levelOf(tp.symbol).getOrElse(0) =>
105+
tryHeal(tp.symbol, tp, pos).getOrElse(tp)
106+
case _ =>
107+
if tp.typeSymbol.is(Package) then tp
108+
else mapOver(tp)
109+
}
110+
111+
private def checkTermLevel(tree: Tree)(using Context): tree.type = {
112+
new TypeTraverser {
113+
def traverse(tp: Type): Unit =
114+
tp match
115+
case tp @ TermRef(NoPrefix, _) if !tp.symbol.isStatic && level != levelOf(tp.symbol).getOrElse(0) =>
116+
levelError(tree.symbol, tree.tpe, tree.sourcePos, "")
117+
case _ => traverseChildren(tp)
118+
}.traverse(tree.tpe)
119+
tree
120+
}
121+
122+
protected def tryHeal(sym: Symbol, tp: TypeRef, pos: SourcePosition)(implicit ctx: Context): Option[TypeRef] = {
123+
val reqType = defn.QuotedTypeClass.typeRef.appliedTo(tp)
124+
val tag = ctx.typer.inferImplicitArg(reqType, pos.span)
125+
tag.tpe match
126+
case tp: TermRef =>
127+
checkStable(tp, pos, "type witness")
128+
Some(getQuoteTypeTags.getTagRef(tp))
129+
case _: SearchFailureType =>
130+
levelError(sym, tp, pos,
131+
i"""
132+
|
133+
| The access would be accepted with the right type tag, but
134+
| ${ctx.typer.missingArgMsg(tag, reqType, "")}""")
135+
case _ =>
136+
levelError(sym, tp, pos,
137+
i"""
138+
|
139+
| The access would be accepted with a given $reqType""")
140+
}
141+
142+
private def levelError(sym: Symbol, tp: Type, pos: SourcePosition, errMsg: String)(using Context) = {
143+
def symStr =
144+
if (!tp.isInstanceOf[ThisType]) sym.show
145+
else if (sym.is(ModuleClass)) sym.sourceModule.show
146+
else i"${sym.name}.this"
147+
ctx.error(
148+
em"""access to $symStr from wrong staging level:
149+
| - the definition is at level ${levelOf(sym).getOrElse(0)},
150+
| - but the access is at level $level.$errMsg""", pos)
151+
None
152+
}
153+
154+
/** Transform quoted trees while maintaining phase correctness */
155+
override protected def transformQuotation(body: Tree, quote: Tree)(implicit ctx: Context): Tree = {
156+
val taggedTypes = new PCPCheckAndHeal.QuoteTypeTags(quote.span)(using ctx)
157+
if (ctx.property(InAnnotation).isDefined)
158+
ctx.error("Cannot have a quote in an annotation", quote.sourcePos)
159+
160+
val contextWithQuote =
161+
if level == 0 then contextWithQuoteTypeTags(taggedTypes)(quoteContext)
162+
else quoteContext
163+
val inQuoteOrSpliceOld = inQuoteOrSplice
164+
inQuoteOrSplice = true
165+
val body1 = transform(body)(contextWithQuote)
166+
inQuoteOrSplice = inQuoteOrSpliceOld
167+
val body2 =
168+
taggedTypes.getTypeTags match
169+
case Nil => body1
170+
case tags => tpd.Block(tags, body1).withSpan(body.span)
171+
172+
super.transformQuotation(body2, quote)
173+
}
174+
175+
/** Transform splice
176+
* - If inside a quote, transform the contents of the splice.
177+
* - If inside inlined code, expand the macro code.
178+
* - If inside of a macro definition, check the validity of the macro.
179+
*/
180+
protected def transformSplice(body: Tree, splice: Tree)(implicit ctx: Context): Tree = {
181+
val inQuoteOrSpliceOld = inQuoteOrSplice
182+
inQuoteOrSplice = true
183+
val body1 = transform(body)(spliceContext)
184+
inQuoteOrSplice = inQuoteOrSpliceOld
185+
splice match {
186+
case Apply(fun @ TypeApply(_, _ :: Nil), _) if splice.isTerm =>
187+
// Type of the splice itsel must also be healed
188+
// internal.Quoted.expr[F[T]](... T ...) --> internal.Quoted.expr[F[$t]](... T ...)
189+
val tp = healType(splice.sourcePos)(splice.tpe.widenTermRefExpr)
190+
cpy.Apply(splice)(cpy.TypeApply(fun)(fun.fun, tpd.TypeTree(tp) :: Nil), body1 :: Nil)
191+
case Apply(f @ Apply(fun @ TypeApply(_, _), qctx :: Nil), _) if splice.isTerm =>
192+
// Type of the splice itsel must also be healed
193+
// internal.Quoted.expr[F[T]](... T ...) --> internal.Quoted.expr[F[$t]](... T ...)
194+
val tp = healType(splice.sourcePos)(splice.tpe.widenTermRefExpr)
195+
cpy.Apply(splice)(cpy.Apply(f)(cpy.TypeApply(fun)(fun.fun, tpd.TypeTree(tp) :: Nil), qctx :: Nil), body1 :: Nil)
196+
case splice: Select =>
197+
val tagRef = getQuoteTypeTags.getTagRef(splice.qualifier.tpe.asInstanceOf[TermRef])
198+
ref(tagRef).withSpan(splice.span)
199+
}
200+
}
201+
}
202+
203+
object PCPCheckAndHeal2 {
204+
import tpd._
205+
206+
// class QuoteTypeTags(span: Span)(using Context) {
207+
208+
// private val tags = collection.mutable.LinkedHashMap.empty[Symbol, TypeDef]
209+
210+
// def getTagRef(spliced: TermRef): TypeRef = {
211+
// val typeDef = tags.getOrElseUpdate(spliced.symbol, mkTagSymbolAndAssignType(spliced))
212+
// typeDef.symbol.typeRef
213+
// }
214+
215+
// def getTypeTags: List[TypeDef] = tags.valuesIterator.toList
216+
217+
// private def mkTagSymbolAndAssignType(spliced: TermRef): TypeDef = {
218+
// val splicedTree = tpd.ref(spliced).withSpan(span)
219+
// val rhs = splicedTree.select(tpnme.splice).withSpan(span)
220+
// val alias = ctx.typeAssigner.assignType(untpd.TypeBoundsTree(rhs, rhs), rhs, rhs, EmptyTree)
221+
// val local = ctx.newSymbol(
222+
// owner = ctx.owner,
223+
// name = UniqueName.fresh((splicedTree.symbol.name.toString + "$_").toTermName).toTypeName,
224+
// flags = Synthetic,
225+
// info = TypeAlias(splicedTree.tpe.select(tpnme.splice)),
226+
// coord = span).asType
227+
// local.addAnnotation(Annotation(defn.InternalQuoted_QuoteTypeTagAnnot))
228+
// ctx.typeAssigner.assignType(untpd.TypeDef(local.name, alias), local)
229+
// }
230+
231+
// }
232+
233+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package dotty.tools.dotc
2+
package transform
3+
4+
import dotty.tools.dotc.ast.Trees._
5+
import dotty.tools.dotc.ast.{TreeTypeMap, tpd, untpd}
6+
import dotty.tools.dotc.core.Constants._
7+
import dotty.tools.dotc.core.Contexts._
8+
import dotty.tools.dotc.core.Decorators._
9+
import dotty.tools.dotc.core.Flags._
10+
import dotty.tools.dotc.core.quoted._
11+
import dotty.tools.dotc.core.NameKinds._
12+
import dotty.tools.dotc.core.StagingContext._
13+
import dotty.tools.dotc.core.StdNames._
14+
import dotty.tools.dotc.core.Symbols._
15+
import dotty.tools.dotc.core.tasty.TreePickler.Hole
16+
import dotty.tools.dotc.core.Types._
17+
import dotty.tools.dotc.util.{SourceFile, SourcePosition}
18+
import dotty.tools.dotc.transform.SymUtils._
19+
import dotty.tools.dotc.transform.TreeMapWithStages._
20+
import dotty.tools.dotc.typer.Implicits.SearchFailureType
21+
import dotty.tools.dotc.typer.Inliner
22+
23+
import scala.collection.mutable
24+
import dotty.tools.dotc.util.SourcePosition
25+
26+
import scala.annotation.constructorOnly
27+
28+
/** Checks that the Phase Consistency Principle (PCP) holds and heals types.
29+
*
30+
* Type healing consists in transforming a phase inconsistent type `T` into `${ implicitly[Type[T]] }`.
31+
*/
32+
class Staging2 extends MacroTransform {
33+
import tpd._
34+
import Staging._
35+
36+
override def phaseName: String = Staging2.name
37+
38+
override def allowsImplicitSearch: Boolean = true
39+
40+
override def checkPostCondition(tree: Tree)(implicit ctx: Context): Unit =
41+
if (ctx.phase <= ctx.reifyQuotesPhase) {
42+
// Recheck that PCP holds but do not heal any inconsistent types as they should already have been heald
43+
tree match {
44+
case PackageDef(pid, _) if tree.symbol.owner == defn.RootClass =>
45+
val checker = new PCPCheckAndHeal2(freshStagingContext) {
46+
override protected def tryHeal(sym: Symbol, tp: TypeRef, pos: SourcePosition)(implicit ctx: Context): Option[TypeRef] = {
47+
def symStr =
48+
if (sym.is(ModuleClass)) sym.sourceModule.show
49+
else i"${sym.name}.this"
50+
val errMsg = s"\nin ${ctx.owner.fullName}"
51+
assert(
52+
ctx.owner.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot) ||
53+
(sym.isType && levelOf(sym).getOrElse(0) > 0),
54+
em"""access to $symStr from wrong staging level:
55+
| - the definition is at level ${levelOf(sym).getOrElse(0)},
56+
| - but the access is at level $level.$errMsg""")
57+
58+
None
59+
}
60+
}
61+
checker.transform(tree)
62+
case _ =>
63+
}
64+
65+
tree.tpe match {
66+
case tpe @ TypeRef(prefix, _) if tpe.typeSymbol eq defn.QuotedType_splice =>
67+
// Type splices must have a know term ref, usually to an implicit argument
68+
// This is mostly intended to catch `quoted.Type[T]#splice` types which should just be `T`
69+
assert(prefix.isInstanceOf[TermRef] || prefix.isInstanceOf[ThisType], prefix)
70+
case _ =>
71+
// OK
72+
}
73+
}
74+
75+
override def run(implicit ctx: Context): Unit =
76+
if (ctx.compilationUnit.needsStaging) super.run(freshStagingContext)
77+
78+
protected def newTransformer(implicit ctx: Context): Transformer = new Transformer {
79+
override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree =
80+
new PCPCheckAndHeal2(ctx).transform(tree)
81+
}
82+
}
83+
84+
85+
object Staging2 {
86+
val name: String = "staging2"
87+
}

tests/neg-macros/quote-this.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ class Foo {
55
def f(using QuoteContext): Unit = '{
66
def bar[T](x: T): T = x
77
bar[
8-
this.type // error
8+
this.type
99
] {
10-
this // error
10+
this // error
1111
}
1212
}
1313

tests/pos-macros/i6140.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import scala.quoted._
2+
sealed trait Trait[T] {
3+
type t = T
4+
}
5+
6+
object O {
7+
def fn[T:Type](t : Trait[T])(using QuoteContext): Type[T] = '[t.t]
8+
}

tests/pos-macros/i8100.scala

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import scala.quoted._
2+
3+
class M {
4+
type E
5+
}
6+
7+
def f[T: Type](using QuoteContext) =
8+
Expr.summon[M] match
9+
case Some('{ $mm : $tt }) =>
10+
'{
11+
val m = $mm
12+
type ME = m.E
13+
${ g[ME](using '[ME]) }
14+
${ g[m.E](using '[ME]) }
15+
${ g[ME](using '[m.E]) }
16+
${ g[m.E](using '[m.E]) }
17+
${ g[ME] }
18+
${ g[m.E] }
19+
}
20+
21+
def g[T](using Type[T]) = ???

0 commit comments

Comments
 (0)