Skip to content

Commit 2e52a0c

Browse files
committed
Fix #4044: Change quote pickled representation
1 parent 346956e commit 2e52a0c

27 files changed

+449
-74
lines changed

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

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1132,19 +1132,14 @@ class TreeUnpickler(reader: TastyReader,
11321132
val idx = readNat()
11331133
val args = until(end)(readTerm())
11341134
val splice = splices(idx)
1135-
1135+
val reifiedArgs = args.map(arg => if (arg.isTerm) new TreeExpr(arg) else new TreeType(arg))
11361136
if (isType) {
1137-
val quotedType =
1138-
if (args.isEmpty) splice.asInstanceOf[quoted.Type[_]]
1139-
else splice.asInstanceOf[Seq[Any] => quoted.Type[_]](args.map(tree => new TreeType(tree)))
1137+
val quotedType = splice.asInstanceOf[Seq[Any] => quoted.Type[_]](reifiedArgs)
11401138
PickledQuotes.quotedTypeToTree(quotedType)
11411139
} else {
1142-
val quotedExpr =
1143-
if (args.isEmpty) splice.asInstanceOf[quoted.Expr[_]]
1144-
else splice.asInstanceOf[Seq[Any] => quoted.Expr[_]](args.map(tree => new TreeExpr(tree)))
1140+
val quotedExpr = splice.asInstanceOf[Seq[Any] => quoted.Expr[_]](reifiedArgs)
11451141
PickledQuotes.quotedExprToTree(quotedExpr)
11461142
}
1147-
11481143
}
11491144

11501145
// ------ Setting positions ------------------------------------------------

compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala

Lines changed: 167 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,13 @@ import core._
55
import Decorators._, Flags._, Types._, Contexts._, Symbols._, Constants._
66
import Flags._
77
import ast.Trees._
8-
import ast.TreeTypeMap
8+
import ast.{TreeTypeMap, untpd}
99
import util.Positions._
1010
import StdNames._
11-
import ast.untpd
1211
import tasty.TreePickler.Hole
1312
import MegaPhase.MiniPhase
1413
import SymUtils._
15-
import NameKinds.OuterSelectName
14+
import NameKinds._
1615
import typer.Implicits.SearchFailureType
1716

1817
import scala.collection.mutable
@@ -22,6 +21,40 @@ import dotty.tools.dotc.core.quoted._
2221

2322
/** Translates quoted terms and types to `unpickle` method calls.
2423
* Checks that the phase consistency principle (PCP) holds.
24+
*
25+
*
26+
* Transforms top level quote
27+
* ```
28+
* '{ ...
29+
* val x1 = ???
30+
* val x2 = ???
31+
* ...
32+
* ~{ ... '{ ... x1 ... x2 ...} ... }
33+
* ...
34+
* }
35+
* ```
36+
* to
37+
* ```
38+
* unpickle(
39+
* [[ // PICKLED TASTY
40+
* ...
41+
* val x1 = ???
42+
* val x2 = ???
43+
* ...
44+
* Hole(0 | x1, x2)
45+
* ...
46+
* ]],
47+
* List(
48+
* (args: Seq[Any]) => {
49+
* val x1$1 = args(0).asInstanceOf[Expr[T]]
50+
* val x2$1 = args(1).asInstanceOf[Expr[T]] // can be asInstanceOf[Type[T]]
51+
* ...
52+
* { ... '{ ... x1$1.unary_~ ... x2$1.unary_~ ...} ... }
53+
* }
54+
* )
55+
* )
56+
* ```
57+
* and then performs the same transformation on `'{ ... x1$1.unary_~ ... x2$1.unary_~ ...}`.
2558
*/
2659
class ReifyQuotes extends MacroTransformWithImplicits {
2760
import ast.tpd._
@@ -37,38 +70,8 @@ class ReifyQuotes extends MacroTransformWithImplicits {
3770
private class LevelInfo {
3871
/** A map from locally defined symbols to the staging levels of their definitions */
3972
val levelOf = new mutable.HashMap[Symbol, Int]
40-
41-
/** A stack of entered symbols, to be unwound after scope exit */
42-
var enteredSyms: List[Symbol] = Nil
43-
}
44-
45-
/** Requiring that `paramRefs` consists of a single reference `seq` to a Seq[Any],
46-
* a tree map that replaces each hole with index `n` with `seq(n)`, applied
47-
* to any arguments in the hole.
48-
*/
49-
private def replaceHoles(paramRefs: List[Tree]) = new TreeMap {
50-
val seq :: Nil = paramRefs
51-
override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match {
52-
case Hole(n, args) =>
53-
val arg =
54-
seq.select(nme.apply).appliedTo(Literal(Constant(n))).ensureConforms(tree.tpe)
55-
if (args.isEmpty) arg
56-
else arg.select(nme.apply).appliedTo(SeqLiteral(args, TypeTree(defn.AnyType)))
57-
case _ =>
58-
super.transform(tree)
59-
}
6073
}
6174

62-
/** If `tree` has holes, convert it to a function taking a `Seq` of elements as arguments
63-
* where each hole is replaced by the corresponding sequence element.
64-
*/
65-
private def elimHoles(tree: Tree)(implicit ctx: Context): Tree =
66-
if (tree.existsSubTree(_.isInstanceOf[Hole]))
67-
Lambda(
68-
MethodType(defn.SeqType.appliedTo(defn.AnyType) :: Nil, tree.tpe),
69-
replaceHoles(_).transform(tree))
70-
else tree
71-
7275
/** The main transformer class
7376
* @param inQuote we are within a `'(...)` context that is not shadowed by a nested `~(...)`
7477
* @param outer the next outer reifier, null is this is the topmost transformer
@@ -94,19 +97,22 @@ class ReifyQuotes extends MacroTransformWithImplicits {
9497
*/
9598
val importedTags = new mutable.LinkedHashMap[TypeRef, Tree]()
9699

100+
/** A stack of entered symbols, to be unwound after scope exit */
101+
var enteredSyms: List[Symbol] = Nil
102+
97103
/** Assuming importedTags = `Type1 -> tag1, ..., TypeN -> tagN`, the expression
98104
*
99105
* { type <Type1> = <tag1>.unary_~
100106
* ...
101-
* type <TypeN> = <tagN>.unary.~
107+
* type <TypeN> = <tagN>.unary_~
102108
* <expr>
103109
* }
104110
*
105111
* references to `TypeI` in `expr` are rewired to point to the locally
106112
* defined versions. As a side effect, prepend the expressions `tag1, ..., `tagN`
107113
* as splices to `embedded`.
108114
*/
109-
def addTags(expr: Tree)(implicit ctx: Context): Tree =
115+
private def addTags(expr: Tree)(implicit ctx: Context): Tree =
110116
if (importedTags.isEmpty) expr
111117
else {
112118
val itags = importedTags.toList
@@ -151,7 +157,7 @@ class ReifyQuotes extends MacroTransformWithImplicits {
151157
}
152158

153159
/** Issue a "splice outside quote" error unless we ar in the body of an inline method */
154-
def spliceOutsideQuotes(pos: Position)(implicit ctx: Context) =
160+
def spliceOutsideQuotes(pos: Position)(implicit ctx: Context): Unit =
155161
ctx.error(i"splice outside quotes", pos)
156162

157163
/** Try to heal phase-inconsistent reference to type `T` using a local type definition.
@@ -162,7 +168,6 @@ class ReifyQuotes extends MacroTransformWithImplicits {
162168
def tryHeal(tp: Type, pos: Position)(implicit ctx: Context): Option[String] = tp match {
163169
case tp: TypeRef =>
164170
if (level == 0) {
165-
assert(ctx.owner.is(Macro))
166171
None
167172
} else {
168173
val reqType = defn.QuotedTypeType.appliedTo(tp)
@@ -258,46 +263,135 @@ class ReifyQuotes extends MacroTransformWithImplicits {
258263
* `scala.quoted.Unpickler.unpickleExpr` that matches `tpe` with
259264
* core and splices as arguments.
260265
*/
261-
private def quotation(body: Tree, quote: Tree)(implicit ctx: Context) = {
262-
val (body1, splices) = nested(isQuote = true).split(body)
263-
if (inSplice)
264-
makeHole(body1, splices, quote.tpe)
266+
private def quotation(body: Tree, quote: Tree)(implicit ctx: Context): Tree = {
267+
val isType = quote.symbol eq defn.typeQuoteMethod
268+
if (level > 0) {
269+
val reifier = nested(isQuote = true)
270+
val body1 = reifier.transform(body)
271+
embedded ++= reifier.embedded
272+
// Keep quotes in quotes as trees to reduce pickled size and have a Expr.show without pickled quotes embedded
273+
if (isType) ref(defn.typeQuoteMethod).appliedToType(body1.tpe.widen)
274+
else ref(defn.quoteMethod).appliedToType(body1.tpe.widen).appliedTo(body1)
275+
}
265276
else {
266-
def liftList(list: List[Tree], tpe: Type): Tree = {
267-
list.foldRight[Tree](ref(defn.NilModule)) { (x, acc) =>
268-
acc.select("::".toTermName).appliedToType(tpe).appliedTo(x)
269-
}
270-
}
271-
val isType = quote.tpe.isRef(defn.QuotedTypeClass)
272-
ref(if (isType) defn.Unpickler_unpickleType else defn.Unpickler_unpickleExpr)
273-
.appliedToType(if (isType) body1.tpe else body1.tpe.widen)
274-
.appliedTo(
277+
val (body1, splices) = nested(isQuote = true).split(body)
278+
val meth =
279+
if (isType) ref(defn.Unpickler_unpickleType).appliedToType(body1.tpe)
280+
else ref(defn.Unpickler_unpickleExpr).appliedToType(body1.tpe.widen)
281+
meth.appliedTo(
275282
liftList(PickledQuotes.pickleQuote(body1).map(x => Literal(Constant(x))), defn.StringType),
276-
liftList(splices, defn.AnyType))
283+
liftList(splices, defn.AnyType)).withPos(quote.pos)
277284
}
278-
}.withPos(quote.pos)
285+
}
279286

280-
/** If inside a quote, split `body` into a core and a list of embedded quotes
287+
/** If inside a quote, split the body of the splice into a core and a list of embedded quotes
281288
* and make a hole from these parts. Otherwise issue an error, unless we
282289
* are in the body of an inline method.
283290
*/
284-
private def splice(body: Tree, splice: Tree)(implicit ctx: Context): Tree = {
285-
if (inQuote) {
286-
val (body1, quotes) = nested(isQuote = false).split(body)
287-
makeHole(body1, quotes, splice.tpe)
291+
private def splice(splice: Select)(implicit ctx: Context): Tree = {
292+
if (level > 1) {
293+
val reifier = nested(isQuote = false)
294+
val body1 = reifier.transform(splice.qualifier)
295+
embedded ++= reifier.embedded
296+
body1.select(splice.name)
288297
}
289-
else {
298+
else if (!inQuote && level == 0) {
290299
spliceOutsideQuotes(splice.pos)
291300
splice
292301
}
293-
}.withPos(splice.pos)
302+
else {
303+
val (body1, quotes) = nested(isQuote = false).split(splice.qualifier)
304+
makeHole(body1, quotes, splice.tpe).withPos(splice.pos)
305+
}
306+
}
307+
308+
/** Transforms the contents of a splice nested
309+
* '{
310+
* val x = ???
311+
* val y = ???
312+
* { ... '{ ... x .. y ... } ... }.unary_~
313+
* }
314+
*
315+
* { ... '{ ... x ... y ... } ... }
316+
* will be transformed to
317+
* (args: Seq[Any]) => {
318+
* val x$1 = args(0).asInstanceOf[Expr[Any]] // or .asInstanceOf[Type[Any]]
319+
* val y$1 = args(1).asInstanceOf[Expr[Any]] // or .asInstanceOf[Type[Any]]
320+
* { ... '{ ... x$1.unary_~ ... y$1.unary_~ ... } ... }
321+
* }
322+
*
323+
* See: `lift`
324+
*
325+
* At the same time register `embedded` trees `x` and `y` to place as arguments of the hole
326+
* placed in the original code.
327+
* '{
328+
* val x = ???
329+
* val y = ???
330+
* Hole(0 | x, y)
331+
* }
332+
*/
333+
private def makeLambda(tree: Tree)(implicit ctx: Context): Tree = {
334+
def body(arg: Tree)(implicit ctx: Context): Tree = {
335+
var i = 0
336+
val lifted = new mutable.ListBuffer[Tree]
337+
lifter = (tree: RefTree) => {
338+
val argTpe =
339+
if (tree.isTerm) defn.QuotedExprType.appliedTo(tree.tpe.widen)
340+
else defn.QuotedTypeType.appliedTo(defn.AnyType)
341+
val selectArg = arg.select(nme.apply).appliedTo(Literal(Constant(i))).asInstance(argTpe)
342+
val liftedArg = SyntheticValDef(UniqueName.fresh(tree.name.toTermName).toTermName, selectArg)
343+
i += 1
344+
embedded += tree
345+
lifted += liftedArg
346+
ref(liftedArg.symbol)
347+
}
348+
val tree2 = transform(tree)
349+
lifter = null
350+
seq(lifted.result(), tree2)
351+
}
352+
val lambdaOwner = ctx.owner.ownersIterator.find(o => levelOf.getOrElse(o, level) == level).get
353+
val tpe = MethodType(defn.SeqType.appliedTo(defn.AnyType) :: Nil, tree.tpe.widen)
354+
val meth = ctx.newSymbol(lambdaOwner, UniqueName.fresh(nme.ANON_FUN), Synthetic | Method, tpe)
355+
Closure(meth, tss => body(tss.head.head)(ctx.withOwner(meth)).changeOwner(ctx.owner, meth))
356+
}
357+
358+
/** Register a reference defined in a quote but used in another quote nested in a splice.
359+
* Returns a lifted version of the reference that needs to be used in its place.
360+
* '{
361+
* val x = ???
362+
* { ... '{ ... x ... } ... }.unary_~
363+
* }
364+
* Lifting the `x` in `{ ... '{ ... x ... } ... }.unary_~` will return a `x$1.unary_~` for which the `x$1`
365+
* be created by some outer reifier.
366+
*
367+
* This transformation is only applied to definitions at staging level 1.
368+
*
369+
* See `needsLifting`
370+
*/
371+
def lift(tree: RefTree)(implicit ctx: Context): Select =
372+
if (!(level == 0 && outer.enteredSyms.contains(tree.symbol))) outer.lift(tree)
373+
else lifter(tree).select(if (tree.isTerm) nme.UNARY_~ else tpnme.UNARY_~)
374+
private[this] var lifter: RefTree => Tree = null
375+
376+
/** Returns true if this tree will be lifted by `makeLambda` */
377+
private def needsLifting(tree: RefTree)(implicit ctx: Context): Boolean = {
378+
def isInLiftedTree(reifier: Reifier): Boolean =
379+
if (reifier.level == 0) true
380+
else if (reifier.level == 1 && reifier.enteredSyms.contains(tree.symbol)) false
381+
else isInLiftedTree(reifier.outer)
382+
level == 1 &&
383+
!tree.symbol.is(Inline) &&
384+
levelOf.get(tree.symbol).contains(1) &&
385+
!enteredSyms.contains(tree.symbol) &&
386+
isInLiftedTree(outer)
387+
}
294388

295389
/** Transform `tree` and return the resulting tree and all `embedded` quotes
296390
* or splices as a pair, after performing the `addTags` transform.
297391
*/
298392
private def split(tree: Tree)(implicit ctx: Context): (Tree, List[Tree]) = {
299-
val tree1 = addTags(transform(tree))
300-
(tree1, embedded.toList.map(elimHoles))
393+
val tree1 = if (inQuote) addTags(transform(tree)) else makeLambda(tree)
394+
(tree1, embedded.toList)
301395
}
302396

303397
/** Register `body` as an `embedded` quote or splice
@@ -321,8 +415,10 @@ class ReifyQuotes extends MacroTransformWithImplicits {
321415
tree match {
322416
case Quoted(quotedTree) =>
323417
quotation(quotedTree, tree)
324-
case Select(body, _) if tree.symbol.isSplice =>
325-
splice(body, tree)
418+
case tree: Select if tree.symbol.isSplice =>
419+
splice(tree)
420+
case tree: RefTree if needsLifting(tree) =>
421+
splice(outer.lift(tree))
326422
case Block(stats, _) =>
327423
val last = enteredSyms
328424
stats.foreach(markDef)
@@ -347,7 +443,7 @@ class ReifyQuotes extends MacroTransformWithImplicits {
347443
tree
348444
case tree: DefDef if tree.symbol.is(Macro) && level == 0 =>
349445
markDef(tree)
350-
val tree1 = nested(isQuote = true).transform(tree)
446+
nested(isQuote = true).transform(tree)
351447
// check macro code as it if appeared in a quoted context
352448
cpy.DefDef(tree)(rhs = EmptyTree)
353449
case _ =>
@@ -356,14 +452,19 @@ class ReifyQuotes extends MacroTransformWithImplicits {
356452
}
357453
}
358454

455+
private def liftList(list: List[Tree], tpe: Type)(implicit ctx: Context): Tree = {
456+
list.foldRight[Tree](ref(defn.NilModule)) { (x, acc) =>
457+
acc.select("::".toTermName).appliedToType(tpe).appliedTo(x)
458+
}
459+
}
460+
359461
/** InlineSplice is used to detect cases where the expansion
360462
* consists of a (possibly multiple & nested) block or a sole expression.
361463
*/
362464
object InlineSplice {
363465
def unapply(tree: Tree)(implicit ctx: Context): Option[Select] = {
364466
tree match {
365-
case expansion: Select if expansion.symbol.isSplice =>
366-
Some(expansion)
467+
case expansion: Select if expansion.symbol.isSplice => Some(expansion)
367468
case Block(List(stat), Literal(Constant(()))) => unapply(stat)
368469
case Block(Nil, expr) => unapply(expr)
369470
case _ => None

tests/neg/i4044a.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import scala.quoted._
2+
3+
class Test {
4+
5+
val a = '(1)
6+
'{
7+
a // error
8+
~a
9+
'(~a) // error
10+
'( '(~a) ) // error
11+
}
12+
13+
}

tests/neg/i4044b.scala

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import scala.quoted._
2+
3+
class Test {
4+
5+
'{
6+
7+
val b = '(3)
8+
9+
'{
10+
b // error
11+
~(b)
12+
~('(b)) // error
13+
'( '(~b) ) // error
14+
}
15+
16+
}
17+
18+
}

0 commit comments

Comments
 (0)