Skip to content

Commit 1d9006a

Browse files
committed
wip
1 parent 7425728 commit 1d9006a

File tree

3 files changed

+96
-22
lines changed

3 files changed

+96
-22
lines changed

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

Lines changed: 60 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,30 @@ import dotty.tools.dotc.core.quoted._
6262
* )
6363
* ```
6464
* and then performs the same transformation on `'{ ... x1$1.unary_~ ... x2$1.unary_~ ...}`.
65+
*
66+
*
67+
* For inline macro definitions we assume that we have a single ~ directly as the RHS.
68+
* We will transform the definition from
69+
* ```
70+
* inline def foo[T1, ...](inline x1: X, ..., y1: Y, ....): Z = ~{ ... T1 ... x ... '(y) ... }
71+
* ```
72+
* to
73+
* ```
74+
* inline def foo[T1, ...](inline x1: X, ..., y1: Y, ....): Seq[Any] => Object = { (args: Seq[Any]) => {
75+
* val T1$1 = args(0).asInstanceOf[Type[T1]]
76+
* ...
77+
* val x1$1 = args(0).asInstanceOf[X]
78+
* ...
79+
* val y1$1 = args(1).asInstanceOf[Expr[Y]]
80+
* ...
81+
* { ... T1$1.unary_~ ... x ... '(y1$1.unary_~) ... }
82+
* }
83+
* ```
84+
* Note: the parameters of `foo` are kept for simple overloading resolution but they are not used in the body of `foo`.
85+
*
86+
* At inline site we will call reflectively the static method `foo` with dummy parameters, which will return a
87+
* precompiled version of the function that will evaluate the `Expr[Z]` that `foo` produces. The lambda is then called
88+
* at the inline site with the lifted arguments of the inlined call.
6589
*/
6690
class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer {
6791
import ast.tpd._
@@ -444,13 +468,8 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer {
444468
val captured = mutable.LinkedHashMap.empty[Symbol, Tree]
445469
val captured2 = capturer(captured)
446470
outer.enteredSyms.foreach(s => capturers.put(s, captured2))
447-
if (ctx.owner.owner.is(Macro)) {
448-
outer.enteredSyms.reverse.foreach(s => {
449-
assert(s.is(Param))
450-
assert(s.owner == ctx.owner.owner)
451-
captured2(ref(s))
452-
})
453-
}
471+
if (ctx.owner.owner.is(Macro))
472+
outer.enteredSyms.reverse.foreach(s => captured2(ref(s)))
454473
val tree2 = transform(tree)
455474
capturers --= outer.enteredSyms
456475
seq(captured.result().valuesIterator.toList, tree2)
@@ -524,15 +543,31 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer {
524543
case _: Import =>
525544
tree
526545
case tree: DefDef if tree.symbol.is(Macro) && level == 0 =>
527-
if (!tree.symbol.isStatic)
528-
ctx.error("Inline macro method must be a static method.", tree.pos)
529-
markDef(tree)
530-
val reifier = nested(isQuote = true)
531-
reifier.transform(tree) // Ignore output, we only need the its embedding
532-
assert(reifier.embedded.size == 1)
533-
val macroLambda = reifier.embedded.head
534-
// replace macro code by lambda used to evaluate the macro expansion
535-
cpy.DefDef(tree)(tpt = TypeTree(defn.ObjectType), rhs = macroLambda)
546+
tree.rhs match {
547+
case InlineSplice(_) =>
548+
if (!tree.symbol.isStatic)
549+
ctx.error("Inline macro method must be a static method.", tree.pos)
550+
markDef(tree)
551+
val reifier = nested(isQuote = true)
552+
reifier.transform(tree) // Ignore output, we only need the its embedding
553+
assert(reifier.embedded.size == 1)
554+
val lambda = reifier.embedded.head
555+
// replace macro code by lambda used to evaluate the macro expansion
556+
cpy.DefDef(tree)(tpt = TypeTree(macroReturnType), rhs = lambda)
557+
case _ =>
558+
ctx.error(
559+
"""Malformed inline macro.
560+
|
561+
|Expected the ~ to be at the top of the RHS:
562+
| inline def foo(...): Int = ~impl(...)
563+
|or
564+
| inline def foo(...): Int = ~{
565+
| val x = 1
566+
| impl(... x ...)
567+
| }
568+
""".stripMargin, tree.rhs.pos)
569+
EmptyTree
570+
}
536571
case _ =>
537572
markDef(tree)
538573
checkLevel(mapOverTree(enteredSyms))
@@ -561,21 +596,25 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer {
561596
}
562597

563598
def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = {
564-
/** Transforms the return type
599+
/** Transforms the return type of
565600
* inline def foo(...): X = ~(...)
566601
* to
567-
* inline def foo(...): Seq[Any] => Object = (args: Seq[Any]) => ...
602+
* inline def foo(...): Seq[Any] => Expr[Any] = (args: Seq[Any]) => ...
568603
*/
569604
def transform(tp: Type): Type = tp match {
570-
case tp: PolyType => PolyType(tp.paramNames, tp.paramInfos, transform(tp.resType))
571605
case tp: MethodType => MethodType(tp.paramNames, tp.paramInfos, transform(tp.resType))
606+
case tp: PolyType => PolyType(tp.paramNames, tp.paramInfos, transform(tp.resType))
572607
case tp: ExprType => ExprType(transform(tp.resType))
573-
case _ => defn.FunctionType(1).appliedTo(defn.SeqType.appliedTo(defn.AnyType), defn.ObjectType)
608+
case _ => macroReturnType
574609
}
575610
transform(tp)
576611
}
577612

578-
override protected def mayChange(sym: Symbol)(implicit ctx: Context) = sym.is(Macro)
613+
override protected def mayChange(sym: Symbol)(implicit ctx: Context): Boolean = sym.is(Macro)
614+
615+
/** Returns the type of the compiled macro as a lambda: Seq[Any] => Object */
616+
private def macroReturnType(implicit ctx: Context): Type =
617+
defn.FunctionType(1).appliedTo(defn.SeqType.appliedTo(defn.AnyType), defn.ObjectType)
579618
}
580619

581620
object ReifyQuotes {

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ object Splicer {
2727
/** Splice the Tree for a Quoted expression. `~'(xyz)` becomes `xyz`
2828
* and for `~xyz` the tree of `xyz` is interpreted for which the
2929
* resulting expression is returned as a `Tree`
30+
*
31+
* See: `ReifyQuotes`
3032
*/
3133
def splice(tree: Tree, call: Tree, bindings: List[Tree], pos: Position)(implicit ctx: Context): Tree = tree match {
3234
case Quoted(quotedTree) => quotedTree
@@ -40,7 +42,12 @@ object Splicer {
4042
interpreted.flatMap(lambda => evaluateLambda(lambda, liftedArgs, pos)).fold(tree)(PickledQuotes.quotedExprToTree)
4143
}
4244

43-
private def getLiftedArgs(call: Tree, bindings: List[Tree])(implicit ctx: Context) = {
45+
/** Given the inline code and bindings, compute the lifted arguments that will be used to execute the macro
46+
* - Type parameters are lifted to quoted.Types.TreeType
47+
* - Inline parameters are listed as their value
48+
* - Other parameters are lifted to quoted.Types.TreeExpr (may reference a binding)
49+
*/
50+
private def getLiftedArgs(call: Tree, bindings: List[Tree])(implicit ctx: Context): List[Any] = {
4451
val bindMap = bindings.map {
4552
case vdef: ValDef => (vdef.rhs, ref(vdef.symbol))
4653
}.toMap
@@ -55,6 +62,7 @@ object Splicer {
5562
case (arg: Literal, tp) if tp.hasAnnotation(defn.InlineParamAnnot) => arg.const.value
5663
case (arg, tp) =>
5764
assert(!tp.hasAnnotation(defn.InlineParamAnnot))
65+
// Replace argument by its binding
5866
new scala.quoted.Exprs.TreeExpr(bindMap.getOrElse(arg, arg))
5967
}
6068
args1 ::: liftArgs(tp.resType, args.tail)

tests/neg/quote-macro-splice.scala

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import scala.quoted._
2+
3+
object Test {
4+
5+
inline def foo1: Int = { // error
6+
println()
7+
~impl(1.toExpr)
8+
}
9+
10+
inline def foo2: Int = { // error
11+
~impl(1.toExpr)
12+
~impl(2.toExpr)
13+
}
14+
15+
inline def foo3: Int = { // error
16+
val a = 1
17+
~impl('(a))
18+
}
19+
20+
inline def foo4: Int = { // error
21+
~impl('(1))
22+
1
23+
}
24+
25+
def impl(n: Expr[Int]): Expr[Int] = ???
26+
27+
}

0 commit comments

Comments
 (0)