Skip to content

Commit c084187

Browse files
committed
Place staged type captures in Quote AST
1 parent 0e00420 commit c084187

25 files changed

+233
-228
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
@@ -1986,7 +1986,7 @@ object desugar {
19861986
trees foreach collect
19871987
case Block(Nil, expr) =>
19881988
collect(expr)
1989-
case Quote(body) =>
1989+
case Quote(body, _) =>
19901990
new UntypedTreeTraverser {
19911991
def traverse(tree: untpd.Tree)(using Context): Unit = tree match {
19921992
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

@@ -1314,9 +1319,9 @@ object Trees {
13141319
case tree: Inlined if (call eq tree.call) && (bindings eq tree.bindings) && (expansion eq tree.expansion) => tree
13151320
case _ => finalize(tree, untpd.Inlined(call, bindings, expansion)(sourceFile(tree)))
13161321
}
1317-
def Quote(tree: Tree)(body: Tree)(using Context): Quote = tree match {
1318-
case tree: Quote if (body eq tree.body) => tree
1319-
case _ => finalize(tree, untpd.Quote(body)(sourceFile(tree)))
1322+
def Quote(tree: Tree)(body: Tree, tags: List[Tree])(using Context): Quote = tree match {
1323+
case tree: Quote if (body eq tree.body) && (tags eq tree.tags) => tree
1324+
case _ => finalize(tree, untpd.Quote(body, tags)(sourceFile(tree)))
13201325
}
13211326
def Splice(tree: Tree)(expr: Tree)(using Context): Splice = tree match {
13221327
case tree: Splice if (expr eq tree.expr) => tree
@@ -1559,8 +1564,8 @@ object Trees {
15591564
case Thicket(trees) =>
15601565
val trees1 = transform(trees)
15611566
if (trees1 eq trees) tree else Thicket(trees1)
1562-
case tree @ Quote(body) =>
1563-
cpy.Quote(tree)(transform(body)(using quoteContext))
1567+
case Quote(body, tags) =>
1568+
cpy.Quote(tree)(transform(body)(using quoteContext), transform(tags))
15641569
case tree @ Splice(expr) =>
15651570
cpy.Splice(tree)(transform(expr)(using spliceContext))
15661571
case tree @ Hole(_, _, args, content, tpt) =>
@@ -1704,8 +1709,8 @@ object Trees {
17041709
this(this(x, arg), annot)
17051710
case Thicket(ts) =>
17061711
this(x, ts)
1707-
case Quote(body) =>
1708-
this(x, body)(using quoteContext)
1712+
case Quote(body, tags) =>
1713+
this(this(x, body)(using quoteContext), tags)
17091714
case Splice(expr) =>
17101715
this(x, expr)(using spliceContext)
17111716
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
@@ -1266,7 +1266,7 @@ object Parsers {
12661266
}
12671267
}
12681268
in.nextToken()
1269-
Quote(t)
1269+
Quote(t, Nil)
12701270
}
12711271
else
12721272
if !in.featureEnabled(Feature.symbolLiterals) then
@@ -2498,7 +2498,7 @@ object Parsers {
24982498
val body =
24992499
if (in.token == LBRACKET) inBrackets(typ())
25002500
else stagedBlock()
2501-
Quote(body)
2501+
Quote(body, Nil)
25022502
}
25032503
}
25042504
case NEW =>

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -716,11 +716,12 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
716716
"Thicket {" ~~ toTextGlobal(trees, "\n") ~~ "}"
717717
case MacroTree(call) =>
718718
keywordStr("macro ") ~ toTextGlobal(call)
719-
case tree @ Quote(body) =>
719+
case tree @ Quote(body, tags) =>
720+
val tagsText = (keywordStr("<") ~ toTextGlobal(tags, ", ") ~ keywordStr(">")).provided(tree.tags.nonEmpty)
720721
val exprTypeText = (keywordStr("[") ~ toTextGlobal(tree.bodyType) ~ keywordStr("]")).provided(printDebug && tree.typeOpt.exists)
721722
val open = if (body.isTerm) keywordStr("{") else keywordStr("[")
722723
val close = if (body.isTerm) keywordStr("}") else keywordStr("]")
723-
keywordStr("'") ~ exprTypeText ~ open ~ toTextGlobal(body) ~ close
724+
keywordStr("'") ~ tagsText ~ exprTypeText ~ open ~ toTextGlobal(body) ~ close
724725
case Splice(expr) =>
725726
val spliceTypeText = (keywordStr("[") ~ toTextGlobal(tree.typeOpt) ~ keywordStr("]")).provided(printDebug && tree.typeOpt.exists)
726727
keywordStr("$") ~ spliceTypeText ~ keywordStr("{") ~ toTextGlobal(expr) ~ keywordStr("}")

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

Lines changed: 27 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -16,28 +16,27 @@ 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`.
27-
*
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:
26+
* For each type `T` that is defined at level 0 and used in later level, we summon a `t: Type[T]`. The `t`
27+
* will be listed in the `tags` of the level 0 quote (`'<t>{ ... }`) and each reference to `T` will be replaced by
28+
* `t.Underlying` in the body of the quote. For example:
3129
* '{
3230
* val x: List[T] = List[T]()
31+
* '{ .. T .. }
3332
* ()
3433
* }
3534
*
3635
* is transformed to
3736
*
38-
* '{
39-
* type t$1 = summon[Type[T]].Underlying
40-
* val x: List[t$1] = List[t$1]();
37+
* '<t>{ // where `t` is a given term of type `Type[T]`
38+
* val x: List[t.Underlying] = List[t.Underlying]();
39+
* '{ .. t.Underlying .. }
4140
* ()
4241
* }
4342
*
@@ -56,11 +55,11 @@ class CrossStageSafety extends TreeMapWithStages {
5655
case tree: Quote =>
5756
if (ctx.property(InAnnotation).isDefined)
5857
report.error("Cannot have a quote in an annotation", tree.srcPos)
59-
val body1 = transformQuoteBody(tree.body, tree.span)
58+
val (tags, body1) = transformQuoteBody(tree.body, tree.span)
6059
val stripAnnotationsDeep: TypeMap = new TypeMap:
6160
def apply(tp: Type): Type = mapOver(tp.stripAnnots)
6261
val bodyType1 = healType(tree.srcPos)(stripAnnotationsDeep(tree.bodyType))
63-
cpy.Quote(tree)(body1).withBodyType(bodyType1)
62+
cpy.Quote(tree)(body1, tags).withBodyType(bodyType1)
6463

6564
case CancelledSplice(tree) =>
6665
transform(tree) // Optimization: `${ 'x }` --> `x`
@@ -74,21 +73,17 @@ class CrossStageSafety extends TreeMapWithStages {
7473
case tree @ QuotedTypeOf(body) =>
7574
if (ctx.property(InAnnotation).isDefined)
7675
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)
76+
77+
val (tags, body1) = transformQuoteBody(body, tree.span)
78+
tags match
79+
case tag :: Nil if body1.isType && body1.tpe =:= tag.tpe.select(tpnme.Underlying) =>
80+
tag // Optimization: `quoted.Type.of[x.Underlying](quotes)` --> `x`
8181
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)
82+
// `quoted.Type.of[<body>](<quotes>)` --> `'[<body1>].apply(<quotes>)`
83+
val quotes = transform(tree.args.head)
84+
val TypeApply(fun, _) = tree.fun: @unchecked
85+
if level != 0 then cpy.Apply(tree)(cpy.TypeApply(tree.fun)(fun, body1 :: Nil), quotes :: Nil)
86+
else tpd.Quote(body1, tags).select(nme.apply).appliedTo(quotes).withSpan(tree.span)
9287

9388
case _: DefDef if tree.symbol.isInlineMethod =>
9489
tree
@@ -137,16 +132,12 @@ class CrossStageSafety extends TreeMapWithStages {
137132
super.transform(tree)
138133
end transform
139134

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-
}
135+
private def transformQuoteBody(body: Tree, span: Span)(using Context): (List[Tree], Tree) =
136+
if level != 0 then
137+
(Nil, transform(body)(using quoteContext))
138+
else inContextWithQuoteTypeTags {
139+
transform(body)(using quoteContext)
140+
}
150141

151142
def transformTypeAnnotationSplices(tp: Type)(using Context) = new TypeMap {
152143
def apply(tp: Type): Type = tp match
@@ -234,7 +225,7 @@ class CrossStageSafety extends TreeMapWithStages {
234225
def unapply(tree: Splice): Option[Tree] =
235226
def rec(tree: Tree): Option[Tree] = tree match
236227
case Block(Nil, expr) => rec(expr)
237-
case Quote(inner) => Some(inner)
228+
case Quote(inner, _) => Some(inner)
238229
case _ => None
239230
rec(tree.expr)
240231
}

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: 4 additions & 4 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 =
@@ -47,7 +47,7 @@ class HealType(pos: SrcPos)(using Context) extends TypeMap {
4747
tp
4848
case prefix: TermRef if tp.symbol.isTypeSplice =>
4949
checkNotWildcardSplice(tp)
50-
if level == 0 then tp else getQuoteTypeTags.getTagRef(prefix)
50+
if level == 0 then tp else getTagRef(prefix)
5151
case _: NamedType | _: ThisType | NoPrefix =>
5252
if levelInconsistentRootOfPath(tp).exists then
5353
tryHeal(tp)
@@ -78,7 +78,7 @@ class HealType(pos: SrcPos)(using Context) extends TypeMap {
7878

7979
/** Try to heal reference to type `T` used in a higher level than its definition.
8080
* 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]]}`.
81+
* reference to a type alias containing the equivalent of `${summon[quoted.Type[T]]}.Underlying`.
8282
* Emits an error if `T` cannot be healed and returns `T`.
8383
*/
8484
protected def tryHeal(tp: TypeRef): Type = {
@@ -88,7 +88,7 @@ class HealType(pos: SrcPos)(using Context) extends TypeMap {
8888
case tp: TermRef =>
8989
ctx.typer.checkStable(tp, pos, "type witness")
9090
if levelOf(tp.symbol) > 0 then tp.select(tpnme.Underlying)
91-
else getQuoteTypeTags.getTagRef(tp)
91+
else getTagRef(tp)
9292
case _: SearchFailureType =>
9393
report.error(
9494
ctx.typer.missingArgMsg(tag, reqType, "")

0 commit comments

Comments
 (0)