
dotty.tools.dotc.transform.ReifyQuotes.scala Maven / Gradle / Ivy
package dotty.tools.dotc
package transform
import core._
import Decorators._, Flags._, Types._, Contexts._, Symbols._, Constants._
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
import dotty.tools.dotc.core.StdNames._
import dotty.tools.dotc.core.quoted._
/** Translates quoted terms and types to `unpickle` method calls.
* Checks that the phase consistency principle (PCP) holds.
*
*
* Transforms top level quote
* ```
* '{ ...
* val x1 = ???
* val x2 = ???
* ...
* ~{ ... '{ ... x1 ... x2 ...} ... }
* ...
* }
* ```
* to
* ```
* unpickle(
* [[ // PICKLED TASTY
* ...
* val x1 = ???
* val x2 = ???
* ...
* Hole(0 | x1, x2)
* ...
* ]],
* List(
* (args: Seq[Any]) => {
* val x1$1 = args(0).asInstanceOf[Expr[T]]
* val x2$1 = args(1).asInstanceOf[Expr[T]] // can be asInstanceOf[Type[T]]
* ...
* { ... '{ ... x1$1.unary_~ ... x2$1.unary_~ ...} ... }
* }
* )
* )
* ```
* and then performs the same transformation on `'{ ... x1$1.unary_~ ... x2$1.unary_~ ...}`.
*
*
* For inline macro definitions we assume that we have a single ~ directly as the RHS.
* We will transform the definition from
* ```
* inline def foo[T1, ...](inline x1: X, ..., y1: Y, ....): Z = ~{ ... T1 ... x ... '(y) ... }
* ```
* to
* ```
* inline def foo[T1, ...](inline 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 `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`.
*
* 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.
*/
class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer {
import ast.tpd._
/** Classloader used for loading macros */
private[this] var myMacroClassLoader: java.lang.ClassLoader = _
private def macroClassLoader(implicit ctx: Context): ClassLoader = {
if (myMacroClassLoader == null) {
val urls = ctx.settings.classpath.value.split(':').map(cp => java.nio.file.Paths.get(cp).toUri.toURL)
myMacroClassLoader = new java.net.URLClassLoader(urls, getClass.getClassLoader)
}
myMacroClassLoader
}
override def phaseName: String = "reifyQuotes"
override def run(implicit ctx: Context): Unit =
if (ctx.compilationUnit.containsQuotesOrSplices) super.run
protected def newTransformer(implicit ctx: Context): Transformer =
new Reifier(inQuote = false, null, 0, new LevelInfo, new mutable.ListBuffer[Tree])
private class LevelInfo {
/** A map from locally defined symbols to the staging levels of their definitions */
val levelOf = new mutable.HashMap[Symbol, Int]
/** Register a reference defined in a quote but used in another quote nested in a splice.
* Returns a version of the reference that needs to be used in its place.
* '{
* val x = ???
* { ... '{ ... x ... } ... }.unary_~
* }
* Eta expanding the `x` in `{ ... '{ ... x ... } ... }.unary_~` will return a `x$1.unary_~` for which the `x$1`
* be created by some outer reifier.
*
* This transformation is only applied to definitions at staging level 1.
*
* See `isCaptured`
*/
val capturers = new mutable.HashMap[Symbol, Tree => Tree]
}
/** The main transformer class
* @param inQuote we are within a `'(...)` context that is not shadowed by a nested `~(...)`
* @param outer the next outer reifier, null is this is the topmost transformer
* @param level the current level, where quotes add one and splices subtract one level
* @param levels a stacked map from symbols to the levels in which they were defined
* @param embedded a list of embedded quotes (if `inSplice = true`) or splices (if `inQuote = true`
*/
private class Reifier(inQuote: Boolean, val outer: Reifier, val level: Int, levels: LevelInfo,
val embedded: mutable.ListBuffer[Tree]) extends ImplicitsTransformer {
import levels._
assert(level >= 0)
/** A nested reifier for a quote (if `isQuote = true`) or a splice (if not) */
def nested(isQuote: Boolean): Reifier = {
val nestedEmbedded = if (level > 1 || (level == 1 && isQuote)) embedded else new mutable.ListBuffer[Tree]
new Reifier(isQuote, this, if (isQuote) level + 1 else level - 1, levels, nestedEmbedded)
}
/** We are in a `~(...)` context that is not shadowed by a nested `'(...)` */
def inSplice = outer != null && !inQuote
/** We are not in a `~(...)` or a `'(...)` */
def isRoot = outer == null
/** A map from type ref T to expressions of type `quoted.Type[T]`".
* These will be turned into splices using `addTags` and represent type variables
* that can be possibly healed.
*/
val importedTags = new mutable.LinkedHashMap[TypeRef, Tree]()
/** A map from type ref T to expressions of type `quoted.Type[T]`" like `importedTags`
* These will be turned into splices using `addTags` and represent types spliced
* explicitly.
*/
val explicitTags = new mutable.LinkedHashSet[TypeRef]()
/** A stack of entered symbols, to be unwound after scope exit */
var enteredSyms: List[Symbol] = Nil
/** Assuming importedTags = `Type1 -> tag1, ..., TypeN -> tagN`, the expression
*
* { type = .unary_~
* ...
* type = .unary_~
*
* }
*
* references to `TypeI` in `expr` are rewired to point to the locally
* defined versions. As a side effect, prepend the expressions `tag1, ..., `tagN`
* as splices to `embedded`.
*/
private def addTags(expr: Tree)(implicit ctx: Context): Tree = {
def mkTagSymbolAndAssignType(typeRef: TypeRef, tag: Tree): Tree = {
val rhs = transform(tag.select(tpnme.UNARY_~))
val alias = ctx.typeAssigner.assignType(untpd.TypeBoundsTree(rhs, rhs), rhs, rhs)
val original = typeRef.symbol.asType
val local = ctx.newSymbol(
owner = ctx.owner,
name = UniqueName.fresh("T".toTermName).toTypeName,
flags = Synthetic,
info = TypeAlias(tag.tpe.select(tpnme.UNARY_~)),
coord = typeRef.prefix.termSymbol.coord).asType
ctx.typeAssigner.assignType(untpd.TypeDef(local.name, alias), local)
}
if (importedTags.isEmpty && explicitTags.isEmpty) expr
else {
val itags = importedTags.toList
// The tree of the tag for each tag comes from implicit search in `tryHeal`
val typeDefs = for ((tref, tag) <- itags) yield {
mkTagSymbolAndAssignType(tref, tag)
}
importedTags.clear()
// The tree of the tag for each tag comes from a type ref e.g., ~t
val explicitTypeDefs = for (tref <- explicitTags) yield {
val tag = ref(tref.prefix.termSymbol)
mkTagSymbolAndAssignType(tref, tag)
}
val tagsExplicitTypeDefsPairs = explicitTags.zip(explicitTypeDefs)
explicitTags.clear()
// Maps type splices to type references of tags e.g., ~t -> some type T$1
val map: Map[Type, Type] = {
tagsExplicitTypeDefsPairs.map(x => (x._1, x._2.symbol.typeRef)) ++
(itags.map(_._1) zip typeDefs.map(_.symbol.typeRef))
}.toMap
val tMap = new TypeMap() {
override def apply(tp: Type): Type = map.getOrElse(tp, mapOver(tp))
}
Block(typeDefs ++ explicitTypeDefs,
new TreeTypeMap(
typeMap = tMap,
substFrom = itags.map(_._1.symbol),
substTo = typeDefs.map(_.symbol)
).apply(expr))
}
}
/** Enter staging level of symbol defined by `tree`, if applicable. */
def markDef(tree: Tree)(implicit ctx: Context) = tree match {
case tree: DefTree =>
val sym = tree.symbol
if ((sym.isClass || !sym.maybeOwner.isType) && !levelOf.contains(sym)) {
levelOf(sym) = level
enteredSyms = sym :: enteredSyms
}
case _ =>
}
/** Does the level of `sym` match the current level?
* An exception is made for inline vals in macros. These are also OK if their level
* is one higher than the current level, because on execution such values
* are constant expression trees and we can pull out the constant from the tree.
*/
def levelOK(sym: Symbol)(implicit ctx: Context): Boolean = levelOf.get(sym) match {
case Some(l) =>
l == level ||
l == 1 && level == 0 && isStage0Value(sym)
case None =>
!sym.is(Param) || levelOK(sym.owner)
}
/** Issue a "splice outside quote" error unless we ar in the body of an inline method */
def spliceOutsideQuotes(pos: Position)(implicit ctx: Context): Unit =
ctx.error(i"splice outside quotes", pos)
/** Try to heal phase-inconsistent reference to type `T` using a local type definition.
* @return None if successful
* @return Some(msg) if unsuccessful where `msg` is a potentially empty error message
* to be added to the "inconsistent phase" message.
*/
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)))
None
} else {
val reqType = defn.QuotedTypeType.appliedTo(tp)
val tag = ctx.typer.inferImplicitArg(reqType, pos)
tag.tpe match {
case fail: SearchFailureType =>
Some(i"""
|
| The access would be accepted with the right type tag, but
| ${ctx.typer.missingArgMsg(tag, reqType, "")}""")
case _ =>
importedTags(tp) = nested(isQuote = false).transform(tag)
None
}
}
case _ =>
Some("")
}
/** Check reference to `sym` for phase consistency, where `tp` is the underlying type
* by which we refer to `sym`.
*/
def check(sym: Symbol, tp: Type, pos: Position)(implicit ctx: Context): Unit = {
val isThis = tp.isInstanceOf[ThisType]
def symStr =
if (!isThis) sym.show
else if (sym.is(ModuleClass)) sym.sourceModule.show
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)
importedTags(sym.typeRef) = capturers(sym)(ref(sym))
else if (sym.exists && !sym.isStaticOwner && !levelOK(sym))
for (errMsg <- tryHeal(tp, pos))
ctx.error(em"""access to $symStr from wrong staging level:
| - the definition is at level ${levelOf.getOrElse(sym, 0)},
| - but the access is at level $level.$errMsg""", pos)
}
/** Check all named types and this-types in a given type for phase consistency. */
def checkType(pos: Position)(implicit ctx: Context): TypeAccumulator[Unit] = new TypeAccumulator[Unit] {
def apply(acc: Unit, tp: Type): Unit = reporting.trace(i"check type level $tp at $level") {
tp match {
case tp: TypeRef if tp.symbol.isSplice =>
if (inQuote) {
explicitTags += tp
outer.checkType(pos).foldOver(acc, tp)
}
else {
if (tp.isTerm) spliceOutsideQuotes(pos)
tp
}
case tp: NamedType =>
check(tp.symbol, tp, pos)
if (!tp.symbol.is(Param))
foldOver(acc, tp)
case tp: ThisType =>
check(tp.cls, tp, pos)
foldOver(acc, tp)
case _ =>
foldOver(acc, tp)
}
}
}
/** If `tree` refers to a locally defined symbol (either directly, or in a pickled type),
* check that its staging level matches the current level. References to types
* that are phase-incorrect can still be healed as follows:
*
* If `T` is a reference to a type at the wrong level, heal it by setting things up
* so that we later add a type definition
*
* type T' = ~quoted.Type[T]
*
* to the quoted text and rename T to T' in it. This is done later in `reify` via
* `addTags`. `checkLevel` itself only records what needs to be done in the
* `typeTagOfRef` field of the current `Splice` structure.
*/
private def checkLevel(tree: Tree)(implicit ctx: Context): Tree = {
tree match {
case (_: Ident) | (_: This) =>
check(tree.symbol, tree.tpe, tree.pos)
case (_: UnApply) | (_: TypeTree) =>
checkType(tree.pos).apply((), tree.tpe)
case Select(qual, OuterSelectName(_, levels)) =>
checkType(tree.pos).apply((), tree.tpe.widen)
case _: Bind =>
checkType(tree.pos).apply((), tree.symbol.info)
case _: Template =>
checkType(tree.pos).apply((), tree.symbol.owner.asClass.givenSelfType)
case _ =>
}
tree
}
/** Split `body` into a core and a list of embedded splices.
* Then if inside a splice, make a hole from these parts.
* If outside a splice, generate a call tp `scala.quoted.Unpickler.unpickleType` or
* `scala.quoted.Unpickler.unpickleExpr` that matches `tpe` with
* core and splices as arguments.
*/
private def quotation(body: Tree, quote: Tree)(implicit ctx: Context): Tree = {
val isType = quote.symbol eq defn.QuotedType_apply
if (body.symbol.isSplice) {
// simplify `'(~x)` to `x` and then transform it
val Select(splice, _) = body
transform(splice)
}
else if (level > 0) {
val body1 = nested(isQuote = true).transform(body)
// Keep quotes as trees to reduce pickled size and have a Expr.show without pickled quotes
if (isType) ref(defn.QuotedType_apply).appliedToType(body1.tpe.widen)
else ref(defn.QuotedExpr_apply).appliedToType(body1.tpe.widen).appliedTo(body1)
}
else body match {
case body: RefTree if isCaptured(body.symbol, level + 1) =>
if (isStage0Value(body.symbol)) {
// Optimization: avoid the full conversion when capturing inlined `x`
// in '{ x } to '{ x$1.toExpr.unary_~ } and go directly to `x$1.toExpr`
liftInlineParamValue(capturers(body.symbol)(body))
} else {
// Optimization: avoid the full conversion when capturing `x`
// in '{ x } to '{ x$1.unary_~ } and go directly to `x$1`
capturers(body.symbol)(body)
}
case _=>
val (body1, splices) = nested(isQuote = true).split(body)
pickledQuote(body1, splices, body.tpe, isType).withPos(quote.pos)
}
}
private def pickledQuote(body: Tree, splices: List[Tree], originalTp: Type, isType: Boolean)(implicit ctx: Context) = {
def pickleAsValue[T](value: T) =
ref(defn.Unpickler_liftedExpr).appliedToType(originalTp.widen).appliedTo(Literal(Constant(value)))
def pickleAsTasty() = {
val meth =
if (isType) ref(defn.Unpickler_unpickleType).appliedToType(originalTp)
else ref(defn.Unpickler_unpickleExpr).appliedToType(originalTp.widen)
meth.appliedTo(
liftList(PickledQuotes.pickleQuote(body).map(x => Literal(Constant(x))), defn.StringType),
liftList(splices, defn.AnyType))
}
if (splices.nonEmpty) pickleAsTasty()
else if (isType) {
def tag(tagName: String) = ref(defn.QuotedTypeModule).select(tagName.toTermName)
if (body.symbol == defn.UnitClass) tag("UnitTag")
else if (body.symbol == defn.BooleanClass) tag("BooleanTag")
else if (body.symbol == defn.ByteClass) tag("ByteTag")
else if (body.symbol == defn.CharClass) tag("CharTag")
else if (body.symbol == defn.ShortClass) tag("ShortTag")
else if (body.symbol == defn.IntClass) tag("IntTag")
else if (body.symbol == defn.LongClass) tag("LongTag")
else if (body.symbol == defn.FloatClass) tag("FloatTag")
else if (body.symbol == defn.DoubleClass) tag("DoubleTag")
else pickleAsTasty()
}
else ReifyQuotes.toValue(body) match {
case Some(value) => pickleAsValue(value)
case _ => pickleAsTasty()
}
}
/** If inside a quote, split the body of the splice into a core and a list of embedded quotes
* and make a hole from these parts. Otherwise issue an error, unless we
* are in the body of an inline method.
*/
private def splice(splice: Select)(implicit ctx: Context): Tree = {
if (level > 1) {
val body1 = nested(isQuote = false).transform(splice.qualifier)
body1.select(splice.name)
}
else if (!inQuote && level == 0) {
spliceOutsideQuotes(splice.pos)
splice
}
else {
val (body1, quotes) = nested(isQuote = false).split(splice.qualifier)
makeHole(body1, quotes, splice.tpe).withPos(splice.pos)
}
}
/** Transforms the contents of a nested splice
* Assuming
* '{
* val x = ???
* val y = ???
* { ... '{ ... x .. y ... } ... }.unary_~
* }
* then the spliced subexpression
* { ... '{ ... x ... y ... } ... }
* will be transformed to
* (args: Seq[Any]) => {
* val x$1 = args(0).asInstanceOf[Expr[Any]] // or .asInstanceOf[Type[Any]]
* val y$1 = args(1).asInstanceOf[Expr[Any]] // or .asInstanceOf[Type[Any]]
* { ... '{ ... x$1.unary_~ ... y$1.unary_~ ... } ... }
* }
*
* See: `capture`
*
* At the same time register `embedded` trees `x` and `y` to place as arguments of the hole
* placed in the original code.
* '{
* val x = ???
* val y = ???
* Hole(0 | x, y)
* }
*/
private def makeLambda(tree: Tree)(implicit ctx: Context): Tree = {
def body(arg: Tree)(implicit ctx: Context): Tree = {
var i = 0
transformWithCapturer(tree)(
(captured: mutable.Map[Symbol, Tree]) => {
(tree: Tree) => {
def newCapture = {
val tpw = tree.tpe.widen
val argTpe =
if (tree.isType) defn.QuotedTypeType.appliedTo(tpw)
else if (isStage0Value(tree.symbol)) tpw
else defn.QuotedExprType.appliedTo(tpw)
val selectArg = arg.select(nme.apply).appliedTo(Literal(Constant(i))).asInstance(argTpe)
val capturedArg = SyntheticValDef(UniqueName.fresh(tree.symbol.name.toTermName).toTermName, selectArg)
i += 1
embedded += tree
captured.put(tree.symbol, capturedArg)
capturedArg
}
ref(captured.getOrElseUpdate(tree.symbol, newCapture).symbol)
}
}
)
}
val lambdaOwner = ctx.owner.ownersIterator.find(o => levelOf.getOrElse(o, level) == level).get
val tpe = MethodType(defn.SeqType.appliedTo(defn.AnyType) :: Nil, tree.tpe.widen)
val meth = ctx.newSymbol(lambdaOwner, UniqueName.fresh(nme.ANON_FUN), Synthetic | Method, tpe)
Closure(meth, tss => body(tss.head.head)(ctx.withOwner(meth)).changeOwner(ctx.owner, meth))
}
private def transformWithCapturer(tree: Tree)(capturer: mutable.Map[Symbol, Tree] => Tree => Tree)(implicit ctx: Context): Tree = {
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_compilationTopLevelSplice)
// Force a macro to have the context in first position
forceCapture(defn.TastyTopLevelSplice_compilationTopLevelSplice)
// Force all parameters of the macro to be created in the definition order
outer.enteredSyms.reverse.foreach(forceCapture)
}
val tree2 = transform(tree)
capturers --= outer.enteredSyms
seq(captured.result().valuesIterator.toList, tree2)
}
/** Returns true if this tree will be captured by `makeLambda` */
private def isCaptured(sym: Symbol, level: Int)(implicit ctx: Context): Boolean = {
// Check phase consistency and presence of capturer
( (level == 1 && levelOf.get(sym).contains(1)) ||
(level == 0 && isStage0Value(sym))
) && capturers.contains(sym)
}
/** Transform `tree` and return the resulting tree and all `embedded` quotes
* or splices as a pair, after performing the `addTags` transform.
*/
private def split(tree: Tree)(implicit ctx: Context): (Tree, List[Tree]) = {
val tree1 = if (inQuote) addTags(transform(tree)) else makeLambda(tree)
(tree1, embedded.toList)
}
/** Register `body` as an `embedded` quote or splice
* and return a hole with `splices` as arguments and the given type `tpe`.
*/
private def makeHole(body: Tree, splices: List[Tree], tpe: Type)(implicit ctx: Context): Hole = {
val idx = embedded.length
embedded += body
Hole(idx, splices).withType(tpe).asInstanceOf[Hole]
}
override def transform(tree: Tree)(implicit ctx: Context): Tree =
reporting.trace(i"reify $tree at $level", show = true) {
def mapOverTree(lastEntered: List[Symbol]) =
try super.transform(tree)
finally
while (enteredSyms ne lastEntered) {
levelOf -= enteredSyms.head
enteredSyms = enteredSyms.tail
}
tree match {
case Quoted(quotedTree) =>
quotation(quotedTree, tree)
case tree: TypeTree if tree.tpe.typeSymbol.isSplice =>
val splicedType = tree.tpe.stripTypeVar.asInstanceOf[TypeRef].prefix.termSymbol
splice(ref(splicedType).select(tpnme.UNARY_~).withPos(tree.pos))
case tree: Select if tree.symbol.isSplice =>
splice(tree)
case tree: RefTree if isCaptured(tree.symbol, level) =>
val capturer = capturers(tree.symbol)
def captureAndSplice(t: Tree) =
splice(t.select(if (tree.isTerm) nme.UNARY_~ else tpnme.UNARY_~))
if (!isStage0Value(tree.symbol)) captureAndSplice(capturer(tree))
else if (level == 0) capturer(tree)
else captureAndSplice(liftInlineParamValue(capturer(tree)))
case Block(stats, _) =>
val last = enteredSyms
stats.foreach(markDef)
mapOverTree(last)
case Inlined(call, bindings, InlineSplice(expansion @ Select(body, name))) =>
assert(call.symbol.is(Macro))
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)
if (ctx.reporter.hasErrors) EmptyTree
else transform(cpy.Inlined(tree)(call2, bindings, spliced))
}
else super.transform(tree)
// due to value-discarding which converts an { e } into { e; () })
if (tree.tpe =:= defn.UnitType) Block(tree2 :: Nil, Literal(Constant(())))
else tree2
case _: Import =>
tree
case tree: DefDef if tree.symbol.is(Macro) && level == 0 =>
tree.rhs match {
case InlineSplice(_) =>
if (!tree.symbol.isStatic)
ctx.error("Inline 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)
case _ =>
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, tree.rhs.pos)
EmptyTree
}
case _ =>
markDef(tree)
checkLevel(mapOverTree(enteredSyms))
}
}
/** Takes a reference to an inline parameter `tree` and lifts it to an Expr */
private def liftInlineParamValue(tree: Tree)(implicit ctx: Context): Tree = {
val tpSym = tree.tpe.widenDealias.classSymbol
val lifter =
if (tpSym eq defn.BooleanClass) defn.QuotedLiftable_BooleanIsLiftable
else if (tpSym eq defn.ByteClass) defn.QuotedLiftable_ByteIsLiftable
else if (tpSym eq defn.CharClass) defn.QuotedLiftable_CharIsLiftable
else if (tpSym eq defn.ShortClass) defn.QuotedLiftable_ShortIsLiftable
else if (tpSym eq defn.IntClass) defn.QuotedLiftable_IntIsLiftable
else if (tpSym eq defn.LongClass) defn.QuotedLiftable_LongIsLiftable
else if (tpSym eq defn.FloatClass) defn.QuotedLiftable_FloatIsLiftable
else if (tpSym eq defn.DoubleClass) defn.QuotedLiftable_DoubleIsLiftable
else defn.QuotedLiftable_StringIsLiftable
ref(lifter).select("toExpr".toTermName).appliedTo(tree)
}
private def isStage0Value(sym: Symbol)(implicit ctx: Context): Boolean =
(sym.is(Inline) && sym.owner.is(Macro) && !defn.isFunctionType(sym.info)) ||
sym == defn.TastyTopLevelSplice_compilationTopLevelSplice // intrinsic value at stage 0
private def liftList(list: List[Tree], tpe: Type)(implicit ctx: Context): Tree = {
list.foldRight[Tree](ref(defn.NilModule)) { (x, acc) =>
acc.select("::".toTermName).appliedToType(tpe).appliedTo(x)
}
}
/** 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
}
}
}
}
def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = {
/** Transforms the return type of
* inline def foo(...): X = ~(...)
* to
* inline 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 {
def toValue(tree: Tree): Option[Any] = tree match {
case Literal(Constant(c)) => Some(c)
case Block(Nil, e) => toValue(e)
case Inlined(_, Nil, e) => toValue(e)
case _ => None
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy