Skip to content

Commit da3bf9e

Browse files
committed
Re-architecture quote pickling
Separate the logic that creates holes in quotes from the logic that pickles the quotes. Holes are created in the `Splicer` phase and the result of the transformation can be `-Ycheck`ed. Now, the `PickleQuotes` phase only needs to extract the contents of the holes, pickle the quote and put them into a call to `unpickleExprV2`/`unpickleType`. We add `unpickleExprV2` to support some optimization in the encoding of the pickled quote. Namely we removed an unnecessary lambda from the arguments of the hole passed into the contents of the hole. By not changing `unpickleExpr` the current compiler will be able to handle the old encoding in binaries compiled with older compilers. Fixes scala#8100 Fixes scala#12440 Fixes scala#13563
1 parent 2ef89b2 commit da3bf9e

Some content is hidden

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

45 files changed

+888
-485
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ class Compiler {
5454
List(new Inlining) :: // Inline and execute macros
5555
List(new PostInlining) :: // Add mirror support for inlined code
5656
List(new Staging) :: // Check staging levels and heal staged types
57+
List(new Splicing) :: // Replace level 1 splices with holes
5758
List(new PickleQuotes) :: // Turn quoted trees into explicit run-time data structures
5859
Nil
5960

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,11 @@ class TreeTypeMap(
133133
val bind1 = tmap.transformSub(bind)
134134
val expr1 = tmap.transform(expr)
135135
cpy.Labeled(labeled)(bind1, expr1)
136-
case Hole(isTermHole, n, args) =>
137-
Hole(isTermHole, n, args.mapConserve(transform)).withSpan(tree.span).withType(mapType(tree.tpe))
136+
case tree @ Hole(_, _, args, content, tpt) =>
137+
val args1 = args.mapConserve(transform)
138+
val content1 = transform(content)
139+
val tpt1 = transform(tpt)
140+
cpy.Hole(tree)(args = args1, content = content1, tpt = tpt1)
138141
case lit @ Literal(Constant(tpe: Type)) =>
139142
cpy.Literal(lit)(Constant(mapType(tpe)))
140143
case tree1 =>

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

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,11 @@ object Trees {
503503
def forwardTo: Tree[T] = fun
504504
}
505505

506+
object GenericApply:
507+
def unapply[T >: Untyped](tree: Tree[T]): Option[(Tree[T], List[Tree[T]])] = tree match
508+
case tree: GenericApply[T] => Some((tree.fun, tree.args))
509+
case _ => None
510+
506511
/** The kind of application */
507512
enum ApplyKind:
508513
case Regular // r.f(x)
@@ -525,8 +530,6 @@ object Trees {
525530
attachmentOrElse(untpd.KindOfApply, ApplyKind.Regular)
526531
}
527532

528-
529-
530533
/** fun[args] */
531534
case class TypeApply[-T >: Untyped] private[ast] (fun: Tree[T], args: List[Tree[T]])(implicit @constructorOnly src: SourceFile)
532535
extends GenericApply[T] {
@@ -972,10 +975,16 @@ object Trees {
972975
def genericEmptyValDef[T >: Untyped]: ValDef[T] = theEmptyValDef.asInstanceOf[ValDef[T]]
973976
def genericEmptyTree[T >: Untyped]: Thicket[T] = theEmptyTree.asInstanceOf[Thicket[T]]
974977

975-
/** Tree that replaces a splice in pickled quotes.
976-
* It is only used when picking quotes (Will never be in a TASTy file).
978+
/** Tree that replaces a level 1 splices in pickled (level 0) quotes.
979+
* It is only used when picking quotes (will never be in a TASTy file).
980+
*
981+
* @param isTermHole If this hole is a term, otherwise it is a type hole.
982+
* @param idx The index of the hole in it's enclosing level 0 quote.
983+
* @param args The arguments of the splice to compute its content
984+
* @param content Lambda that computes the content of the hole. This tree is empty when in a quote pickle.
985+
* @param tpt Type of the hole
977986
*/
978-
case class Hole[-T >: Untyped](isTermHole: Boolean, idx: Int, args: List[Tree[T]])(implicit @constructorOnly src: SourceFile) extends Tree[T] {
987+
case class Hole[-T >: Untyped](isTermHole: Boolean, idx: Int, args: List[Tree[T]], content: Tree[T], tpt: Tree[T])(implicit @constructorOnly src: SourceFile) extends Tree[T] {
979988
type ThisTree[-T >: Untyped] <: Hole[T]
980989
override def isTerm: Boolean = isTermHole
981990
override def isType: Boolean = !isTermHole
@@ -1331,6 +1340,10 @@ object Trees {
13311340
case tree: Thicket if (trees eq tree.trees) => tree
13321341
case _ => finalize(tree, untpd.Thicket(trees)(sourceFile(tree)))
13331342
}
1343+
def Hole(tree: Tree)(isTerm: Boolean, idx: Int, args: List[Tree], content: Tree, tpt: Tree)(using Context): Hole = tree match {
1344+
case tree: Hole if isTerm == tree.isTerm && idx == tree.idx && args.eq(tree.args) && content.eq(tree.content) && content.eq(tree.content) => tree
1345+
case _ => finalize(tree, untpd.Hole(isTerm, idx, args, content, tpt)(sourceFile(tree)))
1346+
}
13341347

13351348
// Copier methods with default arguments; these demand that the original tree
13361349
// is of the same class as the copy. We only include trees with more than 2 elements here.
@@ -1352,6 +1365,9 @@ object Trees {
13521365
TypeDef(tree: Tree)(name, rhs)
13531366
def Template(tree: Template)(constr: DefDef = tree.constr, parents: List[Tree] = tree.parents, derived: List[untpd.Tree] = tree.derived, self: ValDef = tree.self, body: LazyTreeList = tree.unforcedBody)(using Context): Template =
13541367
Template(tree: Tree)(constr, parents, derived, self, body)
1368+
def Hole(tree: Hole)(isTerm: Boolean = tree.isTerm, idx: Int = tree.idx, args: List[Tree] = tree.args, content: Tree = tree.content, tpt: Tree = tree.tpt)(using Context): Hole =
1369+
Hole(tree: Tree)(isTerm, idx, args, content, tpt)
1370+
13551371
}
13561372

13571373
/** Hook to indicate that a transform of some subtree should be skipped */
@@ -1481,6 +1497,8 @@ object Trees {
14811497
case Thicket(trees) =>
14821498
val trees1 = transform(trees)
14831499
if (trees1 eq trees) tree else Thicket(trees1)
1500+
case tree @ Hole(_, _, args, content, tpt) =>
1501+
cpy.Hole(tree)(args = transform(args), content = transform(content), tpt = transform(tpt))
14841502
case _ =>
14851503
transformMoreCases(tree)
14861504
}
@@ -1618,8 +1636,8 @@ object Trees {
16181636
this(this(x, arg), annot)
16191637
case Thicket(ts) =>
16201638
this(x, ts)
1621-
case Hole(_, _, args) =>
1622-
this(x, args)
1639+
case Hole(_, _, args, content, tpt) =>
1640+
this(this(this(x, args), content), tpt)
16231641
case _ =>
16241642
foldMoreCases(x, tree)
16251643
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,9 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
373373
def Throw(expr: Tree)(using Context): Tree =
374374
ref(defn.throwMethod).appliedTo(expr)
375375

376+
def Hole(isTermHole: Boolean, idx: Int, args: List[Tree], content: Tree, tpt: Tree)(using Context): Hole =
377+
ta.assignType(untpd.Hole(isTermHole, idx, args, content, tpt), tpt)
378+
376379
// ------ Making references ------------------------------------------------------
377380

378381
def prefixIsElidable(tp: NamedType)(using Context): Boolean = {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
411411
def Export(expr: Tree, selectors: List[ImportSelector])(implicit src: SourceFile): Export = new Export(expr, selectors)
412412
def PackageDef(pid: RefTree, stats: List[Tree])(implicit src: SourceFile): PackageDef = new PackageDef(pid, stats)
413413
def Annotated(arg: Tree, annot: Tree)(implicit src: SourceFile): Annotated = new Annotated(arg, annot)
414+
def Hole(isTermHole: Boolean, idx: Int, args: List[Tree], content: Tree, tpt: Tree)(implicit src: SourceFile): Hole = new Hole(isTermHole, idx, args, content, tpt)
414415

415416
// ------ Additional creation methods for untyped only -----------------
416417

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -789,7 +789,7 @@ class Definitions {
789789
@tu lazy val QuotesClass: ClassSymbol = requiredClass("scala.quoted.Quotes")
790790

791791
@tu lazy val QuoteUnpicklerClass: ClassSymbol = requiredClass("scala.quoted.runtime.QuoteUnpickler")
792-
@tu lazy val QuoteUnpickler_unpickleExpr: Symbol = QuoteUnpicklerClass.requiredMethod("unpickleExpr")
792+
@tu lazy val QuoteUnpickler_unpickleExprV2: Symbol = QuoteUnpicklerClass.requiredMethod("unpickleExprV2")
793793
@tu lazy val QuoteUnpickler_unpickleType: Symbol = QuoteUnpicklerClass.requiredMethod("unpickleType")
794794

795795
@tu lazy val QuoteMatchingClass: ClassSymbol = requiredClass("scala.quoted.runtime.QuoteMatching")

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ object Phases {
201201
private var mySbtExtractDependenciesPhase: Phase = _
202202
private var myPicklerPhase: Phase = _
203203
private var myInliningPhase: Phase = _
204-
private var myPickleQuotesPhase: Phase = _
204+
private var mySplicingPhase: Phase = _
205205
private var myFirstTransformPhase: Phase = _
206206
private var myCollectNullableFieldsPhase: Phase = _
207207
private var myRefChecksPhase: Phase = _
@@ -223,7 +223,7 @@ object Phases {
223223
final def sbtExtractDependenciesPhase: Phase = mySbtExtractDependenciesPhase
224224
final def picklerPhase: Phase = myPicklerPhase
225225
final def inliningPhase: Phase = myInliningPhase
226-
final def pickleQuotesPhase: Phase = myPickleQuotesPhase
226+
final def splicingPhase: Phase = mySplicingPhase
227227
final def firstTransformPhase: Phase = myFirstTransformPhase
228228
final def collectNullableFieldsPhase: Phase = myCollectNullableFieldsPhase
229229
final def refchecksPhase: Phase = myRefChecksPhase
@@ -248,7 +248,7 @@ object Phases {
248248
mySbtExtractDependenciesPhase = phaseOfClass(classOf[sbt.ExtractDependencies])
249249
myPicklerPhase = phaseOfClass(classOf[Pickler])
250250
myInliningPhase = phaseOfClass(classOf[Inlining])
251-
myPickleQuotesPhase = phaseOfClass(classOf[PickleQuotes])
251+
mySplicingPhase = phaseOfClass(classOf[Splicing])
252252
myFirstTransformPhase = phaseOfClass(classOf[FirstTransform])
253253
myCollectNullableFieldsPhase = phaseOfClass(classOf[CollectNullableFields])
254254
myRefChecksPhase = phaseOfClass(classOf[RefChecks])
@@ -423,7 +423,7 @@ object Phases {
423423
def sbtExtractDependenciesPhase(using Context): Phase = ctx.base.sbtExtractDependenciesPhase
424424
def picklerPhase(using Context): Phase = ctx.base.picklerPhase
425425
def inliningPhase(using Context): Phase = ctx.base.inliningPhase
426-
def pickleQuotesPhase(using Context): Phase = ctx.base.pickleQuotesPhase
426+
def splicingPhase(using Context): Phase = ctx.base.splicingPhase
427427
def firstTransformPhase(using Context): Phase = ctx.base.firstTransformPhase
428428
def refchecksPhase(using Context): Phase = ctx.base.refchecksPhase
429429
def elimRepeatedPhase(using Context): Phase = ctx.base.elimRepeatedPhase

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ import scala.collection.mutable
1313

1414
object StagingContext {
1515

16-
/** A key to be used in a context property that tracks the quoteation level */
16+
/** A key to be used in a context property that tracks the quotation level */
1717
private val QuotationLevel = new Property.Key[Int]
1818

19-
/** A key to be used in a context property that tracks the quoteation stack.
20-
* Stack containing the Quotes references recieved by the surrounding quotes.
19+
/** A key to be used in a context property that tracks the quotation stack.
20+
* Stack containing the Quotes references received by the surrounding quotes.
2121
*/
2222
private val QuotesStack = new Property.Key[List[tpd.Tree]]
2323

@@ -31,7 +31,7 @@ object StagingContext {
3131
def quoteContext(using Context): Context =
3232
ctx.fresh.setProperty(QuotationLevel, level + 1)
3333

34-
/** Context with an incremented quotation level and pushes a refecence to a Quotes on the quote context stack */
34+
/** Context with an incremented quotation level and pushes a reference to a Quotes on the quote context stack */
3535
def pushQuotes(qctxRef: tpd.Tree)(using Context): Context =
3636
val old = ctx.property(QuotesStack).getOrElse(List.empty)
3737
ctx.fresh.setProperty(QuotationLevel, level + 1)
@@ -48,7 +48,7 @@ object StagingContext {
4848
ctx.property(TaggedTypes).get
4949

5050
/** Context with a decremented quotation level and pops the Some of top of the quote context stack or None if the stack is empty.
51-
* The quotation stack could be empty if we are in a top level splice or an eroneous splice directly witin a top level splice.
51+
* The quotation stack could be empty if we are in a top level splice or an erroneous splice directly within a top level splice.
5252
*/
5353
def popQuotes()(using Context): (Option[tpd.Tree], Context) =
5454
val ctx1 = ctx.fresh.setProperty(QuotationLevel, level - 1)

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -644,11 +644,11 @@ class TreePickler(pickler: TastyPickler) {
644644
pickleTree(hi)
645645
pickleTree(alias)
646646
}
647-
case Hole(_, idx, args) =>
647+
case Hole(_, idx, args, _, tpt) =>
648648
writeByte(HOLE)
649649
withLength {
650650
writeNat(idx)
651-
pickleType(tree.tpe, richTypes = true)
651+
pickleType(tpt.tpe, richTypes = true)
652652
args.foreach(pickleTree)
653653
}
654654
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1288,7 +1288,7 @@ class TreeUnpickler(reader: TastyReader,
12881288
val idx = readNat()
12891289
val tpe = readType()
12901290
val args = until(end)(readTerm())
1291-
Hole(true, idx, args).withType(tpe)
1291+
Hole(true, idx, args, EmptyTree, TypeTree(tpe)).withType(tpe)
12921292
case _ =>
12931293
readPathTerm()
12941294
}
@@ -1324,7 +1324,7 @@ class TreeUnpickler(reader: TastyReader,
13241324
val idx = readNat()
13251325
val tpe = readType()
13261326
val args = until(end)(readTerm())
1327-
Hole(false, idx, args).withType(tpe)
1327+
Hole(false, idx, args, EmptyTree, TypeTree(tpe)).withType(tpe)
13281328
case _ =>
13291329
if (isTypeTreeTag(nextByte)) readTerm()
13301330
else {

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -699,10 +699,12 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
699699
"Thicket {" ~~ toTextGlobal(trees, "\n") ~~ "}"
700700
case MacroTree(call) =>
701701
keywordStr("macro ") ~ toTextGlobal(call)
702-
case Hole(isTermHole, idx, args) =>
703-
val (prefix, postfix) = if isTermHole then ("{{{ ", " }}}") else ("[[[ ", " ]]]")
702+
case Hole(isTermHole, idx, args, content, tpt) =>
703+
val (prefix, postfix) = if isTermHole then ("{{{", "}}}") else ("[[[", "]]]")
704704
val argsText = toTextGlobal(args, ", ")
705-
prefix ~~ idx.toString ~~ "|" ~~ argsText ~~ postfix
705+
val contentText = toTextGlobal(content)
706+
val tptText = toTextGlobal(tpt)
707+
prefix ~~ idx.toString ~~ "|" ~~ tptText ~~ "|" ~~ argsText ~~ "|" ~~ contentText ~~ postfix
706708
case _ =>
707709
tree.fallbackToText(this)
708710
}

compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,12 @@ object PickledQuotes {
5252
}
5353

5454
/** Unpickle the tree contained in the TastyExpr */
55-
def unpickleTerm(pickled: String | List[String], typeHole: (Int, Seq[Any]) => scala.quoted.Type[?], termHole: (Int, Seq[Any], scala.quoted.Quotes) => scala.quoted.Expr[?])(using Context): Tree = {
55+
def unpickleTerm(pickled: String | List[String], typeHole: (Int, Seq[Any]) => scala.quoted.Type[?], termHole: (Int, Seq[Any], scala.quoted.Quotes) => scala.quoted.Expr[?], version: Int)(using Context): Tree = {
5656
val unpickled = withMode(Mode.ReadPositions)(unpickle(pickled, isType = false))
57-
val Inlined(call, Nil, expnasion) = unpickled
57+
val Inlined(call, Nil, expansion) = unpickled
5858
val inlineCtx = inlineContext(call)
59-
val expansion1 = spliceTypes(expnasion, typeHole, termHole)(using inlineCtx)
60-
val expansion2 = spliceTerms(expansion1, typeHole, termHole)(using inlineCtx)
59+
val expansion1 = spliceTypes(expansion, typeHole, termHole)(using inlineCtx)
60+
val expansion2 = spliceTerms(expansion1, typeHole, termHole, version)(using inlineCtx)
6161
cpy.Inlined(unpickled)(call, Nil, expansion2)
6262
}
6363

@@ -68,16 +68,19 @@ object PickledQuotes {
6868
}
6969

7070
/** Replace all term holes with the spliced terms */
71-
private def spliceTerms(tree: Tree, typeHole: (Int, Seq[Any]) => scala.quoted.Type[?], termHole: (Int, Seq[Any], scala.quoted.Quotes) => scala.quoted.Expr[?])(using Context): Tree = {
71+
private def spliceTerms(tree: Tree, typeHole: (Int, Seq[Any]) => scala.quoted.Type[?], termHole: (Int, Seq[Any], scala.quoted.Quotes) => scala.quoted.Expr[?], version: Int)(using Context): Tree = {
7272
val evaluateHoles = new TreeMap {
7373
override def transform(tree: tpd.Tree)(using Context): tpd.Tree = tree match {
74-
case Hole(isTerm, idx, args) =>
74+
case Hole(isTermHole, idx, args, _, _) =>
7575
inContext(SpliceScope.contextWithNewSpliceScope(tree.sourcePos)) {
7676
val reifiedArgs = args.map { arg =>
77-
if (arg.isTerm) (q: Quotes) ?=> new ExprImpl(arg, SpliceScope.getCurrent)
77+
if (arg.isTerm)
78+
version match
79+
case 1 => (q: Quotes) ?=> new ExprImpl(arg, SpliceScope.getCurrent)
80+
case 2 => new ExprImpl(arg, SpliceScope.getCurrent)
7881
else new TypeImpl(arg, SpliceScope.getCurrent)
7982
}
80-
if isTerm then
83+
if isTermHole then
8184
val quotedExpr = termHole(idx, reifiedArgs, QuotesImpl())
8285
val filled = PickledQuotes.quotedExprToTree(quotedExpr)
8386

@@ -131,11 +134,14 @@ object PickledQuotes {
131134
case tdef: TypeDef =>
132135
assert(tdef.symbol.hasAnnotation(defn.QuotedRuntime_SplicedTypeAnnot))
133136
val tree = tdef.rhs match
134-
case TypeBoundsTree(_, Hole(_, idx, args), _) =>
137+
case TypeBoundsTree(_, Hole(_, idx, args, _, _), _) => // to keep for backwards compat
135138
val quotedType = typeHole(idx, args)
136139
PickledQuotes.quotedTypeToTree(quotedType)
137-
case TypeBoundsTree(_, tpt, _) =>
140+
case TypeBoundsTree(_, tpt, _) => // to keep for backwards compat
138141
tpt
142+
case Hole(_, idx, args, _, _) =>
143+
val quotedType = typeHole(idx, args)
144+
PickledQuotes.quotedTypeToTree(quotedType)
139145
(tdef.symbol, tree.tpe)
140146
}.toMap
141147
class ReplaceSplicedTyped extends TypeMap() {

0 commit comments

Comments
 (0)