Skip to content

Commit 0ee80f9

Browse files
committed
Place staged type captures in Quote AST
1 parent d103f8c commit 0ee80f9

25 files changed

+249
-251
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ object CompilationUnit {
157157
if tree.symbol.is(Flags.Inline) then
158158
containsInline = true
159159
tree match
160-
case tpd.Quote(_) =>
160+
case _: tpd.Quote =>
161161
containsQuote = true
162162
case tree: tpd.Apply if tree.symbol == defn.QuotedTypeModule_of =>
163163
containsQuote = true

compiler/src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1976,7 +1976,7 @@ object desugar {
19761976
trees foreach collect
19771977
case Block(Nil, expr) =>
19781978
collect(expr)
1979-
case Quote(body) =>
1979+
case Quote(body, _) =>
19801980
new UntypedTreeTraverser {
19811981
def traverse(tree: untpd.Tree)(using Context): Unit = tree match {
19821982
case Splice(expr) => collect(expr)

compiler/src/dotty/tools/dotc/ast/Trees.scala

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -690,9 +690,14 @@ object Trees {
690690
* when type checking. TASTy files will not contain type quotes. Type quotes are used again
691691
* in the `staging` phase to represent the reification of `Type.of[T]]`.
692692
*
693+
* Type tags `tags` are always empty before the `staging` phase. Tags for stage inconsistent
694+
* types are added in the `staging` phase to level 0 quotes. Tags for types that refer to
695+
* definitions in an outer quote are added in the `splicing` phase
696+
*
693697
* @param body The tree that was quoted
698+
* @param tags Term references to instances of `Type[T]` for `T`s that are used in the quote
694699
*/
695-
case class Quote[+T <: Untyped] private[ast] (body: Tree[T])(implicit @constructorOnly src: SourceFile)
700+
case class Quote[+T <: Untyped] private[ast] (body: Tree[T], tags: List[Tree[T]])(implicit @constructorOnly src: SourceFile)
696701
extends TermTree[T] {
697702
type ThisTree[+T <: Untyped] = Quote[T]
698703

@@ -1313,9 +1318,9 @@ object Trees {
13131318
case tree: Inlined if (call eq tree.call) && (bindings eq tree.bindings) && (expansion eq tree.expansion) => tree
13141319
case _ => finalize(tree, untpd.Inlined(call, bindings, expansion)(sourceFile(tree)))
13151320
}
1316-
def Quote(tree: Tree)(body: Tree)(using Context): Quote = tree match {
1317-
case tree: Quote if (body eq tree.body) => tree
1318-
case _ => finalize(tree, untpd.Quote(body)(sourceFile(tree)))
1321+
def Quote(tree: Tree)(body: Tree, tags: List[Tree])(using Context): Quote = tree match {
1322+
case tree: Quote if (body eq tree.body) && (tags eq tree.tags) => tree
1323+
case _ => finalize(tree, untpd.Quote(body, tags)(sourceFile(tree)))
13191324
}
13201325
def Splice(tree: Tree)(expr: Tree)(using Context): Splice = tree match {
13211326
case tree: Splice if (expr eq tree.expr) => tree
@@ -1558,8 +1563,8 @@ object Trees {
15581563
case Thicket(trees) =>
15591564
val trees1 = transform(trees)
15601565
if (trees1 eq trees) tree else Thicket(trees1)
1561-
case tree @ Quote(body) =>
1562-
cpy.Quote(tree)(transform(body)(using quoteContext))
1566+
case Quote(body, tags) =>
1567+
cpy.Quote(tree)(transform(body)(using quoteContext), transform(tags))
15631568
case tree @ Splice(expr) =>
15641569
cpy.Splice(tree)(transform(expr)(using spliceContext))
15651570
case tree @ Hole(isTerm, idx, args, content, tpt) =>
@@ -1703,8 +1708,8 @@ object Trees {
17031708
this(this(x, arg), annot)
17041709
case Thicket(ts) =>
17051710
this(x, ts)
1706-
case Quote(body) =>
1707-
this(x, body)(using quoteContext)
1711+
case Quote(body, tags) =>
1712+
this(this(x, body)(using quoteContext), tags)
17081713
case Splice(expr) =>
17091714
this(x, expr)(using spliceContext)
17101715
case Hole(_, _, args, content, tpt) =>

compiler/src/dotty/tools/dotc/ast/tpd.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,8 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
170170
def Inlined(call: Tree, bindings: List[MemberDef], expansion: Tree)(using Context): Inlined =
171171
ta.assignType(untpd.Inlined(call, bindings, expansion), bindings, expansion)
172172

173-
def Quote(body: Tree)(using Context): Quote =
174-
untpd.Quote(body).withBodyType(body.tpe)
173+
def Quote(body: Tree, tags: List[Tree])(using Context): Quote =
174+
untpd.Quote(body, tags).withBodyType(body.tpe)
175175

176176
def Splice(expr: Tree, tpe: Type)(using Context): Splice =
177177
untpd.Splice(expr).withType(tpe)

compiler/src/dotty/tools/dotc/ast/untpd.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -397,7 +397,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
397397
def SeqLiteral(elems: List[Tree], elemtpt: Tree)(implicit src: SourceFile): SeqLiteral = new SeqLiteral(elems, elemtpt)
398398
def JavaSeqLiteral(elems: List[Tree], elemtpt: Tree)(implicit src: SourceFile): JavaSeqLiteral = new JavaSeqLiteral(elems, elemtpt)
399399
def Inlined(call: tpd.Tree, bindings: List[MemberDef], expansion: Tree)(implicit src: SourceFile): Inlined = new Inlined(call, bindings, expansion)
400-
def Quote(body: Tree)(implicit src: SourceFile): Quote = new Quote(body)
400+
def Quote(body: Tree, tags: List[Tree])(implicit src: SourceFile): Quote = new Quote(body, tags)
401401
def Splice(expr: Tree)(implicit src: SourceFile): Splice = new Splice(expr)
402402
def TypeTree()(implicit src: SourceFile): TypeTree = new TypeTree()
403403
def InferredTypeTree()(implicit src: SourceFile): TypeTree = new InferredTypeTree()

compiler/src/dotty/tools/dotc/core/Phases.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ object Phases {
211211
private var mySbtExtractDependenciesPhase: Phase = _
212212
private var myPicklerPhase: Phase = _
213213
private var myInliningPhase: Phase = _
214+
private var myStagingPhase: Phase = _
214215
private var mySplicingPhase: Phase = _
215216
private var myFirstTransformPhase: Phase = _
216217
private var myCollectNullableFieldsPhase: Phase = _
@@ -235,6 +236,7 @@ object Phases {
235236
final def sbtExtractDependenciesPhase: Phase = mySbtExtractDependenciesPhase
236237
final def picklerPhase: Phase = myPicklerPhase
237238
final def inliningPhase: Phase = myInliningPhase
239+
final def stagingPhase: Phase = myStagingPhase
238240
final def splicingPhase: Phase = mySplicingPhase
239241
final def firstTransformPhase: Phase = myFirstTransformPhase
240242
final def collectNullableFieldsPhase: Phase = myCollectNullableFieldsPhase
@@ -262,6 +264,7 @@ object Phases {
262264
mySbtExtractDependenciesPhase = phaseOfClass(classOf[sbt.ExtractDependencies])
263265
myPicklerPhase = phaseOfClass(classOf[Pickler])
264266
myInliningPhase = phaseOfClass(classOf[Inlining])
267+
myStagingPhase = phaseOfClass(classOf[Staging])
265268
mySplicingPhase = phaseOfClass(classOf[Splicing])
266269
myFirstTransformPhase = phaseOfClass(classOf[FirstTransform])
267270
myCollectNullableFieldsPhase = phaseOfClass(classOf[CollectNullableFields])
@@ -449,6 +452,7 @@ object Phases {
449452
def sbtExtractDependenciesPhase(using Context): Phase = ctx.base.sbtExtractDependenciesPhase
450453
def picklerPhase(using Context): Phase = ctx.base.picklerPhase
451454
def inliningPhase(using Context): Phase = ctx.base.inliningPhase
455+
def stagingPhase(using Context): Phase = ctx.base.stagingPhase
452456
def splicingPhase(using Context): Phase = ctx.base.splicingPhase
453457
def firstTransformPhase(using Context): Phase = ctx.base.firstTransformPhase
454458
def refchecksPhase(using Context): Phase = ctx.base.refchecksPhase

compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -665,7 +665,7 @@ class TreePickler(pickler: TastyPickler) {
665665
pickleTree(hi)
666666
pickleTree(alias)
667667
}
668-
case tree @ Quote(body) =>
668+
case tree @ Quote(body, Nil) =>
669669
// TODO: Add QUOTE tag to TASTy
670670
assert(body.isTerm,
671671
"""Quote with type should not be pickled.

compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1269,7 +1269,7 @@ class TreeUnpickler(reader: TastyReader,
12691269

12701270
def quotedExpr(fn: Tree, args: List[Tree]): Tree =
12711271
val TypeApply(_, targs) = fn: @unchecked
1272-
untpd.Quote(args.head).withBodyType(targs.head.tpe)
1272+
untpd.Quote(args.head, Nil).withBodyType(targs.head.tpe)
12731273

12741274
def splicedExpr(fn: Tree, args: List[Tree]): Tree =
12751275
val TypeApply(_, targs) = fn: @unchecked

compiler/src/dotty/tools/dotc/inlines/Inliner.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -827,7 +827,7 @@ class Inliner(val call: tpd.Tree)(using Context):
827827

828828
override def typedQuote(tree: untpd.Quote, pt: Type)(using Context): Tree =
829829
super.typedQuote(tree, pt) match
830-
case Quote(Splice(inner)) => inner
830+
case Quote(Splice(inner), _) => inner
831831
case tree1 =>
832832
ctx.compilationUnit.needsStaging = true
833833
tree1

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1243,7 +1243,7 @@ object Parsers {
12431243
}
12441244
}
12451245
in.nextToken()
1246-
Quote(t)
1246+
Quote(t, Nil)
12471247
}
12481248
else
12491249
if !in.featureEnabled(Feature.symbolLiterals) then
@@ -2480,7 +2480,7 @@ object Parsers {
24802480
val body =
24812481
if (in.token == LBRACKET) inBrackets(typ())
24822482
else stagedBlock()
2483-
Quote(body)
2483+
Quote(body, Nil)
24842484
}
24852485
}
24862486
case NEW =>

compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -726,11 +726,12 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
726726
"Thicket {" ~~ toTextGlobal(trees, "\n") ~~ "}"
727727
case MacroTree(call) =>
728728
keywordStr("macro ") ~ toTextGlobal(call)
729-
case tree @ Quote(body) =>
729+
case tree @ Quote(body, tags) =>
730+
val tagsText = (keywordStr("<") ~ toTextGlobal(tags, ", ") ~ keywordStr(">")).provided(tree.tags.nonEmpty)
730731
val exprTypeText = (keywordStr("[") ~ toTextGlobal(tree.bodyType) ~ keywordStr("]")).provided(printDebug && tree.typeOpt.exists)
731732
val open = if (body.isTerm) keywordStr("{") else keywordStr("[")
732733
val close = if (body.isTerm) keywordStr("}") else keywordStr("]")
733-
keywordStr("'") ~ exprTypeText ~ open ~ toTextGlobal(body) ~ close
734+
keywordStr("'") ~ tagsText ~ exprTypeText ~ open ~ toTextGlobal(body) ~ close
734735
case Splice(expr) =>
735736
val spliceTypeText = (keywordStr("[") ~ toTextGlobal(tree.typeOpt) ~ keywordStr("]")).provided(printDebug && tree.typeOpt.exists)
736737
keywordStr("$") ~ spliceTypeText ~ keywordStr("{") ~ toTextGlobal(expr) ~ keywordStr("}")

compiler/src/dotty/tools/dotc/staging/CrossStageSafety.scala

Lines changed: 40 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -16,28 +16,36 @@ import dotty.tools.dotc.util.Property
1616
import dotty.tools.dotc.util.Spans._
1717
import dotty.tools.dotc.util.SrcPos
1818

19-
/** Checks that staging level consistency holds and heals staged types .
19+
/** Checks that staging level consistency holds and heals staged types.
2020
*
2121
* Local term references are level consistent if and only if they are used at the same level as their definition.
2222
*
2323
* Local type references can be used at the level of their definition or lower. If used used at a higher level,
2424
* it will be healed if possible, otherwise it is inconsistent.
2525
*
26-
* Type healing consists in transforming a level inconsistent type `T` into `summon[Type[T]].Underlying`.
26+
* Healing a type consists in replacing locally defined types defined at staging level 0 and used in higher levels.
27+
* For each type local `T` that is defined at level 0 and used in a quote, we summon a tag `t: Type[T]`. This `t`
28+
* tag must be defined at level 0. The tags will be listed in the `tags` of the level 0 quote (`'<t>{ ... }`) and
29+
* each reference to `T` will be replaced by `t.Underlying` in the body of the quote.
30+
*
31+
* We delay the healing of types in quotes at level 1 or higher until those quotes reach level 0. At this point
32+
* more types will be statically known and fewer types will need to be healed. This also keeps the nested quotes
33+
* in their original form, we do not want macro users to see any artifacts of this phase in quoted expressions
34+
* they might inspect.
35+
*
36+
* Type heal example:
2737
*
28-
* As references to types do not necessarily have an associated tree it is not always possible to replace the types directly.
29-
* Instead we always generate a type alias for it and place it at the start of the surrounding quote. This also avoids duplication.
30-
* For example:
3138
* '{
3239
* val x: List[T] = List[T]()
40+
* '{ .. T .. }
3341
* ()
3442
* }
3543
*
3644
* is transformed to
3745
*
38-
* '{
39-
* type t$1 = summon[Type[T]].Underlying
40-
* val x: List[t$1] = List[t$1]();
46+
* '<t>{ // where `t` is a given term of type `Type[T]`
47+
* val x: List[t.Underlying] = List[t.Underlying]();
48+
* '{ .. t.Underlying .. }
4149
* ()
4250
* }
4351
*
@@ -56,11 +64,18 @@ class CrossStageSafety extends TreeMapWithStages {
5664
case tree: Quote =>
5765
if (ctx.property(InAnnotation).isDefined)
5866
report.error("Cannot have a quote in an annotation", tree.srcPos)
59-
val body1 = transformQuoteBody(tree.body, tree.span)
60-
val stripAnnotationsDeep: TypeMap = new TypeMap:
61-
def apply(tp: Type): Type = mapOver(tp.stripAnnots)
62-
val bodyType1 = healType(tree.srcPos)(stripAnnotationsDeep(tree.bodyType))
63-
cpy.Quote(tree)(body1).withBodyType(bodyType1)
67+
68+
val tree1 =
69+
val stripAnnotationsDeep: TypeMap = new TypeMap:
70+
def apply(tp: Type): Type = mapOver(tp.stripAnnots)
71+
val bodyType1 = healType(tree.srcPos)(stripAnnotationsDeep(tree.bodyType))
72+
tree.withBodyType(bodyType1)
73+
74+
if level == 0 then
75+
val (tags, body1) = inContextWithQuoteTypeTags { transform(tree1.body)(using quoteContext) }
76+
cpy.Quote(tree1)(body1, tags)
77+
else
78+
super.transform(tree1)
6479

6580
case CancelledSplice(tree) =>
6681
transform(tree) // Optimization: `${ 'x }` --> `x`
@@ -74,22 +89,18 @@ class CrossStageSafety extends TreeMapWithStages {
7489
case tree @ QuotedTypeOf(body) =>
7590
if (ctx.property(InAnnotation).isDefined)
7691
report.error("Cannot have a quote in an annotation", tree.srcPos)
77-
body.tpe match
78-
case DirectTypeOf(termRef) =>
79-
// Optimization: `quoted.Type.of[x.Underlying](quotes)` --> `x`
80-
ref(termRef).withSpan(tree.span)
81-
case _ =>
82-
transformQuoteBody(body, tree.span) match
83-
case DirectTypeOf.Healed(termRef) =>
84-
// Optimization: `quoted.Type.of[@SplicedType type T = x.Underlying; T](quotes)` --> `x`
85-
ref(termRef).withSpan(tree.span)
86-
case transformedBody =>
87-
val quotes = transform(tree.args.head)
88-
// `quoted.Type.of[<body>](quotes)` --> `quoted.Type.of[<body2>](quotes)`
89-
val TypeApply(fun, _) = tree.fun: @unchecked
90-
if level != 0 then cpy.Apply(tree)(cpy.TypeApply(tree.fun)(fun, transformedBody :: Nil), quotes :: Nil)
91-
else tpd.Quote(transformedBody).select(nme.apply).appliedTo(quotes).withSpan(tree.span)
9292

93+
if level == 0 then
94+
val (tags, body1) = inContextWithQuoteTypeTags { transform(body)(using quoteContext) }
95+
val quotes = transform(tree.args.head)
96+
tags match
97+
case tag :: Nil if body1.isType && body1.tpe =:= tag.tpe.select(tpnme.Underlying) =>
98+
tag // Optimization: `quoted.Type.of[x.Underlying](quotes)` --> `x`
99+
case _ =>
100+
// `quoted.Type.of[<body>](<quotes>)` --> `'[<body1>].apply(<quotes>)`
101+
tpd.Quote(body1, tags).select(nme.apply).appliedTo(quotes).withSpan(tree.span)
102+
else
103+
super.transform(tree)
93104
case _: DefDef if tree.symbol.isInlineMethod =>
94105
tree
95106

@@ -137,17 +148,6 @@ class CrossStageSafety extends TreeMapWithStages {
137148
super.transform(tree)
138149
end transform
139150

140-
private def transformQuoteBody(body: Tree, span: Span)(using Context): Tree = {
141-
val taggedTypes = new QuoteTypeTags(span)
142-
val contextWithQuote =
143-
if level == 0 then contextWithQuoteTypeTags(taggedTypes)(using quoteContext)
144-
else quoteContext
145-
val transformedBody = transform(body)(using contextWithQuote)
146-
taggedTypes.getTypeTags match
147-
case Nil => transformedBody
148-
case tags => tpd.Block(tags, transformedBody).withSpan(body.span)
149-
}
150-
151151
def transformTypeAnnotationSplices(tp: Type)(using Context) = new TypeMap {
152152
def apply(tp: Type): Type = tp match
153153
case tp: AnnotatedType =>
@@ -234,7 +234,7 @@ class CrossStageSafety extends TreeMapWithStages {
234234
def unapply(tree: Splice): Option[Tree] =
235235
def rec(tree: Tree): Option[Tree] = tree match
236236
case Block(Nil, expr) => rec(expr)
237-
case Quote(inner) => Some(inner)
237+
case Quote(inner, _) => Some(inner)
238238
case _ => None
239239
rec(tree.expr)
240240
}

compiler/src/dotty/tools/dotc/staging/DirectTypeOf.scala

Lines changed: 0 additions & 25 deletions
This file was deleted.

compiler/src/dotty/tools/dotc/staging/HealType.scala

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class HealType(pos: SrcPos)(using Context) extends TypeMap {
2525
*
2626
* If `T` is a reference to a type at the wrong level, try to heal it by replacing it with
2727
* a type tag of type `quoted.Type[T]`.
28-
* The tag is generated by an instance of `QuoteTypeTags` directly if the splice is explicit
28+
* The tag is recorded by an instance of `QuoteTypeTags` directly if the splice is explicit
2929
* or indirectly by `tryHeal`.
3030
*/
3131
def apply(tp: Type): Type =
@@ -43,11 +43,9 @@ class HealType(pos: SrcPos)(using Context) extends TypeMap {
4343

4444
private def healTypeRef(tp: TypeRef): Type =
4545
tp.prefix match
46-
case NoPrefix if tp.typeSymbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot) =>
47-
tp
4846
case prefix: TermRef if tp.symbol.isTypeSplice =>
4947
checkNotWildcardSplice(tp)
50-
if level == 0 then tp else getQuoteTypeTags.getTagRef(prefix)
48+
if level == 0 then tp else getTagRef(prefix)
5149
case _: NamedType | _: ThisType | NoPrefix =>
5250
if levelInconsistentRootOfPath(tp).exists then
5351
tryHeal(tp)
@@ -58,7 +56,7 @@ class HealType(pos: SrcPos)(using Context) extends TypeMap {
5856

5957
private object NonSpliceAlias:
6058
def unapply(tp: TypeRef)(using Context): Option[Type] = tp.underlying match
61-
case TypeAlias(alias) if !tp.symbol.isTypeSplice && !tp.typeSymbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot) => Some(alias)
59+
case TypeAlias(alias) if !tp.symbol.isTypeSplice => Some(alias)
6260
case _ => None
6361

6462
private def checkNotWildcardSplice(splice: TypeRef): Unit =
@@ -78,7 +76,7 @@ class HealType(pos: SrcPos)(using Context) extends TypeMap {
7876

7977
/** Try to heal reference to type `T` used in a higher level than its definition.
8078
* Returns a reference to a type tag generated by `QuoteTypeTags` that contains a
81-
* reference to a type alias containing the equivalent of `${summon[quoted.Type[T]]}`.
79+
* reference to a type alias containing the equivalent of `${summon[quoted.Type[T]]}.Underlying`.
8280
* Emits an error if `T` cannot be healed and returns `T`.
8381
*/
8482
protected def tryHeal(tp: TypeRef): Type = {
@@ -88,7 +86,7 @@ class HealType(pos: SrcPos)(using Context) extends TypeMap {
8886
case tp: TermRef =>
8987
ctx.typer.checkStable(tp, pos, "type witness")
9088
if levelOf(tp.symbol) > 0 then tp.select(tpnme.Underlying)
91-
else getQuoteTypeTags.getTagRef(tp)
89+
else getTagRef(tp)
9290
case _: SearchFailureType =>
9391
report.error(
9492
ctx.typer.missingArgMsg(tag, reqType, "")

0 commit comments

Comments
 (0)