@@ -5,14 +5,13 @@ import core._
55import Decorators ._ , Flags ._ , Types ._ , Contexts ._ , Symbols ._ , Constants ._
66import Flags ._
77import ast .Trees ._
8- import ast .TreeTypeMap
8+ import ast .{ TreeTypeMap , untpd }
99import util .Positions ._
1010import StdNames ._
11- import ast .untpd
1211import tasty .TreePickler .Hole
1312import MegaPhase .MiniPhase
1413import SymUtils ._
15- import NameKinds .OuterSelectName
14+ import NameKinds ._
1615import typer .Implicits .SearchFailureType
1716
1817import 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 */
2659class 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
0 commit comments