Skip to content

Commit ec3cdad

Browse files
authored
Unencode quote and splice trees (#17342)
We currently use the `Quote` and `Splice` ASTs for untyped quotes and splices. Then when we type them we encode them into `scala.quoted.runtime.Expr.{quote,splice,nestedSplice}`. This non-semantic representation if fragile and the source of many past bug. In this PR we change the internal representation of quotes and splices to use the `Quote` and `Splice` ASTs as typed trees. The core of this change in the AST representation and how we type them in `QuotesAndSplices`. Other changes consist in adapting the code from one representation to the other. ```diff - case class Quote(quoted: Tree)(implicit @constructorOnly src: SourceFile) extends TermTree + case class Quote[+T <: Untyped] private[ast] (body: Tree[T])(implicit @constructorOnly src: SourceFile) extends TermTree[T] - case class Splice(expr: Tree)(implicit @constructorOnly src: SourceFile) extends TermTree + case class Splice[+T <: Untyped] private[ast] (expr: Tree[T])(implicit @constructorOnly src: SourceFile) extends TermTree[T] ``` * We do not change the TASTy (file and reflection) representation of quotes and splices. This is something that we should consider adding in a future PR. * We can use the simpler encoding of `splice` in all cases. The additional `Quotes` in `nestedSplice` is not used. * This does not change binary compatibility.
2 parents 4f331a3 + 9214daa commit ec3cdad

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+488
-455
lines changed

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,11 +154,13 @@ object CompilationUnit {
154154
var containsCaptureChecking = false
155155
var containsMacroAnnotation = false
156156
def traverse(tree: Tree)(using Context): Unit = {
157-
if (tree.symbol.isQuote)
158-
containsQuote = true
159157
if tree.symbol.is(Flags.Inline) then
160158
containsInline = true
161159
tree match
160+
case tpd.Quote(_) =>
161+
containsQuote = true
162+
case tree: tpd.Apply if tree.symbol == defn.QuotedTypeModule_of =>
163+
containsQuote = true
162164
case Import(qual, selectors) =>
163165
tpd.languageImport(qual) match
164166
case Some(prefix) =>

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1986,13 +1986,13 @@ object desugar {
19861986
trees foreach collect
19871987
case Block(Nil, expr) =>
19881988
collect(expr)
1989-
case Quote(expr) =>
1989+
case Quote(body) =>
19901990
new UntypedTreeTraverser {
19911991
def traverse(tree: untpd.Tree)(using Context): Unit = tree match {
19921992
case Splice(expr) => collect(expr)
19931993
case _ => traverseChildren(tree)
19941994
}
1995-
}.traverse(expr)
1995+
}.traverse(body)
19961996
case CapturingTypeTree(refs, parent) =>
19971997
collect(parent)
19981998
case _ =>

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

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,33 +1026,19 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
10261026
case t => assert(t.span.exists, i"$t")
10271027
}
10281028

1029-
/** Extractors for quotes */
1030-
object Quoted {
1029+
object QuotedTypeOf {
10311030
/** Extracts the content of a quoted tree.
10321031
* The result can be the contents of a term or type quote, which
10331032
* will return a term or type tree respectively.
10341033
*/
10351034
def unapply(tree: tpd.Apply)(using Context): Option[tpd.Tree] =
1036-
if tree.symbol == defn.QuotedRuntime_exprQuote then
1037-
// quoted.runtime.Expr.quote[T](<body>)
1038-
Some(tree.args.head)
1039-
else if tree.symbol == defn.QuotedTypeModule_of then
1035+
if tree.symbol == defn.QuotedTypeModule_of then
10401036
// quoted.Type.of[<body>](quotes)
10411037
val TypeApply(_, body :: _) = tree.fun: @unchecked
10421038
Some(body)
10431039
else None
10441040
}
10451041

1046-
/** Extractors for splices */
1047-
object Spliced {
1048-
/** Extracts the content of a spliced expression tree.
1049-
* The result can be the contents of a term splice, which
1050-
* will return a term tree.
1051-
*/
1052-
def unapply(tree: tpd.Apply)(using Context): Option[tpd.Tree] =
1053-
if tree.symbol.isExprSplice then Some(tree.args.head) else None
1054-
}
1055-
10561042
/** Extractors for type splices */
10571043
object SplicedType {
10581044
/** Extracts the content of a spliced type tree.

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

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import annotation.unchecked.uncheckedVariance
1717
import annotation.constructorOnly
1818
import compiletime.uninitialized
1919
import Decorators._
20+
import staging.StagingLevel.*
2021

2122
object Trees {
2223

@@ -677,6 +678,60 @@ object Trees {
677678
override def isType = expansion.isType
678679
}
679680

681+
/** A tree representing a quote `'{ body }` or `'[ body ]`.
682+
* `Quote`s are created by the `Parser`. In typer they can be typed as a
683+
* `Quote` with a known `tpt` or desugared and typed as a quote pattern.
684+
*
685+
* `Quotes` are checked and transformed in the `staging`, `splicing` and `pickleQuotes`
686+
* phases. After `pickleQuotes` phase, the only quotes that exist are in `inline`
687+
* methods. These are dropped when we remove the inline method implementations.
688+
*
689+
* Type quotes `'[body]` from the parser are desugared into quote patterns (using a `Type.of[T]]`)
690+
* when type checking. TASTy files will not contain type quotes. Type quotes are used again
691+
* in the `staging` phase to represent the reification of `Type.of[T]]`.
692+
*
693+
* @param body The tree that was quoted
694+
*/
695+
case class Quote[+T <: Untyped] private[ast] (body: Tree[T])(implicit @constructorOnly src: SourceFile)
696+
extends TermTree[T] {
697+
type ThisTree[+T <: Untyped] = Quote[T]
698+
699+
/** Is this a type quote `'[tpe]' */
700+
def isTypeQuote = body.isType
701+
702+
/** Type of the quoted expression as seen from outside the quote */
703+
def bodyType(using Context): Type =
704+
val quoteType = typeOpt // `Quotes ?=> Expr[T]` or `Quotes ?=> Type[T]`
705+
val exprType = quoteType.argInfos.last // `Expr[T]` or `Type[T]`
706+
exprType.argInfos.head // T
707+
708+
/** Set the type of the body of the quote */
709+
def withBodyType(tpe: Type)(using Context): Quote[Type] =
710+
val exprType = // `Expr[T]` or `Type[T]`
711+
if body.isTerm then defn.QuotedExprClass.typeRef.appliedTo(tpe)
712+
else defn.QuotedTypeClass.typeRef.appliedTo(tpe)
713+
val quoteType = // `Quotes ?=> Expr[T]` or `Quotes ?=> Type[T]`
714+
defn.FunctionType(1, isContextual = true)
715+
.appliedTo(defn.QuotesClass.typeRef, exprType)
716+
withType(quoteType)
717+
}
718+
719+
/** A tree representing a splice `${ expr }`
720+
*
721+
* `Splice`s are created by the `Parser`. In typer they can be typed as a
722+
* `Splice` with a known `tpt` or desugared and typed as a quote pattern holes.
723+
*
724+
* `Splice` are checked and transformed in the `staging` and `splicing` phases.
725+
* After `splicing` phase, the only splices that exist are in `inline`
726+
* methods. These are dropped when we remove the inline method implementations.
727+
*
728+
* @param expr The tree that was spliced
729+
*/
730+
case class Splice[+T <: Untyped] private[ast] (expr: Tree[T])(implicit @constructorOnly src: SourceFile)
731+
extends TermTree[T] {
732+
type ThisTree[+T <: Untyped] = Splice[T]
733+
}
734+
680735
/** A type tree that represents an existing or inferred type */
681736
case class TypeTree[+T <: Untyped]()(implicit @constructorOnly src: SourceFile)
682737
extends DenotingTree[T] with TypTree[T] {
@@ -1087,6 +1142,8 @@ object Trees {
10871142
type SeqLiteral = Trees.SeqLiteral[T]
10881143
type JavaSeqLiteral = Trees.JavaSeqLiteral[T]
10891144
type Inlined = Trees.Inlined[T]
1145+
type Quote = Trees.Quote[T]
1146+
type Splice = Trees.Splice[T]
10901147
type TypeTree = Trees.TypeTree[T]
10911148
type InferredTypeTree = Trees.InferredTypeTree[T]
10921149
type SingletonTypeTree = Trees.SingletonTypeTree[T]
@@ -1257,6 +1314,14 @@ object Trees {
12571314
case tree: Inlined if (call eq tree.call) && (bindings eq tree.bindings) && (expansion eq tree.expansion) => tree
12581315
case _ => finalize(tree, untpd.Inlined(call, bindings, expansion)(sourceFile(tree)))
12591316
}
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)))
1320+
}
1321+
def Splice(tree: Tree)(expr: Tree)(using Context): Splice = tree match {
1322+
case tree: Splice if (expr eq tree.expr) => tree
1323+
case _ => finalize(tree, untpd.Splice(expr)(sourceFile(tree)))
1324+
}
12601325
def SingletonTypeTree(tree: Tree)(ref: Tree)(using Context): SingletonTypeTree = tree match {
12611326
case tree: SingletonTypeTree if (ref eq tree.ref) => tree
12621327
case _ => finalize(tree, untpd.SingletonTypeTree(ref)(sourceFile(tree)))
@@ -1494,6 +1559,10 @@ object Trees {
14941559
case Thicket(trees) =>
14951560
val trees1 = transform(trees)
14961561
if (trees1 eq trees) tree else Thicket(trees1)
1562+
case tree @ Quote(body) =>
1563+
cpy.Quote(tree)(transform(body)(using quoteContext))
1564+
case tree @ Splice(expr) =>
1565+
cpy.Splice(tree)(transform(expr)(using spliceContext))
14971566
case tree @ Hole(_, _, args, content, tpt) =>
14981567
cpy.Hole(tree)(args = transform(args), content = transform(content), tpt = transform(tpt))
14991568
case _ =>
@@ -1635,6 +1704,10 @@ object Trees {
16351704
this(this(x, arg), annot)
16361705
case Thicket(ts) =>
16371706
this(x, ts)
1707+
case Quote(body) =>
1708+
this(x, body)(using quoteContext)
1709+
case Splice(expr) =>
1710+
this(x, expr)(using spliceContext)
16381711
case Hole(_, _, args, content, tpt) =>
16391712
this(this(this(x, args), content), tpt)
16401713
case _ =>

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,12 @@ 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)
175+
176+
def Splice(expr: Tree, tpe: Type)(using Context): Splice =
177+
untpd.Splice(expr).withType(tpe)
178+
173179
def TypeTree(tp: Type, inferred: Boolean = false)(using Context): TypeTree =
174180
(if inferred then untpd.InferredTypeTree() else untpd.TypeTree()).withType(tp)
175181

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

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,6 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
111111
override def isType: Boolean = !isTerm
112112
}
113113
case class Throw(expr: Tree)(implicit @constructorOnly src: SourceFile) extends TermTree
114-
case class Quote(quoted: Tree)(implicit @constructorOnly src: SourceFile) extends TermTree
115-
case class Splice(expr: Tree)(implicit @constructorOnly src: SourceFile) extends TermTree {
116-
def isInBraces: Boolean = span.end != expr.span.end
117-
}
118114
case class ForYield(enums: List[Tree], expr: Tree)(implicit @constructorOnly src: SourceFile) extends TermTree
119115
case class ForDo(enums: List[Tree], body: Tree)(implicit @constructorOnly src: SourceFile) extends TermTree
120116
case class GenFrom(pat: Tree, expr: Tree, checkMode: GenCheckMode)(implicit @constructorOnly src: SourceFile) extends Tree
@@ -401,6 +397,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
401397
def SeqLiteral(elems: List[Tree], elemtpt: Tree)(implicit src: SourceFile): SeqLiteral = new SeqLiteral(elems, elemtpt)
402398
def JavaSeqLiteral(elems: List[Tree], elemtpt: Tree)(implicit src: SourceFile): JavaSeqLiteral = new JavaSeqLiteral(elems, elemtpt)
403399
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)
401+
def Splice(expr: Tree)(implicit src: SourceFile): Splice = new Splice(expr)
404402
def TypeTree()(implicit src: SourceFile): TypeTree = new TypeTree()
405403
def InferredTypeTree()(implicit src: SourceFile): TypeTree = new InferredTypeTree()
406404
def SingletonTypeTree(ref: Tree)(implicit src: SourceFile): SingletonTypeTree = new SingletonTypeTree(ref)
@@ -622,14 +620,6 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
622620
case tree: Throw if expr eq tree.expr => tree
623621
case _ => finalize(tree, untpd.Throw(expr)(tree.source))
624622
}
625-
def Quote(tree: Tree)(quoted: Tree)(using Context): Tree = tree match {
626-
case tree: Quote if quoted eq tree.quoted => tree
627-
case _ => finalize(tree, untpd.Quote(quoted)(tree.source))
628-
}
629-
def Splice(tree: Tree)(expr: Tree)(using Context): Tree = tree match {
630-
case tree: Splice if expr eq tree.expr => tree
631-
case _ => finalize(tree, untpd.Splice(expr)(tree.source))
632-
}
633623
def ForYield(tree: Tree)(enums: List[Tree], expr: Tree)(using Context): TermTree = tree match {
634624
case tree: ForYield if (enums eq tree.enums) && (expr eq tree.expr) => tree
635625
case _ => finalize(tree, untpd.ForYield(enums, expr)(tree.source))
@@ -711,10 +701,6 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
711701
cpy.Tuple(tree)(transform(trees))
712702
case Throw(expr) =>
713703
cpy.Throw(tree)(transform(expr))
714-
case Quote(t) =>
715-
cpy.Quote(tree)(transform(t))
716-
case Splice(expr) =>
717-
cpy.Splice(tree)(transform(expr))
718704
case ForYield(enums, expr) =>
719705
cpy.ForYield(tree)(transform(enums), transform(expr))
720706
case ForDo(enums, body) =>
@@ -772,10 +758,6 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
772758
this(x, trees)
773759
case Throw(expr) =>
774760
this(x, expr)
775-
case Quote(t) =>
776-
this(x, t)
777-
case Splice(expr) =>
778-
this(x, expr)
779761
case ForYield(enums, expr) =>
780762
this(this(x, enums), expr)
781763
case ForDo(enums, body) =>

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -665,6 +665,26 @@ class TreePickler(pickler: TastyPickler) {
665665
pickleTree(hi)
666666
pickleTree(alias)
667667
}
668+
case tree @ Quote(body) =>
669+
// TODO: Add QUOTE tag to TASTy
670+
assert(body.isTerm,
671+
"""Quote with type should not be pickled.
672+
|Quote with type should only exists after staging phase at staging level 0.""".stripMargin)
673+
pickleTree(
674+
// scala.quoted.runtime.Expr.quoted[<tree.bodyType>](<body>)
675+
ref(defn.QuotedRuntime_exprQuote)
676+
.appliedToType(tree.bodyType)
677+
.appliedTo(body)
678+
.withSpan(tree.span)
679+
)
680+
case Splice(expr) =>
681+
pickleTree( // TODO: Add SPLICE tag to TASTy
682+
// scala.quoted.runtime.Expr.splice[<tree.tpe>](<expr>)
683+
ref(defn.QuotedRuntime_exprSplice)
684+
.appliedToType(tree.tpe)
685+
.appliedTo(expr)
686+
.withSpan(tree.span)
687+
)
668688
case Hole(_, idx, args, _, tpt) =>
669689
writeByte(HOLE)
670690
withLength {

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1267,6 +1267,21 @@ class TreeUnpickler(reader: TastyReader,
12671267
res.withAttachment(SuppressedApplyToNone, ())
12681268
else res
12691269

1270+
def quotedExpr(fn: Tree, args: List[Tree]): Tree =
1271+
val TypeApply(_, targs) = fn: @unchecked
1272+
untpd.Quote(args.head).withBodyType(targs.head.tpe)
1273+
1274+
def splicedExpr(fn: Tree, args: List[Tree]): Tree =
1275+
val TypeApply(_, targs) = fn: @unchecked
1276+
Splice(args.head, targs.head.tpe)
1277+
1278+
def nestedSpliceExpr(fn: Tree, args: List[Tree]): Tree =
1279+
fn match
1280+
case Apply(TypeApply(_, targs), _ :: Nil) => // nestedSplice[T](quotes)(expr)
1281+
Splice(args.head, targs.head.tpe)
1282+
case _ => // nestedSplice[T](quotes)
1283+
tpd.Apply(fn, args)
1284+
12701285
def simplifyLub(tree: Tree): Tree =
12711286
tree.overwriteType(tree.tpe.simplified)
12721287
tree
@@ -1283,6 +1298,9 @@ class TreeUnpickler(reader: TastyReader,
12831298
val fn = readTree()
12841299
val args = until(end)(readTree())
12851300
if fn.symbol.isConstructor then constructorApply(fn, args)
1301+
else if fn.symbol == defn.QuotedRuntime_exprQuote then quotedExpr(fn, args)
1302+
else if fn.symbol == defn.QuotedRuntime_exprSplice then splicedExpr(fn, args)
1303+
else if fn.symbol == defn.QuotedRuntime_exprNestedSplice then nestedSpliceExpr(fn, args)
12861304
else tpd.Apply(fn, args)
12871305
case TYPEAPPLY =>
12881306
tpd.TypeApply(readTree(), until(end)(readTpt()))

0 commit comments

Comments
 (0)