Skip to content

Commit 9bd6589

Browse files
committed
Generate macro splicers before pickling
1 parent 64497c6 commit 9bd6589

File tree

24 files changed

+260
-191
lines changed

24 files changed

+260
-191
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ class Compiler {
4747
List(new sbt.ExtractDependencies) :: // Sends information on classes' dependencies to sbt via callbacks
4848
List(new PostTyper) :: // Additional checks and cleanups after type checking
4949
List(new sbt.ExtractAPI) :: // Sends a representation of the API of classes to sbt via callbacks
50+
List(new MacrosSplitter) :: // Applies ReifyQuotes transformation to the body of the macros
5051
Nil
5152

5253
/** Phases dealing with TASTY tree pickling and unpickling */

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -676,7 +676,7 @@ class Definitions {
676676
def Unpickler_unpickleType = ctx.requiredMethod("scala.runtime.quoted.Unpickler.unpickleType")
677677

678678
lazy val TastyTopLevelSpliceModule = ctx.requiredModule("scala.tasty.TopLevelSplice")
679-
lazy val TastyTopLevelSplice_compilationTopLevelSplice = TastyTopLevelSpliceModule.requiredMethod("tastyContext")
679+
lazy val TastyTopLevelSplice_tastyContext = TastyTopLevelSpliceModule.requiredMethod("tastyContext")
680680

681681
lazy val EqType = ctx.requiredClassRef("scala.Eq")
682682
def EqClass(implicit ctx: Context) = EqType.symbol.asClass

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ object NameKinds {
292292
val SuperArgName = new UniqueNameKind("$superArg$")
293293
val DocArtifactName = new UniqueNameKind("$doc")
294294
val UniqueInlineName = new UniqueNameKind("$i")
295+
val UniqueSpliceName = new UniqueNameKind("$splice$")
295296

296297
/** A kind of unique extension methods; Unlike other unique names, these can be
297298
* unmangled.
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
package dotty.tools.dotc
2+
package transform
3+
4+
import core._
5+
import Decorators._
6+
import Types._
7+
import Contexts._
8+
import Symbols._
9+
import Constants._
10+
import Flags._
11+
import ast.Trees._
12+
import ast.{TreeTypeMap, tpd}
13+
import tasty.TreePickler.Hole
14+
import SymUtils._
15+
import NameKinds._
16+
17+
import scala.collection.mutable
18+
import dotty.tools.dotc.core.StdNames._
19+
20+
21+
/** Translates macro implementation.
22+
* Checks that the phase consistency principle (PCP) holds on the body of a macro.
23+
*
24+
*
25+
* Transforms an inline macro definitions we assume that we have a single ~ directly as the RHS.
26+
* We will transform the definition from
27+
* ```
28+
* inline def foo[T1, ...](inline x1: X, ..., y1: Y, ....): Z = ~{ ... x1 ... '{ ... T1 ... x1 ... '(y) ... } }
29+
* ```
30+
* to
31+
* ```
32+
* inline def foo[T1, ...](inline x1: X, ..., y1: Y, ....): Z = ~foo$splice$1[T1, ...]('[T1], ..., x1, ..., '(y1), ...)
33+
*
34+
* def foo$splice$1[T1, ...](T1$1: Type[T1], ..., x1$1: X, ..., y1: Expr[Y], ....): Z =
35+
* { ... x1$1 .... '{ ... T1$1.unary_~ ... x1$1.toExpr.unary_~ ... y1$1.unary_~ ... } ... }
36+
* ```
37+
* Where `inline` parameters with type Boolean, Byte, Short, Int, Long, Float, Double, Char and String are
38+
* passed as their actual runtime value. See `isStage0Value`. Other `inline` arguments such as functions are handled
39+
* like `y1: Y`.
40+
*
41+
*
42+
* At inline site we will call reflectively the static method `foo$splice$1` with the interpreted parameters.
43+
*/
44+
class MacrosSplitter extends ReifyQuotes {
45+
import ast.tpd._
46+
47+
override def phaseName: String = "macrosSplitter"
48+
49+
override def run(implicit ctx: Context): Unit =
50+
if (ctx.compilationUnit.containsQuotesOrSplices) super.run
51+
52+
protected override def newTransformer(implicit ctx: Context): Transformer = {
53+
new Transformer {
54+
override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = tree match {
55+
case tree: PackageDef => super.transform(tree)
56+
case tree: TypeDef => super.transform(tree)
57+
case tree: Template => super.transform(tree)
58+
case macroDefTree: DefDef if macroDefTree.symbol.is(Inline) =>
59+
60+
val reifier = new Reifier(true, null, 1, new LevelInfo, new mutable.ListBuffer[Tree])
61+
62+
val transformedTree = reifier.transform(macroDefTree) // Ignore output, we only need the its embedding
63+
64+
if (reifier.embedded.isEmpty) macroDefTree // Not a macro
65+
else {
66+
if (!macroDefTree.symbol.isStatic) // TODO remove restriction
67+
ctx.error("Inline macro method must be a static method.", macroDefTree.pos)
68+
if (InlineSplice.unapply(macroDefTree.rhs).isEmpty) { // TODO allow multiple splices (issue #4801)
69+
ctx.error(
70+
"""Malformed inline macro.
71+
|
72+
|Expected the ~ to be at the top of the RHS:
73+
| inline def foo(...): Int = ~impl(...)
74+
|or
75+
| inline def foo(...): Int = ~{
76+
| val x = 1
77+
| impl(... x ...)
78+
| }
79+
""".stripMargin, macroDefTree.rhs.pos)
80+
EmptyTree
81+
}
82+
else {
83+
val splicers = List.newBuilder[DefDef]
84+
val transformer = new TreeMap() {
85+
override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = tree match {
86+
case Hole(idx, args) =>
87+
val targs = args.filter(_.isType).map(_.tpe)
88+
89+
val Block((lambdaDef: tpd.DefDef) :: Nil, _) = reifier.embedded(idx)
90+
91+
val splicer = genSplicer(macroDefTree, lambdaDef, targs)
92+
splicers += splicer
93+
94+
val liftedArgs = args.map { arg =>
95+
if (arg.symbol.is(Inline) || arg.symbol == defn.TastyTopLevelSplice_tastyContext) arg
96+
else if (arg.isType) ref(defn.QuotedType_apply).appliedToType(arg.tpe)
97+
else ref(defn.QuotedExpr_apply).appliedToType(arg.tpe.widen).appliedTo(arg)
98+
}
99+
ref(splicer.symbol).appliedToTypes(targs).appliedToArgs(liftedArgs).select(nme.UNARY_~)
100+
case _ => super.transform(tree)
101+
}
102+
}
103+
val newRhs = transformer.transform(transformedTree.asInstanceOf[DefDef].rhs)
104+
val newDef = cpy.DefDef(macroDefTree)(rhs = newRhs)
105+
Thicket(newDef :: splicers.result())
106+
}
107+
}
108+
case _ =>
109+
tree
110+
}
111+
}
112+
}
113+
114+
/** Generates splicer method from a lambda generated by `ReifyQuotes`
115+
*
116+
* Transforms `lambdaDef` (with the given `targs`)
117+
* ```
118+
* (args: Seq[Any]) => {
119+
* val T1$1 = args(0).asInstanceOf[Type[T1]]
120+
* ...
121+
* val x1$1 = args(0).asInstanceOf[X]
122+
* ...
123+
* val y1$1 = args(1).asInstanceOf[Expr[Y]]
124+
* ...
125+
* { ... x1$1 .... '{ ... T1$1.unary_~ ... x1$1.toExpr.unary_~ ... y1$1.unary_~ ... } ... }
126+
* }
127+
* ```
128+
*
129+
* into
130+
*
131+
* ```
132+
* def foo$splice$1[T1, ...](T1$1: Type[T1], ..., x1$1: X, ..., y1: Expr[Y], ....): Z =
133+
* { ... x1$1 .... '{ ... T1$1.unary_~ ... x1$1.toExpr.unary_~ ... y1$1.unary_~ ... } ... }
134+
* ```
135+
*/
136+
private def genSplicer(macroDefTree: DefDef, lambdaDef: DefDef, targs: List[Type])(implicit ctx: Context): DefDef = {
137+
val sym = macroDefTree.symbol
138+
139+
val DefDef(_, _, _, _, Block(args: List[ValDef] @unchecked, body: Tree @unchecked)) = lambdaDef
140+
141+
val splicerRetType = {
142+
val methodType = MethodType(args.map(_.name), args.map(_.tpt.tpe), lambdaDef.symbol.info.finalResultType)
143+
if (targs.isEmpty) methodType
144+
else {
145+
val tpArgSyms = targs.map(_.typeSymbol)
146+
val tpArgName = tpArgSyms.map(_.name.asTypeName)
147+
val tpArgBounds = tpArgSyms.map(_.info.bounds)
148+
PolyType(tpArgName)(_ => tpArgBounds, pt => methodType.subst(targs.map(_.typeSymbol), pt.paramRefs))
149+
}
150+
}
151+
152+
val splicerName = UniqueSpliceName.fresh(sym.name.asTermName)
153+
val splicerSym = ctx.newSymbol(sym.owner, splicerName, sym.flags &~ Inline &~ Implicit, splicerRetType, sym.privateWithin, sym.coord).asTerm
154+
155+
def treeTypeMap(tparams: List[Type], params: List[Tree]) = {
156+
val treeMap: Tree => Tree = {
157+
val map = args.map(_.symbol).zip(params).toMap
158+
tree => map.getOrElse(tree.symbol, tree)
159+
}
160+
val typeMap: Type => Type = new TypeMap() {
161+
private val map = macroDefTree.tparams.map(_.symbol).zip(tparams).toMap
162+
def apply(tp: Type): Type = map.getOrElse(tp.typeSymbol, mapOver(tp))
163+
}
164+
new TreeTypeMap(
165+
typeMap = typeMap, treeMap = treeMap,
166+
oldOwners = lambdaDef.symbol :: Nil, newOwners = splicerSym :: Nil,
167+
substFrom = lambdaDef.tparams.map(_.symbol), substTo = tparams.map(_.typeSymbol)
168+
)
169+
}
170+
171+
polyDefDef(splicerSym, tparams => vparamss => treeTypeMap(tparams, vparamss.head).transform(body))
172+
}
173+
174+
/** InlineSplice is used to detect cases where the expansion
175+
* consists of a (possibly multiple & nested) block or a sole expression.
176+
*/
177+
object InlineSplice {
178+
def unapply(tree: Tree)(implicit ctx: Context): Option[Select] = {
179+
tree match {
180+
case expansion: Select if expansion.symbol.isSplice => Some(expansion)
181+
case Block(List(stat), Literal(Constant(()))) => unapply(stat)
182+
case Block(Nil, expr) => unapply(expr)
183+
case _ => None
184+
}
185+
}
186+
}
187+
188+
}

0 commit comments

Comments
 (0)