-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Decoupling Macros in ReifyQuotes #4795
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
package dotty.tools.dotc | ||
package transform | ||
|
||
import core._ | ||
import Decorators._ | ||
import Types._ | ||
import Contexts._ | ||
import Symbols._ | ||
import Constants._ | ||
import Flags._ | ||
import ast.Trees._ | ||
import ast.{TreeTypeMap, tpd} | ||
import tasty.TreePickler.Hole | ||
import SymUtils._ | ||
import NameKinds._ | ||
|
||
import scala.collection.mutable | ||
import dotty.tools.dotc.core.StdNames._ | ||
|
||
|
||
/** Translates macro implementation. | ||
* Checks that the phase consistency principle (PCP) holds on the body of a macro. | ||
* | ||
* | ||
* Transforms an inline macro definitions we assume that we have a single ~ directly as the RHS. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (?) Should be two sentences, maybe? |
||
* We will transform the definition from | ||
* ``` | ||
* inline def foo[T1, ...](inline x1: X, ..., y1: Y, ....): Z = ~{ ... x1 ... '{ ... T1 ... x1 ... '(y) ... } } | ||
* ``` | ||
* to | ||
* ``` | ||
* inline def foo[T1, ...](inline x1: X, ..., y1: Y, ....): Z = ~foo$splice$1[T1, ...]('[T1], ..., x1, ..., '(y1), ...) | ||
* | ||
* def foo$splice$1[T1, ...](T1$1: Type[T1], ..., x1$1: X, ..., y1: Expr[Y], ....): Z = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if the original splice is already of the form
In that case, we can simply forward to If we do have a requirement like this, what is the added benefit of the phase? |
||
* { ... x1$1 .... '{ ... T1$1.unary_~ ... x1$1.toExpr.unary_~ ... y1$1.unary_~ ... } ... } | ||
* ``` | ||
* Where `inline` parameters with type Boolean, Byte, Short, Int, Long, Float, Double, Char and String are | ||
* passed as their actual runtime value. See `isStage0Value`. Other `inline` arguments such as functions are handled | ||
* like `y1: Y`. | ||
* | ||
* | ||
* At inline site we will call reflectively the static method `foo$splice$1` with the interpreted parameters. | ||
*/ | ||
class MacrosSplitter extends ReifyQuotes { | ||
import ast.tpd._ | ||
|
||
override def phaseName: String = "macrosSplitter" | ||
|
||
override def run(implicit ctx: Context): Unit = | ||
if (ctx.compilationUnit.containsQuotesOrSplices) super.run | ||
|
||
protected override def newTransformer(implicit ctx: Context): Transformer = { | ||
new Transformer { | ||
override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = tree match { | ||
case tree: PackageDef => super.transform(tree) | ||
case tree: TypeDef => super.transform(tree) | ||
case tree: Template => super.transform(tree) | ||
case macroDefTree: DefDef if macroDefTree.symbol.is(Inline) => | ||
|
||
val reifier = new Reifier(true, null, 1, new LevelInfo, new mutable.ListBuffer[Tree]) | ||
|
||
val transformedTree = reifier.transform(macroDefTree) // Ignore output, we only need the its embedding | ||
|
||
if (reifier.embedded.isEmpty) macroDefTree // Not a macro | ||
else { | ||
if (!macroDefTree.symbol.isStatic) // TODO remove restriction (issue #4803) | ||
ctx.error("Inline macro method must be a static method.", macroDefTree.pos) | ||
if (InlineSplice.unapply(macroDefTree.rhs).isEmpty) { // TODO allow multiple splices (issue #4801) | ||
ctx.error( | ||
"""Malformed inline macro. | ||
| | ||
|Expected the ~ to be at the top of the RHS: | ||
| inline def foo(...): Int = ~impl(...) | ||
|or | ||
| inline def foo(...): Int = ~{ | ||
| val x = 1 | ||
| impl(... x ...) | ||
| } | ||
""".stripMargin, macroDefTree.rhs.pos) | ||
EmptyTree | ||
} | ||
else { | ||
val splicers = List.newBuilder[DefDef] | ||
val transformer = new TreeMap() { | ||
override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = tree match { | ||
case Hole(idx, args) => | ||
val targs = args.filter(_.isType).map(_.tpe) | ||
|
||
val Block((lambdaDef: tpd.DefDef) :: Nil, _) = reifier.embedded(idx) | ||
|
||
val splicer = genSplicer(macroDefTree, lambdaDef, targs) | ||
splicers += splicer | ||
|
||
val liftedArgs = args.map { arg => | ||
if (arg.symbol.is(Inline) || arg.symbol == defn.TastyTopLevelSplice_tastyContext) arg | ||
else if (arg.isType) ref(defn.QuotedType_apply).appliedToType(arg.tpe) | ||
else ref(defn.QuotedExpr_apply).appliedToType(arg.tpe.widen).appliedTo(arg) | ||
} | ||
ref(splicer.symbol).appliedToTypes(targs).appliedToArgs(liftedArgs).select(nme.UNARY_~) | ||
case _ => super.transform(tree) | ||
} | ||
} | ||
val newRhs = transformer.transform(transformedTree.asInstanceOf[DefDef].rhs) | ||
val newDef = cpy.DefDef(macroDefTree)(rhs = newRhs) | ||
Thicket(newDef :: splicers.result()) | ||
} | ||
} | ||
case _ => | ||
tree | ||
} | ||
} | ||
} | ||
|
||
/** Generates splicer method from a lambda generated by `ReifyQuotes` | ||
* | ||
* Transforms `lambdaDef` (with the given `targs`) | ||
* ``` | ||
* (args: Seq[Any]) => { | ||
* val T1$1 = args(0).asInstanceOf[Type[T1]] | ||
* ... | ||
* val x1$1 = args(0).asInstanceOf[X] | ||
* ... | ||
* val y1$1 = args(1).asInstanceOf[Expr[Y]] | ||
* ... | ||
* { ... x1$1 .... '{ ... T1$1.unary_~ ... x1$1.toExpr.unary_~ ... y1$1.unary_~ ... } ... } | ||
* } | ||
* ``` | ||
* | ||
* into | ||
* | ||
* ``` | ||
* def foo$splice$1[T1, ...](T1$1: Type[T1], ..., x1$1: X, ..., y1: Expr[Y], ....): Z = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Where does |
||
* { ... x1$1 .... '{ ... T1$1.unary_~ ... x1$1.toExpr.unary_~ ... y1$1.unary_~ ... } ... } | ||
* ``` | ||
*/ | ||
private def genSplicer(macroDefTree: DefDef, lambdaDef: DefDef, targs: List[Type])(implicit ctx: Context): DefDef = { | ||
val sym = macroDefTree.symbol | ||
|
||
val DefDef(_, _, _, _, Block(args: List[ValDef] @unchecked, body: Tree @unchecked)) = lambdaDef | ||
|
||
val splicerRetType = { | ||
val methodType = MethodType(args.map(_.name), args.map(_.tpt.tpe), lambdaDef.symbol.info.finalResultType) | ||
if (targs.isEmpty) methodType | ||
else { | ||
val tpArgSyms = targs.map(_.typeSymbol) | ||
val tpArgName = tpArgSyms.map(_.name.asTypeName) | ||
val tpArgBounds = tpArgSyms.map(_.info.bounds) | ||
PolyType(tpArgName)(_ => tpArgBounds, pt => methodType.subst(targs.map(_.typeSymbol), pt.paramRefs)) | ||
} | ||
} | ||
|
||
val splicerName = UniqueSpliceName.fresh(sym.name.asTermName) | ||
val splicerSym = ctx.newSymbol(sym.owner, splicerName, sym.flags &~ Inline &~ Implicit, splicerRetType, sym.privateWithin, sym.coord).asTerm | ||
|
||
def treeTypeMap(tparams: List[Type], params: List[Tree]) = { | ||
val treeMap: Tree => Tree = { | ||
val map = args.map(_.symbol).zip(params).toMap | ||
tree => map.getOrElse(tree.symbol, tree) | ||
} | ||
val typeMap: Type => Type = new TypeMap() { | ||
private val map = macroDefTree.tparams.map(_.symbol).zip(tparams).toMap | ||
def apply(tp: Type): Type = map.getOrElse(tp.typeSymbol, mapOver(tp)) | ||
} | ||
new TreeTypeMap( | ||
typeMap = typeMap, treeMap = treeMap, | ||
oldOwners = lambdaDef.symbol :: Nil, newOwners = splicerSym :: Nil, | ||
substFrom = lambdaDef.tparams.map(_.symbol), substTo = tparams.map(_.typeSymbol) | ||
) | ||
} | ||
|
||
polyDefDef(splicerSym, tparams => vparamss => treeTypeMap(tparams, vparamss.head).transform(body)) | ||
} | ||
|
||
/** InlineSplice is used to detect cases where the expansion | ||
* consists of a (possibly multiple & nested) block or a sole expression. | ||
*/ | ||
object InlineSplice { | ||
def unapply(tree: Tree)(implicit ctx: Context): Option[Select] = { | ||
tree match { | ||
case expansion: Select if expansion.symbol.isSplice => Some(expansion) | ||
case Block(List(stat), Literal(Constant(()))) => unapply(stat) | ||
case Block(Nil, expr) => unapply(expr) | ||
case _ => None | ||
} | ||
} | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"to macro bodies"