Skip to content

Commit c98ec3e

Browse files
committed
Change top-level ~ evaluation scheme
* 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
1 parent 946ba3f commit c98ec3e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+434
-339
lines changed

compiler/src/dotty/tools/dotc/tastyreflect/TastyImpl.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -448,9 +448,9 @@ class TastyImpl(val rootContext: Contexts.Context) extends scala.tasty.Tasty { s
448448
}
449449

450450
object Inlined extends InlinedExtractor {
451-
def unapply(x: Term)(implicit ctx: Context): Option[(Term, List[Statement], Term)] = x match {
451+
def unapply(x: Term)(implicit ctx: Context): Option[(Option[Term], List[Statement], Term)] = x match {
452452
case x: tpd.Inlined @unchecked =>
453-
Some((x.call, x.bindings, x.expansion))
453+
Some((optional(x.call), x.bindings, x.expansion))
454454
case _ => None
455455
}
456456
}

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

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -245,12 +245,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
245245
// be duplicated
246246
// 2. To enable correct pickling (calls can share symbols with the inlined code, which
247247
// would trigger an assertion when pickling).
248-
// In the case of macros we keep the call to be able to reconstruct the parameters that
249-
// are passed to the macro. This same simplification is applied in ReifiedQuotes when the
250-
// macro splices are evaluated.
251-
val callTrace =
252-
if (call.symbol.is(Macro)) call
253-
else Ident(call.symbol.topLevelClass.typeRef).withPos(call.pos)
248+
val callTrace = Ident(call.symbol.topLevelClass.typeRef).withPos(call.pos)
254249
cpy.Inlined(tree)(callTrace, transformSub(bindings), transform(expansion)(inlineContext(call)))
255250
case tree: Template =>
256251
withNoCheckNews(tree.parents.flatMap(newPart)) {

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

Lines changed: 20 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,10 @@ import Flags._
77
import ast.Trees._
88
import ast.{TreeTypeMap, untpd}
99
import util.Positions._
10-
import StdNames._
1110
import tasty.TreePickler.Hole
12-
import MegaPhase.MiniPhase
1311
import SymUtils._
1412
import NameKinds._
1513
import dotty.tools.dotc.ast.tpd.Tree
16-
import dotty.tools.dotc.core.DenotTransformers.InfoTransformer
1714
import typer.Implicits.SearchFailureType
1815

1916
import scala.collection.mutable
@@ -60,33 +57,9 @@ import dotty.tools.dotc.core.quoted._
6057
*
6158
*
6259
* For transparent macro definitions we assume that we have a single ~ directly as the RHS.
63-
* We will transform the definition from
64-
* ```
65-
* transparent def foo[T1, ...] (transparent x1: X, ..., y1: Y, ....): Z = ~{ ... T1 ... x ... '(y) ... }
66-
* ```
67-
* to
68-
* ```
69-
* transparent def foo[T1, ...] (transparent x1: X, ..., y1: Y, ....): Seq[Any] => Object = { (args: Seq[Any]) => {
70-
* val T1$1 = args(0).asInstanceOf[Type[T1]]
71-
* ...
72-
* val x1$1 = args(0).asInstanceOf[X]
73-
* ...
74-
* val y1$1 = args(1).asInstanceOf[Expr[Y]]
75-
* ...
76-
* { ... x1$1 .... '{ ... T1$1.unary_~ ... x1$1.toExpr.unary_~ ... y1$1.unary_~ ... } ... }
77-
* }
78-
* ```
79-
* Where `transparent` parameters with type Boolean, Byte, Short, Int, Long, Float, Double, Char and String are
80-
* passed as their actual runtime value. See `isStage0Value`. Other `transparent` arguments such as functions are handled
81-
* like `y1: Y`.
82-
*
83-
* Note: the parameters of `foo` are kept for simple overloading resolution but they are not used in the body of `foo`.
84-
*
85-
* At inline site we will call reflectively the static method `foo` with dummy parameters, which will return a
86-
* precompiled version of the function that will evaluate the `Expr[Z]` that `foo` produces. The lambda is then called
87-
* at the inline site with the lifted arguments of the inlined call.
60+
* The Splicer is used to check that the RHS will be interpretable (with the `Splicer`) once inlined.
8861
*/
89-
class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer {
62+
class ReifyQuotes extends MacroTransformWithImplicits {
9063
import ast.tpd._
9164

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

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

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

513-
def registerCapturer(sym: Symbol): Unit = capturers.put(sym, captured2)
514-
def forceCapture(sym: Symbol): Unit = captured2(ref(sym))
515-
516-
outer.enteredSyms.foreach(registerCapturer)
517-
518-
if (ctx.owner.owner.is(Macro)) {
519-
registerCapturer(defn.TastyTopLevelSplice_tastyContext)
520-
// Force a macro to have the context in first position
521-
forceCapture(defn.TastyTopLevelSplice_tastyContext)
522-
// Force all parameters of the macro to be created in the definition order
523-
outer.enteredSyms.reverse.foreach(forceCapture)
524-
}
486+
outer.enteredSyms.foreach(sym => capturers.put(sym, captured2))
525487

526488
val tree2 = transform(tree)
527489
capturers --= outer.enteredSyms
@@ -582,16 +544,12 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer {
582544
val last = enteredSyms
583545
stats.foreach(markDef)
584546
mapOverTree(last)
585-
case Inlined(call, bindings, InlineSplice(expansion @ Select(body, name))) if !call.isEmpty =>
586-
assert(call.symbol.is(Macro))
547+
case Inlined(call, bindings, InlineSplice(spliced)) if !call.isEmpty =>
587548
val tree2 =
588549
if (level == 0) {
589-
// Simplification of the call done in PostTyper for non-macros can also be performed now
590-
// see PostTyper `case Inlined(...) =>` for description of the simplification
591-
val call2 = Ident(call.symbol.topLevelClass.typeRef).withPos(call.pos)
592-
val spliced = Splicer.splice(body, call, bindings, tree.pos, macroClassLoader).withPos(tree.pos)
550+
val evaluatedSplice = Splicer.splice(spliced, tree.pos, macroClassLoader).withPos(tree.pos)
593551
if (ctx.reporter.hasErrors) EmptyTree
594-
else transform(cpy.Inlined(tree)(call2, bindings, spliced))
552+
else transform(cpy.Inlined(tree)(call, bindings, evaluatedSplice))
595553
}
596554
else super.transform(tree)
597555

@@ -607,22 +565,16 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer {
607565
ctx.error("Transparent macro method must be a static method.", tree.pos)
608566
markDef(tree)
609567
val reifier = nested(isQuote = true)
610-
reifier.transform(tree) // Ignore output, we only need the its embedding
611-
assert(reifier.embedded.size == 1)
612-
val lambda = reifier.embedded.head
613-
// replace macro code by lambda used to evaluate the macro expansion
614-
cpy.DefDef(tree)(tpt = TypeTree(macroReturnType), rhs = lambda)
568+
reifier.transform(tree) // Ignore output, only check PCP
569+
cpy.DefDef(tree)(rhs = defaultValue(tree.rhs.tpe))
615570
case _ =>
616571
ctx.error(
617572
"""Malformed transparent macro.
618573
|
619574
|Expected the ~ to be at the top of the RHS:
620-
| transparent def foo(...): Int = ~impl(...)
621-
|or
622-
| transparent def foo(...): Int = ~{
623-
| val x = 1
624-
| impl(... x ...)
625-
| }
575+
| transparent def foo(x: X, ..., y: Y): Int = ~impl(x, ... '(y))
576+
|
577+
|The contents of the splice must call a static method. Arguments must be quoted or inlined.
626578
""".stripMargin, tree.rhs.pos)
627579
EmptyTree
628580
}
@@ -651,7 +603,7 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer {
651603
}
652604

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

657609
private def liftList(list: List[Tree], tpe: Type)(implicit ctx: Context): Tree = {
@@ -664,38 +616,14 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer {
664616
* consists of a (possibly multiple & nested) block or a sole expression.
665617
*/
666618
object InlineSplice {
667-
def unapply(tree: Tree)(implicit ctx: Context): Option[Select] = {
668-
tree match {
669-
case expansion: Select if expansion.symbol.isSplice => Some(expansion)
670-
case Block(List(stat), Literal(Constant(()))) => unapply(stat)
671-
case Block(Nil, expr) => unapply(expr)
672-
case _ => None
673-
}
619+
def unapply(tree: Tree)(implicit ctx: Context): Option[Tree] = tree match {
620+
case Select(qual, _) if tree.symbol.isSplice && Splicer.canBeSpliced(qual) => Some(qual)
621+
case Block(List(stat), Literal(Constant(()))) => unapply(stat)
622+
case Block(Nil, expr) => unapply(expr)
623+
case _ => None
674624
}
675625
}
676626
}
677-
678-
def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = {
679-
/** Transforms the return type of
680-
* transparent def foo(...): X = ~(...)
681-
* to
682-
* transparent def foo(...): Seq[Any] => Expr[Any] = (args: Seq[Any]) => ...
683-
*/
684-
def transform(tp: Type): Type = tp match {
685-
case tp: MethodType => MethodType(tp.paramNames, tp.paramInfos, transform(tp.resType))
686-
case tp: PolyType => PolyType(tp.paramNames, tp.paramInfos, transform(tp.resType))
687-
case tp: ExprType => ExprType(transform(tp.resType))
688-
case _ => macroReturnType
689-
}
690-
transform(tp)
691-
}
692-
693-
override protected def mayChange(sym: Symbol)(implicit ctx: Context): Boolean =
694-
ctx.compilationUnit.containsQuotesOrSplices && sym.isTerm && sym.is(Macro)
695-
696-
/** Returns the type of the compiled macro as a lambda: Seq[Any] => Object */
697-
private def macroReturnType(implicit ctx: Context): Type =
698-
defn.FunctionType(1).appliedTo(defn.SeqType.appliedTo(defn.AnyType), defn.ObjectType)
699627
}
700628

701629
object ReifyQuotes {

0 commit comments

Comments
 (0)