Skip to content
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

Change top-level ~ evaluation scheme #4822

Merged
merged 3 commits into from
Jul 27, 2018
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
8 changes: 1 addition & 7 deletions compiler/src/dotty/tools/dotc/transform/PostTyper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,6 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
if (c.owner eq c.outer.owner) markAsMacro(c.outer)
else if (c.owner.isTransparentMethod) {
c.owner.setFlag(Macro)
c.owner.resetFlag(Erased) // FIXME: Macros should be Erased, but that causes problems right now
}
else if (!c.outer.owner.is(Package)) markAsMacro(c.outer)

Expand Down Expand Up @@ -245,12 +244,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))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the comment that was here before apply here as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. Now we just remove the RHS to prune the tree earlier in the pipeline. Maybe this could be done later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic will be simplified later in #4826.

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