Skip to content

Commit 21e4a27

Browse files
committed
Revamp of ReifyQuotes
- now supports multi-staging - and checks that PCP holds
1 parent fde8699 commit 21e4a27

File tree

1 file changed

+89
-19
lines changed

1 file changed

+89
-19
lines changed

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

Lines changed: 89 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import ast.Trees._
88
import MegaPhase.MiniPhase
99
import 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
*/
1314
class 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

Comments
 (0)