Skip to content

Commit 21a5b72

Browse files
committed
Make ReifyQuotes a macro phase
The previous attempt to make it a miniphase in the FirstTransform group turned out to be too complicated. The implementation so far did not handle type splices in Expr trees correctly, and doing so would not have been easy. We now run ReifyQuotes as a macro phase immediately after pickler. This means trees are reified exactly as they are pickled. We address the problem of slowdowns due to extra tree traversal by keeping a flag in CompilationUnit which allows us to skip the traversal altogether for compilation units that do not contain quotes.
1 parent 0f9b83c commit 21a5b72

File tree

11 files changed

+65
-49
lines changed

11 files changed

+65
-49
lines changed

compiler/src/dotty/tools/dotc/CompilationUnit.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ class CompilationUnit(val source: SourceFile) {
2121

2222
/** Pickled TASTY binaries, indexed by class. */
2323
var pickled: Map[ClassSymbol, Array[Byte]] = Map()
24+
25+
/** Will be reset to `true` if `untpdTree` contains `Quote` trees. The information
26+
* is used in phase ReifyQuotes in order to avoid traversing a quote-less tree.
27+
*/
28+
var containsQuotes: Boolean = false
2429
}
2530

2631
object CompilationUnit {

compiler/src/dotty/tools/dotc/Compiler.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,10 @@ class Compiler {
4747
List(new sbt.ExtractAPI), // Sends a representation of the API of classes to sbt via callbacks
4848
List(new Pickler), // Generate TASTY info
4949
List(new LinkAll), // Reload compilation units from TASTY for library code (if needed)
50+
List(new ReifyQuotes), // Turn quoted trees into explicit run-time data structures
5051
List(new FirstTransform, // Some transformations to put trees into a canonical form
5152
new CheckReentrant, // Internal use only: Check that compiled program has no data races involving global vars
52-
new ElimPackagePrefixes, // Eliminate syntactic references to Java packages
53-
new ReifyQuotes), // Eliminate syntactic references to Java packages
53+
new ElimPackagePrefixes), // Eliminate references to package prefixes in Select nodes
5454
List(new CheckStatic, // Check restrictions that apply to @static members
5555
new ElimRepeated, // Rewrite vararg parameters and arguments
5656
new NormalizeFlags, // Rewrite some definition flags

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,9 @@ class Definitions {
591591
def ClassTagClass(implicit ctx: Context) = ClassTagType.symbol.asClass
592592
def ClassTagModule(implicit ctx: Context) = ClassTagClass.companionModule
593593

594+
lazy val MetaQuotedType = ctx.requiredClassRef("scala.meta.Quoted")
595+
def MetaQuotedClass(implicit ctx: Context) = MetaQuotedType.symbol.asClass
596+
594597
lazy val MetaExprType = ctx.requiredClassRef("scala.meta.Expr")
595598
def MetaExprClass(implicit ctx: Context) = MetaExprType.symbol.asClass
596599

compiler/src/dotty/tools/dotc/core/Mode.scala

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,4 @@ object Mode {
9393

9494
/** We are in the IDE */
9595
val Interactive = newMode(20, "Interactive")
96-
97-
/** We are in a quoted type during the ReifyQuotes phase */
98-
val InQuotedType = newMode(21, "InQuotedType")
9996
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,8 @@ class FirstTransform extends MiniPhase with InfoTransformer { thisPhase =>
189189
ast.Trees.flatten(reorderAndComplete(trees)(ctx.withPhase(thisPhase.next)))
190190

191191
private def toTypeTree(tree: Tree)(implicit ctx: Context) =
192-
if (ctx.mode.is(Mode.InQuotedType)) tree else TypeTree(tree.tpe).withPos(tree.pos)
193-
192+
TypeTree(tree.tpe).withPos(tree.pos)
193+
194194
override def transformOther(tree: Tree)(implicit ctx: Context) = tree match {
195195
case tree: Import => EmptyTree
196196
case tree: NamedArg => transformAllDeep(tree.arg)

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

Lines changed: 42 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -12,60 +12,64 @@ import scala.collection.mutable
1212
* The mini phase needs to run in the same group as FirstTransform. So far we lack
1313
* the machinery to express this constraint in code.
1414
*/
15-
class ReifyQuotes extends MiniPhase {
15+
class ReifyQuotes extends MacroTransform {
1616
import ast.tpd._
1717

1818
override def phaseName: String = "reifyQuotes"
1919

20+
override def run(implicit ctx: Context): Unit =
21+
if (ctx.compilationUnit.containsQuotes) super.run
22+
23+
protected def newTransformer(implicit ctx: Context): Transformer = new Reifier
24+
2025
/** Serialize `tree`. Embedded splices are represented as nodes of the form
2126
*
2227
* Select(qual, sym)
2328
*
2429
* where `sym` is either `defn.MetaExpr_~` or `defn.MetaType_~`. For any splice,
25-
* the `qual` part will be of the form `Typed(EmptyTree, TypeTree(<underlying type>))`,
26-
* but that knowledge is not needed to uniquely identify a splice node.
30+
* the `qual` part should not be pickled, since it will be added separately later
31+
* as a splice.
2732
*/
2833
def pickleTree(tree: Tree, isType: Boolean)(implicit ctx: Context): String =
2934
tree.show // TODO: replace with TASTY
3035

31-
private def reifyCall(body: Tree, isType: Boolean)(implicit ctx: Context) = {
36+
private class Reifier extends Transformer {
37+
38+
/** Turn `body` of quote into a call of `scala.meta.Unpickler.unpickleType` or
39+
* `scala.meta.Unpickler.unpickleExpr` depending onwhether `isType` is true or not.
40+
* The arguments to the method are:
41+
*
42+
* - the serialized `body`, as returned from `pickleTree`
43+
* - all splices found in `body`
44+
*/
45+
private def reifyCall(body: Tree, isType: Boolean)(implicit ctx: Context) = {
3246

33-
object liftSplices extends TreeMap {
34-
val splices = new mutable.ListBuffer[Tree]
35-
override def transform(tree: Tree)(implicit ctx: Context) = tree match {
36-
case tree @ Select(qual, name)
37-
if tree.symbol == defn.MetaExpr_~ || tree.symbol == defn.MetaType_~ =>
38-
splices += {
39-
if (isType) // transform splice again because embedded type trees were not rewritten before
40-
transformAllDeep(qual)(ctx.retractMode(Mode.InQuotedType))
41-
else qual
42-
}
43-
val placeHolder = Typed(EmptyTree, TypeTree(qual.tpe.widen))
44-
cpy.Select(tree)(placeHolder, name)
45-
case _ =>
46-
super.transform(tree)
47+
object collectSplices extends TreeAccumulator[mutable.ListBuffer[Tree]] {
48+
override def apply(splices: mutable.ListBuffer[Tree], tree: Tree)(implicit ctx: Context) = tree match {
49+
case tree @ Select(qual, _)
50+
if tree.symbol == defn.MetaExpr_~ || tree.symbol == defn.MetaType_~ =>
51+
splices += transform(qual)
52+
case _ =>
53+
foldOver(splices, tree)
54+
}
4755
}
48-
}
56+
val splices = collectSplices(new mutable.ListBuffer[Tree], body).toList
57+
val reified = pickleTree(body, isType)
4958

50-
val reified = pickleTree(liftSplices.transform(body), isType)
51-
val splices = liftSplices.splices.toList
52-
val spliceType = if (isType) defn.MetaTypeType else defn.MetaExprType
59+
ref(if (isType) defn.Unpickler_unpickleType else defn.Unpickler_unpickleExpr)
60+
.appliedToType(if (isType) body.tpe else body.tpe.widen)
61+
.appliedTo(
62+
Literal(Constant(reified)),
63+
SeqLiteral(splices, TypeTree(defn.MetaQuotedType)))
64+
}
5365

54-
ref(if (isType) defn.Unpickler_unpickleType else defn.Unpickler_unpickleExpr)
55-
.appliedToType(if (isType) body.tpe else body.tpe.widen)
56-
.appliedTo(
57-
Literal(Constant(reified)),
58-
SeqLiteral(splices, TypeTree(spliceType.appliedTo(TypeBounds.empty))))
66+
override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match {
67+
case Apply(fn, arg :: Nil) if fn.symbol == defn.quoteMethod =>
68+
reifyCall(arg, isType = false)
69+
case TypeApply(fn, arg :: Nil) if fn.symbol == defn.typeQuoteMethod =>
70+
reifyCall(arg, isType = true)
71+
case _ =>
72+
super.transform(tree)
73+
}
5974
}
60-
61-
override def transformApply(tree: Apply)(implicit ctx: Context): Tree =
62-
if (tree.fun.symbol == defn.quoteMethod) reifyCall(tree.args.head, isType = false)
63-
else tree
64-
65-
override def prepareForTypeApply(tree: TypeApply)(implicit ctx: Context): Context =
66-
if (tree.symbol == defn.typeQuoteMethod) ctx.addMode(Mode.InQuotedType) else ctx
67-
68-
override def transformTypeApply(tree: TypeApply)(implicit ctx: Context): Tree =
69-
if (tree.fun.symbol == defn.typeQuoteMethod) reifyCall(tree.args.head, isType = true)
70-
else tree
7175
}

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1073,6 +1073,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
10731073
}
10741074

10751075
def typedQuote(tree: untpd.Quote, pt: Type)(implicit ctx: Context): Tree = track("typedQuote") {
1076+
ctx.compilationUnit.containsQuotes = true
10761077
val untpd.Quote(body) = tree
10771078
val isType = body.isType
10781079
val resultClass = if (isType) defn.MetaTypeClass else defn.MetaExprClass

library/src/scala/meta/Expr.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
package scala.meta
22

3-
class Expr[T] {
3+
class Expr[T] extends Quoted {
44
def unary_~ : T = ???
55
}

library/src/scala/meta/Quoted.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package scala.meta
2+
3+
/** Common superclass of Expr and Type */
4+
class Quoted
5+
6+

library/src/scala/meta/Type.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
package scala.meta
22

3-
class Type[T] {
3+
class Type[T] extends Quoted {
44
type unary_~ = T
55
}

library/src/scala/meta/Unpickler.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ object Unpickler {
1111
/** Unpickle `repr` which represents a pickled `Expr` tree,
1212
* replacing splice nodes with `args`
1313
*/
14-
def unpickleExpr[T](repr: Pickled, args: Seq[Expr[_]]): Expr[T] = ???
14+
def unpickleExpr[T](repr: Pickled, args: Seq[Quoted]): Expr[T] = ???
1515

1616
/** Unpickle `repr` which represents a pickled `Type` tree,
1717
* replacing splice nodes with `args`
1818
*/
19-
def unpickleType[T](repr: Pickled, args: Seq[Type[_]]): Type[T] = ???
19+
def unpickleType[T](repr: Pickled, args: Seq[Quoted]): Type[T] = ???
2020
}

0 commit comments

Comments
 (0)