@@ -5,14 +5,13 @@ import core._
5
5
import Decorators ._ , Flags ._ , Types ._ , Contexts ._ , Symbols ._ , Constants ._
6
6
import Flags ._
7
7
import ast .Trees ._
8
- import ast .TreeTypeMap
8
+ import ast .{ TreeTypeMap , untpd }
9
9
import util .Positions ._
10
10
import StdNames ._
11
- import ast .untpd
12
11
import tasty .TreePickler .Hole
13
12
import MegaPhase .MiniPhase
14
13
import SymUtils ._
15
- import NameKinds .OuterSelectName
14
+ import NameKinds ._
16
15
import typer .Implicits .SearchFailureType
17
16
18
17
import scala .collection .mutable
@@ -22,6 +21,40 @@ import dotty.tools.dotc.core.quoted._
22
21
23
22
/** Translates quoted terms and types to `unpickle` method calls.
24
23
* 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_~ ...}`.
25
58
*/
26
59
class ReifyQuotes extends MacroTransformWithImplicits {
27
60
import ast .tpd ._
@@ -37,38 +70,8 @@ class ReifyQuotes extends MacroTransformWithImplicits {
37
70
private class LevelInfo {
38
71
/** A map from locally defined symbols to the staging levels of their definitions */
39
72
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
- }
60
73
}
61
74
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
-
72
75
/** The main transformer class
73
76
* @param inQuote we are within a `'(...)` context that is not shadowed by a nested `~(...)`
74
77
* @param outer the next outer reifier, null is this is the topmost transformer
@@ -94,19 +97,22 @@ class ReifyQuotes extends MacroTransformWithImplicits {
94
97
*/
95
98
val importedTags = new mutable.LinkedHashMap [TypeRef , Tree ]()
96
99
100
+ /** A stack of entered symbols, to be unwound after scope exit */
101
+ var enteredSyms : List [Symbol ] = Nil
102
+
97
103
/** Assuming importedTags = `Type1 -> tag1, ..., TypeN -> tagN`, the expression
98
104
*
99
105
* { type <Type1> = <tag1>.unary_~
100
106
* ...
101
- * type <TypeN> = <tagN>.unary. ~
107
+ * type <TypeN> = <tagN>.unary_ ~
102
108
* <expr>
103
109
* }
104
110
*
105
111
* references to `TypeI` in `expr` are rewired to point to the locally
106
112
* defined versions. As a side effect, prepend the expressions `tag1, ..., `tagN`
107
113
* as splices to `embedded`.
108
114
*/
109
- def addTags (expr : Tree )(implicit ctx : Context ): Tree =
115
+ private def addTags (expr : Tree )(implicit ctx : Context ): Tree =
110
116
if (importedTags.isEmpty) expr
111
117
else {
112
118
val itags = importedTags.toList
@@ -151,7 +157,7 @@ class ReifyQuotes extends MacroTransformWithImplicits {
151
157
}
152
158
153
159
/** 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 =
155
161
ctx.error(i " splice outside quotes " , pos)
156
162
157
163
/** Try to heal phase-inconsistent reference to type `T` using a local type definition.
@@ -162,7 +168,6 @@ class ReifyQuotes extends MacroTransformWithImplicits {
162
168
def tryHeal (tp : Type , pos : Position )(implicit ctx : Context ): Option [String ] = tp match {
163
169
case tp : TypeRef =>
164
170
if (level == 0 ) {
165
- assert(ctx.owner.is(Macro ))
166
171
None
167
172
} else {
168
173
val reqType = defn.QuotedTypeType .appliedTo(tp)
@@ -258,46 +263,135 @@ class ReifyQuotes extends MacroTransformWithImplicits {
258
263
* `scala.quoted.Unpickler.unpickleExpr` that matches `tpe` with
259
264
* core and splices as arguments.
260
265
*/
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
+ }
265
276
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(
275
282
liftList(PickledQuotes .pickleQuote(body1).map(x => Literal (Constant (x))), defn.StringType ),
276
- liftList(splices, defn.AnyType ))
283
+ liftList(splices, defn.AnyType )).withPos(quote.pos)
277
284
}
278
- }.withPos(quote.pos)
285
+ }
279
286
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
281
288
* and make a hole from these parts. Otherwise issue an error, unless we
282
289
* are in the body of an inline method.
283
290
*/
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)
288
297
}
289
- else {
298
+ else if ( ! inQuote && level == 0 ) {
290
299
spliceOutsideQuotes(splice.pos)
291
300
splice
292
301
}
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
+ }
294
388
295
389
/** Transform `tree` and return the resulting tree and all `embedded` quotes
296
390
* or splices as a pair, after performing the `addTags` transform.
297
391
*/
298
392
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)
301
395
}
302
396
303
397
/** Register `body` as an `embedded` quote or splice
@@ -321,8 +415,10 @@ class ReifyQuotes extends MacroTransformWithImplicits {
321
415
tree match {
322
416
case Quoted (quotedTree) =>
323
417
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))
326
422
case Block (stats, _) =>
327
423
val last = enteredSyms
328
424
stats.foreach(markDef)
@@ -347,7 +443,7 @@ class ReifyQuotes extends MacroTransformWithImplicits {
347
443
tree
348
444
case tree : DefDef if tree.symbol.is(Macro ) && level == 0 =>
349
445
markDef(tree)
350
- val tree1 = nested(isQuote = true ).transform(tree)
446
+ nested(isQuote = true ).transform(tree)
351
447
// check macro code as it if appeared in a quoted context
352
448
cpy.DefDef (tree)(rhs = EmptyTree )
353
449
case _ =>
@@ -356,14 +452,19 @@ class ReifyQuotes extends MacroTransformWithImplicits {
356
452
}
357
453
}
358
454
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
+
359
461
/** InlineSplice is used to detect cases where the expansion
360
462
* consists of a (possibly multiple & nested) block or a sole expression.
361
463
*/
362
464
object InlineSplice {
363
465
def unapply (tree : Tree )(implicit ctx : Context ): Option [Select ] = {
364
466
tree match {
365
- case expansion : Select if expansion.symbol.isSplice =>
366
- Some (expansion)
467
+ case expansion : Select if expansion.symbol.isSplice => Some (expansion)
367
468
case Block (List (stat), Literal (Constant (()))) => unapply(stat)
368
469
case Block (Nil , expr) => unapply(expr)
369
470
case _ => None
0 commit comments