@@ -8,7 +8,8 @@ import ast.Trees._
88import MegaPhase .MiniPhase
99import scala .collection .mutable
1010
11- /** Translates quoted terms and types to reify method calls.
11+ /** Translates quoted terms and types to `unpickle` method calls.
12+ * Checks that the phase consistency principle (PCP) holds.
1213 */
1314class ReifyQuotes extends MacroTransform {
1415 import ast .tpd ._
@@ -33,39 +34,108 @@ class ReifyQuotes extends MacroTransform {
3334
3435 private class Reifier extends Transformer {
3536
37+ /** The current staging level */
38+ private var currentLevel = 0
39+
40+ /** The splices encountered so far, indexed by staging level */
41+ private val splicesAtLevel = mutable.ArrayBuffer (new mutable.ListBuffer [Tree ])
42+
43+ // Invariant: -1 <= currentLevel <= splicesAtLevel.length
44+
45+ /** A map from locally defined symbol's to the staging levels of their definitions */
46+ private val levelOf = new mutable.HashMap [Symbol , Int ]
47+
48+ /** A stack of entered symbol's, to be unwound after block exit */
49+ private var enteredSyms : List [Symbol ] = Nil
50+
51+ /** Enter staging level of symbol defined by `tree`, if applicable. */
52+ def markDef (tree : Tree )(implicit ctx : Context ) = tree match {
53+ case tree : MemberDef if ! levelOf.contains(tree.symbol) =>
54+ levelOf(tree.symbol) = currentLevel
55+ enteredSyms = tree.symbol :: enteredSyms
56+ case _ =>
57+ }
58+
59+ /** If reference is to a locally defined symbol, check that its staging level
60+ * matches the current level.
61+ */
62+ def checkLevel (tree : Tree )(implicit ctx : Context ): Unit = {
63+
64+ def check (sym : Symbol , show : Symbol => String ): Unit =
65+ if (levelOf.getOrElse(sym, currentLevel) != currentLevel)
66+ ctx.error(em """ access to ${show(sym)} from wrong staging level:
67+ | - the definition is at level ${levelOf(sym)},
68+ | - but the access is at level $currentLevel. """ , tree.pos)
69+
70+ def showThis (sym : Symbol ) = i " ${sym.name}.this "
71+
72+ val sym = tree.symbol
73+ if (sym.exists)
74+ if (tree.isInstanceOf [This ]) check(sym, showThis)
75+ else if (sym.owner.isType) check(sym.owner, showThis)
76+ else check(sym, _.show)
77+ }
78+
3679 /** Turn `body` of quote into a call of `scala.meta.Unpickler.unpickleType` or
3780 * `scala.meta.Unpickler.unpickleExpr` depending onwhether `isType` is true or not.
3881 * The arguments to the method are:
3982 *
4083 * - the serialized `body`, as returned from `pickleTree`
4184 * - all splices found in `body`
4285 */
43- private def reifyCall (body : Tree , isType : Boolean )(implicit ctx : Context ) = {
44-
45- object collectSplices extends TreeAccumulator [mutable.ListBuffer [Tree ]] {
46- override def apply (splices : mutable.ListBuffer [Tree ], tree : Tree )(implicit ctx : Context ) = tree match {
47- case tree @ Select (qual, _)
48- if tree.symbol == defn.MetaExpr_~ || tree.symbol == defn.MetaType_~ =>
49- splices += transform(qual)
50- case _ =>
51- foldOver(splices, tree)
52- }
53- }
54- val splices = collectSplices(new mutable.ListBuffer [Tree ], body).toList
55- val reified = pickleTree(body, isType)
56-
86+ private def reifyCall (body : Tree , isType : Boolean )(implicit ctx : Context ) =
5787 ref(if (isType) defn.Unpickler_unpickleType else defn.Unpickler_unpickleExpr )
5888 .appliedToType(if (isType) body.tpe else body.tpe.widen)
5989 .appliedTo(
60- Literal (Constant (reified)),
61- SeqLiteral (splices, TypeTree (defn.MetaQuotedType )))
90+ Literal (Constant (pickleTree(body, isType))),
91+ SeqLiteral (splicesAtLevel(currentLevel).toList, TypeTree (defn.MetaQuotedType )))
92+
93+ /** Perform operation `op` in quoted context */
94+ private def inQuote (op : => Tree )(implicit ctx : Context ) = {
95+ currentLevel += 1
96+ if (currentLevel == splicesAtLevel.length) splicesAtLevel += null
97+ val savedSplices = splicesAtLevel(currentLevel)
98+ splicesAtLevel(currentLevel) = new mutable.ListBuffer [Tree ]
99+ try op
100+ finally {
101+ splicesAtLevel(currentLevel) = savedSplices
102+ currentLevel -= 1
103+ }
62104 }
63105
64106 override def transform (tree : Tree )(implicit ctx : Context ): Tree = tree match {
65107 case Apply (fn, arg :: Nil ) if fn.symbol == defn.quoteMethod =>
66- reifyCall(arg, isType = false )
108+ inQuote( reifyCall(transform( arg) , isType = false ) )
67109 case TypeApply (fn, arg :: Nil ) if fn.symbol == defn.typeQuoteMethod =>
68- reifyCall(arg, isType = true )
110+ inQuote(reifyCall(transform(arg), isType = true ))
111+ case Select (body, name)
112+ if tree.symbol == defn.MetaExpr_~ || tree.symbol == defn.MetaType_~ =>
113+ currentLevel -= 1
114+ val body1 = try transform(body) finally currentLevel += 1
115+ if (currentLevel > 0 ) {
116+ splicesAtLevel(currentLevel) += body1
117+ tree
118+ }
119+ else {
120+ if (currentLevel < 0 )
121+ ctx.error(i " splice ~ not allowed under toplevel splice " , tree.pos)
122+ cpy.Select (tree)(body1, name)
123+ }
124+ case (_ : Ident ) | (_ : This ) =>
125+ checkLevel(tree)
126+ super .transform(tree)
127+ case _ : MemberDef =>
128+ markDef(tree)
129+ super .transform(tree)
130+ case Block (stats, _) =>
131+ val last = enteredSyms
132+ stats.foreach(markDef)
133+ try super .transform(tree)
134+ finally
135+ while (enteredSyms ne last) {
136+ levelOf -= enteredSyms.head
137+ enteredSyms = enteredSyms.tail
138+ }
69139 case _ =>
70140 super .transform(tree)
71141 }
0 commit comments