Skip to content

Commit

Permalink
Change top-level ~ evaluation scheme
Browse files Browse the repository at this point in the history
* top-level ~ must contain a call to a static method
  * arguments must be quoted
  * inline arguments can be passed directly
* removed dependency on the original inlined code (removed in PostTyper)
* ReifyQuotes is no longer an InfoTransformer
* Splicer implements both checking and evaluation of splices in a
  single abstraction
* Fix #4773
* Fix #4735
  • Loading branch information
nicolasstucki committed Jul 27, 2018
1 parent 946ba3f commit c98ec3e
Show file tree
Hide file tree
Showing 42 changed files with 434 additions and 339 deletions.
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/tastyreflect/TastyImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -448,9 +448,9 @@ class TastyImpl(val rootContext: Contexts.Context) extends scala.tasty.Tasty { s
}

object Inlined extends InlinedExtractor {
def unapply(x: Term)(implicit ctx: Context): Option[(Term, List[Statement], Term)] = x match {
def unapply(x: Term)(implicit ctx: Context): Option[(Option[Term], List[Statement], Term)] = x match {
case x: tpd.Inlined @unchecked =>
Some((x.call, x.bindings, x.expansion))
Some((optional(x.call), x.bindings, x.expansion))
case _ => None
}
}
Expand Down
7 changes: 1 addition & 6 deletions compiler/src/dotty/tools/dotc/transform/PostTyper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -245,12 +245,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
// be duplicated
// 2. To enable correct pickling (calls can share symbols with the inlined code, which
// would trigger an assertion when pickling).
// In the case of macros we keep the call to be able to reconstruct the parameters that
// are passed to the macro. This same simplification is applied in ReifiedQuotes when the
// macro splices are evaluated.
val callTrace =
if (call.symbol.is(Macro)) call
else Ident(call.symbol.topLevelClass.typeRef).withPos(call.pos)
val callTrace = Ident(call.symbol.topLevelClass.typeRef).withPos(call.pos)
cpy.Inlined(tree)(callTrace, transformSub(bindings), transform(expansion)(inlineContext(call)))
case tree: Template =>
withNoCheckNews(tree.parents.flatMap(newPart)) {
Expand Down
112 changes: 20 additions & 92 deletions compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,10 @@ import Flags._
import ast.Trees._
import ast.{TreeTypeMap, untpd}
import util.Positions._
import StdNames._
import tasty.TreePickler.Hole
import MegaPhase.MiniPhase
import SymUtils._
import NameKinds._
import dotty.tools.dotc.ast.tpd.Tree
import dotty.tools.dotc.core.DenotTransformers.InfoTransformer
import typer.Implicits.SearchFailureType

import scala.collection.mutable
Expand Down Expand Up @@ -60,33 +57,9 @@ import dotty.tools.dotc.core.quoted._
*
*
* For transparent macro definitions we assume that we have a single ~ directly as the RHS.
* We will transform the definition from
* ```
* transparent def foo[T1, ...] (transparent x1: X, ..., y1: Y, ....): Z = ~{ ... T1 ... x ... '(y) ... }
* ```
* to
* ```
* transparent def foo[T1, ...] (transparent x1: X, ..., y1: Y, ....): Seq[Any] => Object = { (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_~ ... } ... }
* }
* ```
* Where `transparent` parameters with type Boolean, Byte, Short, Int, Long, Float, Double, Char and String are
* passed as their actual runtime value. See `isStage0Value`. Other `transparent` arguments such as functions are handled
* like `y1: Y`.
*
* Note: the parameters of `foo` are kept for simple overloading resolution but they are not used in the body of `foo`.
*
* At inline site we will call reflectively the static method `foo` with dummy parameters, which will return a
* precompiled version of the function that will evaluate the `Expr[Z]` that `foo` produces. The lambda is then called
* at the inline site with the lifted arguments of the inlined call.
* The Splicer is used to check that the RHS will be interpretable (with the `Splicer`) once inlined.
*/
class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer {
class ReifyQuotes extends MacroTransformWithImplicits {
import ast.tpd._

/** Classloader used for loading macros */
Expand Down Expand Up @@ -255,7 +228,7 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer {
!sym.is(Param) || levelOK(sym.owner)
}

/** Issue a "splice outside quote" error unless we ar in the body of a transparent method */
/** Issue a "splice outside quote" error unless we are in the body of a transparent method */
def spliceOutsideQuotes(pos: Position)(implicit ctx: Context): Unit =
ctx.error(i"splice outside quotes", pos)

Expand All @@ -267,7 +240,7 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer {
def tryHeal(tp: Type, pos: Position)(implicit ctx: Context): Option[String] = tp match {
case tp: TypeRef =>
if (level == 0) {
assert(ctx.owner.ownersIterator.exists(_.is(Macro)))
assert(ctx.owner.ownersIterator.exists(_.is(Transparent)))
None
} else {
val reqType = defn.QuotedTypeType.appliedTo(tp)
Expand Down Expand Up @@ -298,7 +271,7 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer {
else i"${sym.name}.this"
if (!isThis && sym.maybeOwner.isType && !sym.is(Param))
check(sym.owner, sym.owner.thisType, pos)
else if (level == 1 && sym.isType && sym.is(Param) && sym.owner.is(Macro) && !outer.isRoot)
else if (level == 1 && sym.isType && sym.is(Param) && sym.owner.is(Transparent) && !outer.isRoot)
importedTags(sym.typeRef) = capturers(sym)(ref(sym))
else if (sym.exists && !sym.isStaticOwner && !levelOK(sym))
for (errMsg <- tryHeal(tp, pos))
Expand Down Expand Up @@ -510,18 +483,7 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer {
val captured = mutable.LinkedHashMap.empty[Symbol, Tree]
val captured2 = capturer(captured)

def registerCapturer(sym: Symbol): Unit = capturers.put(sym, captured2)
def forceCapture(sym: Symbol): Unit = captured2(ref(sym))

outer.enteredSyms.foreach(registerCapturer)

if (ctx.owner.owner.is(Macro)) {
registerCapturer(defn.TastyTopLevelSplice_tastyContext)
// Force a macro to have the context in first position
forceCapture(defn.TastyTopLevelSplice_tastyContext)
// Force all parameters of the macro to be created in the definition order
outer.enteredSyms.reverse.foreach(forceCapture)
}
outer.enteredSyms.foreach(sym => capturers.put(sym, captured2))

val tree2 = transform(tree)
capturers --= outer.enteredSyms
Expand Down Expand Up @@ -582,16 +544,12 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer {
val last = enteredSyms
stats.foreach(markDef)
mapOverTree(last)
case Inlined(call, bindings, InlineSplice(expansion @ Select(body, name))) if !call.isEmpty =>
assert(call.symbol.is(Macro))
case Inlined(call, bindings, InlineSplice(spliced)) if !call.isEmpty =>
val tree2 =
if (level == 0) {
// Simplification of the call done in PostTyper for non-macros can also be performed now
// see PostTyper `case Inlined(...) =>` for description of the simplification
val call2 = Ident(call.symbol.topLevelClass.typeRef).withPos(call.pos)
val spliced = Splicer.splice(body, call, bindings, tree.pos, macroClassLoader).withPos(tree.pos)
val evaluatedSplice = Splicer.splice(spliced, tree.pos, macroClassLoader).withPos(tree.pos)
if (ctx.reporter.hasErrors) EmptyTree
else transform(cpy.Inlined(tree)(call2, bindings, spliced))
else transform(cpy.Inlined(tree)(call, bindings, evaluatedSplice))
}
else super.transform(tree)

Expand All @@ -607,22 +565,16 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer {
ctx.error("Transparent macro method must be a static method.", tree.pos)
markDef(tree)
val reifier = nested(isQuote = true)
reifier.transform(tree) // Ignore output, we only need the its embedding
assert(reifier.embedded.size == 1)
val lambda = reifier.embedded.head
// replace macro code by lambda used to evaluate the macro expansion
cpy.DefDef(tree)(tpt = TypeTree(macroReturnType), rhs = lambda)
reifier.transform(tree) // Ignore output, only check PCP
cpy.DefDef(tree)(rhs = defaultValue(tree.rhs.tpe))
case _ =>
ctx.error(
"""Malformed transparent macro.
|
|Expected the ~ to be at the top of the RHS:
| transparent def foo(...): Int = ~impl(...)
|or
| transparent def foo(...): Int = ~{
| val x = 1
| impl(... x ...)
| }
| transparent def foo(x: X, ..., y: Y): Int = ~impl(x, ... '(y))
|
|The contents of the splice must call a static method. Arguments must be quoted or inlined.
""".stripMargin, tree.rhs.pos)
EmptyTree
}
Expand Down Expand Up @@ -651,7 +603,7 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer {
}

private def isStage0Value(sym: Symbol)(implicit ctx: Context): Boolean =
(sym.is(Transparent) && sym.owner.is(Macro) && !defn.isFunctionType(sym.info)) ||
(sym.is(Transparent) && sym.owner.is(Transparent) && !defn.isFunctionType(sym.info)) ||
sym == defn.TastyTopLevelSplice_tastyContext // intrinsic value at stage 0

private def liftList(list: List[Tree], tpe: Type)(implicit ctx: Context): Tree = {
Expand All @@ -664,38 +616,14 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer {
* 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
}
def unapply(tree: Tree)(implicit ctx: Context): Option[Tree] = tree match {
case Select(qual, _) if tree.symbol.isSplice && Splicer.canBeSpliced(qual) => Some(qual)
case Block(List(stat), Literal(Constant(()))) => unapply(stat)
case Block(Nil, expr) => unapply(expr)
case _ => None
}
}
}

def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = {
/** Transforms the return type of
* transparent def foo(...): X = ~(...)
* to
* transparent def foo(...): Seq[Any] => Expr[Any] = (args: Seq[Any]) => ...
*/
def transform(tp: Type): Type = tp match {
case tp: MethodType => MethodType(tp.paramNames, tp.paramInfos, transform(tp.resType))
case tp: PolyType => PolyType(tp.paramNames, tp.paramInfos, transform(tp.resType))
case tp: ExprType => ExprType(transform(tp.resType))
case _ => macroReturnType
}
transform(tp)
}

override protected def mayChange(sym: Symbol)(implicit ctx: Context): Boolean =
ctx.compilationUnit.containsQuotesOrSplices && sym.isTerm && sym.is(Macro)

/** Returns the type of the compiled macro as a lambda: Seq[Any] => Object */
private def macroReturnType(implicit ctx: Context): Type =
defn.FunctionType(1).appliedTo(defn.SeqType.appliedTo(defn.AnyType), defn.ObjectType)
}

object ReifyQuotes {
Expand Down
Loading

0 comments on commit c98ec3e

Please sign in to comment.