From e4ddb5bc03da2994238992fca6a5bc1a742986fd Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 23 Mar 2018 15:35:37 +0100 Subject: [PATCH 1/5] Fix #4151: Make sure the definition of the macro is correcty formed --- .../tools/dotc/transform/ReifyQuotes.scala | 24 ++++++++++++++--- tests/neg/quote-macro-splice.scala | 27 +++++++++++++++++++ 2 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 tests/neg/quote-macro-splice.scala diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index 66b4620405bd..82e9088f35e2 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -508,10 +508,26 @@ class ReifyQuotes extends MacroTransformWithImplicits { case _: Import => tree case tree: DefDef if tree.symbol.is(Macro) && level == 0 => - markDef(tree) - nested(isQuote = true).transform(tree) - // check macro code as it if appeared in a quoted context - cpy.DefDef(tree)(rhs = EmptyTree) + tree.rhs match { + case InlineSplice(_) => + markDef(tree) + nested(isQuote = true).transform(tree) + // check macro code as it if appeared in a quoted context + cpy.DefDef(tree)(rhs = EmptyTree) + 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)) diff --git a/tests/neg/quote-macro-splice.scala b/tests/neg/quote-macro-splice.scala new file mode 100644 index 000000000000..e854a76a77f7 --- /dev/null +++ b/tests/neg/quote-macro-splice.scala @@ -0,0 +1,27 @@ +import scala.quoted._ + +object Test { + + inline def foo1: Int = { // error + println() + ~impl(1.toExpr) + } + + inline def foo2: Int = { // error + ~impl(1.toExpr) + ~impl(2.toExpr) + } + + inline def foo3: Int = { // error + val a = 1 + ~impl('(a)) + } + + inline def foo4: Int = { // error + ~impl('(1)) + 1 + } + + def impl(n: Expr[Int]): Expr[Int] = ??? + +} From 17fcc6d271e72d4dadf81d58045d6c1d30ef05d3 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 26 Mar 2018 08:43:14 +0200 Subject: [PATCH 2/5] Make sure inline macros are static methods --- .../tools/dotc/transform/ReifyQuotes.scala | 2 ++ tests/neg/quote-MacroOverride.scala | 2 +- .../quote-interpolator-core-old.scala} | 8 ++--- tests/pos/i3916/Macro_1.scala | 21 -------------- tests/pos/i3916/Test_2.scala | 5 ---- tests/pos/quote-0.scala | 29 ++++++++++--------- tests/pos/quote-assert/quoted_2.scala | 2 +- .../quote-interpolator-core/quoted_2.scala | 11 ------- 8 files changed, 23 insertions(+), 57 deletions(-) rename tests/{pos/quote-interpolator-core/quoted_1.scala => neg/quote-interpolator-core-old.scala} (76%) delete mode 100644 tests/pos/i3916/Macro_1.scala delete mode 100644 tests/pos/i3916/Test_2.scala delete mode 100644 tests/pos/quote-interpolator-core/quoted_2.scala diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index 82e9088f35e2..96d4ae3ccd69 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -510,6 +510,8 @@ class ReifyQuotes extends MacroTransformWithImplicits { 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) nested(isQuote = true).transform(tree) // check macro code as it if appeared in a quoted context diff --git a/tests/neg/quote-MacroOverride.scala b/tests/neg/quote-MacroOverride.scala index 56a14fe304c8..cfc0a7ebb374 100644 --- a/tests/neg/quote-MacroOverride.scala +++ b/tests/neg/quote-MacroOverride.scala @@ -5,7 +5,7 @@ object Test { inline def g(): Unit = () } - class B extends A { + object B extends A { inline def f() = ~('()) // error: may not override override def g() = () // error: may not override } diff --git a/tests/pos/quote-interpolator-core/quoted_1.scala b/tests/neg/quote-interpolator-core-old.scala similarity index 76% rename from tests/pos/quote-interpolator-core/quoted_1.scala rename to tests/neg/quote-interpolator-core-old.scala index 7b136b080957..43eeef3ff95f 100644 --- a/tests/pos/quote-interpolator-core/quoted_1.scala +++ b/tests/neg/quote-interpolator-core-old.scala @@ -5,9 +5,9 @@ import scala.quoted._ object FInterpolation { implicit class FInterpolatorHelper(val sc: StringContext) extends AnyVal { - inline def ff(arg1: Any): String = ~fInterpolation(sc, Seq('(arg1))) - inline def ff(arg1: Any, arg2: Any): String = ~fInterpolation(sc, Seq('(arg1), '(arg2))) - inline def ff(arg1: Any, arg2: Any, arg3: Any): String = ~fInterpolation(sc, Seq('(arg1), '(arg2), '(arg3))) + inline def ff(arg1: Any): String = ~fInterpolation(sc, Seq('(arg1))) // error: Inline macro method must be a static method + inline def ff(arg1: Any, arg2: Any): String = ~fInterpolation(sc, Seq('(arg1), '(arg2))) // error: Inline macro method must be a static method + inline def ff(arg1: Any, arg2: Any, arg3: Any): String = ~fInterpolation(sc, Seq('(arg1), '(arg2), '(arg3))) // error: Inline macro method must be a static method // ... } @@ -24,4 +24,4 @@ object FInterpolation { def hello = "hello" -} \ No newline at end of file +} diff --git a/tests/pos/i3916/Macro_1.scala b/tests/pos/i3916/Macro_1.scala deleted file mode 100644 index 3ad93b037a5a..000000000000 --- a/tests/pos/i3916/Macro_1.scala +++ /dev/null @@ -1,21 +0,0 @@ -import scala.quoted._ - -class FInterpolatorHelper(val sc: StringContext) extends AnyVal { - inline def ff(arg1: Any): String = ~FInterpolation.fInterpolation(sc, Seq('(arg1))) - inline def ff(arg1: Any, arg2: Any): String = ~FInterpolation.fInterpolation(sc, Seq('(arg1), '(arg2))) - inline def ff(arg1: Any, arg2: Any, arg3: Any): String = ~FInterpolation.fInterpolation(sc, Seq('(arg1), '(arg2), '(arg3))) - // ... -} - -object FInterpolation { - private def liftSeq(args: Seq[Expr[Any]]): Expr[Seq[Any]] = args match { - case x :: xs => '{ (~x) +: ~(liftSeq(xs)) } - case Nil => '(Seq(): Seq[Any]) - } - - def fInterpolation(sc: StringContext, args: Seq[Expr[Any]]): Expr[String] = { - val str: Expr[String] = sc.parts.mkString("").toExpr - val args1: Expr[Seq[Any]] = liftSeq(args) - '{ (~str).format(~args1: _*) } - } -} diff --git a/tests/pos/i3916/Test_2.scala b/tests/pos/i3916/Test_2.scala deleted file mode 100644 index 6da6c6997794..000000000000 --- a/tests/pos/i3916/Test_2.scala +++ /dev/null @@ -1,5 +0,0 @@ - object Test { - def main(args: Array[String]): Unit = { - println(new FInterpolatorHelper(StringContext("hello%s")).ff(5)) - } -} diff --git a/tests/pos/quote-0.scala b/tests/pos/quote-0.scala index c6b17d609e0f..ae517ec575b2 100644 --- a/tests/pos/quote-0.scala +++ b/tests/pos/quote-0.scala @@ -2,26 +2,27 @@ import scala.quoted._ import dotty.tools.dotc.quoted.Toolbox._ -class Test { - object Macros { +object Macros { - inline def assert(expr: => Boolean): Unit = - ~ assertImpl('(expr)) + inline def assert(expr: => Boolean): Unit = + ~ assertImpl('(expr)) - def assertImpl(expr: Expr[Boolean]) = - '{ if !(~expr) then throw new AssertionError(s"failed assertion: ${~showExpr(expr)}") } + def assertImpl(expr: Expr[Boolean]) = + '{ if !(~expr) then throw new AssertionError(s"failed assertion: ${~showExpr(expr)}") } - def showExpr[T](expr: Expr[T]): Expr[String] = expr.toString.toExpr + def showExpr[T](expr: Expr[T]): Expr[String] = expr.toString.toExpr - inline def power(inline n: Int, x: Double) = ~powerCode(n, '(x)) + inline def power(inline n: Int, x: Double) = ~powerCode(n, '(x)) - def powerCode(n: Int, x: Expr[Double]): Expr[Double] = - if (n == 0) '(1.0) - else if (n == 1) x - else if (n % 2 == 0) '{ { val y = ~x * ~x; ~powerCode(n / 2, '(y)) } } - else '{ ~x * ~powerCode(n - 1, x) } - } + def powerCode(n: Int, x: Expr[Double]): Expr[Double] = + if (n == 0) '(1.0) + else if (n == 1) x + else if (n % 2 == 0) '{ { val y = ~x * ~x; ~powerCode(n / 2, '(y)) } } + else '{ ~x * ~powerCode(n - 1, x) } +} + +class Test { val program = '{ import Macros._ diff --git a/tests/pos/quote-assert/quoted_2.scala b/tests/pos/quote-assert/quoted_2.scala index a6e44fcf9ffa..337892490574 100644 --- a/tests/pos/quote-assert/quoted_2.scala +++ b/tests/pos/quote-assert/quoted_2.scala @@ -2,7 +2,7 @@ import dotty.tools.dotc.quoted.Toolbox._ import scala.quoted._ import Macros._ -class Test { +object Test { inline def assert(expr: => Boolean): Unit = ~ assertImpl('(expr)) diff --git a/tests/pos/quote-interpolator-core/quoted_2.scala b/tests/pos/quote-interpolator-core/quoted_2.scala deleted file mode 100644 index 807c458f6420..000000000000 --- a/tests/pos/quote-interpolator-core/quoted_2.scala +++ /dev/null @@ -1,11 +0,0 @@ - -import FInterpolation._ - -object Test { - println(ff"integer: ${5}%d") - println(ff"string: ${"l"}%s") - println(ff"${5}%s, ${6}%d, ${"hello"}%s") - val hello = "hello" - println(ff"${5}%s, ${6}%d, ${hello}%s") - println(ff"${5}%s, ${6}%d, ${FInterpolation.hello}%s") -} \ No newline at end of file From 54a1dcdaa4b4a2cc019ee08b258cb8483a819d80 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 20 Mar 2018 15:29:49 +0100 Subject: [PATCH 3/5] Fix #3853: Compile code in the macro definition and remove interpreter The only interpreted code remaining is a call to a static method containing a lambda which can compute the resulting inlined quoted expression. Arguments to the macro are trivially computed from the inlined call and bindings. --- .../tools/dotc/interpreter/Interpreter.scala | 222 ------------------ .../tools/dotc/transform/PostTyper.scala | 4 +- .../tools/dotc/transform/ReifyQuotes.scala | 82 +++++-- .../dotty/tools/dotc/transform/Splicer.scala | 194 +++++++++++++-- .../dotty/tools/dotc/typer/RefChecks.scala | 4 +- tests/neg/quote-non-static-macro.scala | 18 ++ tests/pos/i4023/Macro_1.scala | 2 +- tests/pos/quote-nested-object/Macro_1.scala | 25 ++ tests/pos/quote-nested-object/Test_2.scala | 12 + tests/run/quote-simple-macro.check | 1 + tests/run/quote-simple-macro/quoted_1.scala | 6 + tests/run/quote-simple-macro/quoted_2.scala | 9 + 12 files changed, 320 insertions(+), 259 deletions(-) delete mode 100644 compiler/src/dotty/tools/dotc/interpreter/Interpreter.scala create mode 100644 tests/neg/quote-non-static-macro.scala create mode 100644 tests/pos/quote-nested-object/Macro_1.scala create mode 100644 tests/pos/quote-nested-object/Test_2.scala create mode 100644 tests/run/quote-simple-macro.check create mode 100644 tests/run/quote-simple-macro/quoted_1.scala create mode 100644 tests/run/quote-simple-macro/quoted_2.scala diff --git a/compiler/src/dotty/tools/dotc/interpreter/Interpreter.scala b/compiler/src/dotty/tools/dotc/interpreter/Interpreter.scala deleted file mode 100644 index ff4d5e2ecb65..000000000000 --- a/compiler/src/dotty/tools/dotc/interpreter/Interpreter.scala +++ /dev/null @@ -1,222 +0,0 @@ -package dotty.tools.dotc -package interpreter - -import java.io.{PrintWriter, StringWriter} - -import dotty.tools.dotc.ast.tpd -import dotty.tools.dotc.ast.Trees._ -import dotty.tools.dotc.core.Constants._ -import dotty.tools.dotc.core.Contexts._ -import dotty.tools.dotc.core.Decorators._ -import dotty.tools.dotc.core.Flags._ -import dotty.tools.dotc.core.Names._ -import dotty.tools.dotc.core.Symbols._ -import dotty.tools.dotc.core.quoted.Quoted -import dotty.tools.dotc.util.Positions.Position - -import scala.reflect.ClassTag -import java.net.URLClassLoader -import java.lang.reflect.Constructor -import java.lang.reflect.Method - -/** Tree interpreter that can interpret - * * Literal constants - * * Calls to static methods - * * New objects with explicit `new` keyword - * * Quoted code - * - * The interpreter assumes that all calls in the trees are to code that was - * previously compiled and is present in the classpath of the current context. - */ -class Interpreter(implicit ctx: Context) { - import tpd._ - - type Env = Map[Symbol, Object] - - private[this] val classLoader = { - val urls = ctx.settings.classpath.value.split(':').map(cp => java.nio.file.Paths.get(cp).toUri.toURL) - new URLClassLoader(urls, getClass.getClassLoader) - } - - /** Returns the interpreted result of interpreting the code represented by the tree. - * Return Some of the result or None if some error happen during the interpretation. - */ - def interpretTree[T](tree: Tree)(implicit ct: ClassTag[T]): Option[T] = { - try { - interpretTreeImpl(tree, Map.empty) match { - case obj: T => Some(obj) - case obj => - // TODO upgrade to a full type tag check or something similar - ctx.error(s"Interpreted tree returned a result of an unexpected type. Expected ${ct.runtimeClass} but was ${obj.getClass}", tree.pos) - None - } - } catch { - case ex: StopInterpretation => - ctx.error(ex.msg, ex.pos) - None - } - } - - /** Returns the interpreted result of interpreting the code represented by the tree. - * Returns the result of the interpreted tree. - * - * If some error is encountered while interpreting a ctx.error is emitted and a StopInterpretation is thrown. - */ - private def interpretTreeImpl(tree: Tree, env: Env): Object = { - // println(s"Interpreting:\n${tree.show}\n$env\n") - - implicit val pos: Position = tree.pos - - tree match { - case Quoted(quotedTree) => - if (quotedTree.isTerm) new scala.quoted.Exprs.TreeExpr(quotedTree) - else new scala.quoted.Types.TreeType(quotedTree) - - case Literal(Constant(c)) => c.asInstanceOf[Object] - - case Apply(fn, args) if fn.symbol.isConstructor => - val clazz = loadClass(fn.symbol.owner.symbol.fullName) - val paramClasses = paramsSig(fn.symbol) - val interpretedArgs = args.map(arg => interpretTreeImpl(arg, env)) - val constructor = getConstructor(clazz, paramClasses) - stopIfRuntimeException(constructor.newInstance(interpretedArgs: _*)) - - case _: RefTree if tree.symbol.isStatic => - val clazz = loadClass(tree.symbol.owner.companionModule.fullName) - val method = getMethod(clazz, tree.symbol.name, Nil) - stopIfRuntimeException(method.invoke(null)) - - case tree: Apply => - val evaluatedPrefix = if (tree.symbol.isStatic) null else interpretPrefix(tree, env) - val clazz = - if (tree.symbol.isStatic) loadClass(tree.symbol.owner.companionModule.fullName) - else evaluatedPrefix.getClass - val paramClasses = paramsSig(tree.symbol) - val interpretedArgs = interpretArgs(tree, env) - val method = getMethod(clazz, tree.symbol.name, paramClasses) - stopIfRuntimeException(method.invoke(evaluatedPrefix, interpretedArgs: _*)) - - case tree: Ident if env.contains(tree.symbol) => - env(tree.symbol) - - case Block(stats, expr) => - val env2 = stats.foldLeft(env)((acc, x) => interpretStat(x, acc)) - interpretTreeImpl(expr, env2) - - case tree: NamedArg => - interpretTreeImpl(tree.arg, env) - - case Inlined(_, bindings, expansion) => - val env2 = bindings.foldLeft(env)((acc, x) => interpretStat(x, acc)) - interpretTreeImpl(expansion, env2) - - case TypeApply(fn, _) => - interpretTreeImpl(fn, env) - - case Typed(expr, _) => - interpretTreeImpl(expr, env) - - case Select(qualifier, name) - if tree.symbol.owner.isValueClass && tree.symbol.is(ParamAccessor) && env.contains(qualifier.symbol) => - val value = env(qualifier.symbol) - val clazz = value.getClass - if (clazz.getCanonicalName != tree.symbol.owner.showFullName) value // Already unboxed - else { - val method = getMethod(clazz, name, Nil) - stopIfRuntimeException(method.invoke(value)) - } - - case SeqLiteral(elems, _) => - elems.map(elem => interpretTreeImpl(elem, env)) - - case _ => - // TODO Add more precise descriptions of why it could not be interpreted. - // This should be done after the full interpreter is implemented. - throw new StopInterpretation(s"Could not interpret ${tree.show}. Consider extracting logic into a helper def.", tree.pos) - } - } - - private def interpretArgs(tree: Tree, env: Env): Seq[Object] = { - val b = Seq.newBuilder[Object] - def interpretArgs(tree: Tree): Unit = tree match { - case Apply(fn, args) => - interpretArgs(fn) - args.foreach(arg => b += interpretTreeImpl(arg, env)) - case _ => - } - interpretArgs(tree) - b.result() - } - - private def interpretPrefix(tree: Tree, env: Env): Object = tree match { - case Apply(qual, _) => interpretPrefix(qual, env) - case TypeApply(qual, _) => interpretPrefix(qual, env) - case Select(qual, _) => interpretTreeImpl(qual, env) - } - - /** Interprets the statement and returns the updated environment */ - private def interpretStat(stat: Tree, env: Env): Env = stat match { - case tree: ValDef => - val obj = interpretTreeImpl(tree.rhs, env) - env.updated(tree.symbol, obj) - - case _ => - interpretTreeImpl(stat, env) - env - } - - private def loadClass(name: Name)(implicit pos: Position): Class[_] = { - try classLoader.loadClass(name.toString) - catch { - case _: ClassNotFoundException => - val msg = s"Could not find interpreted class $name in classpath" - throw new StopInterpretation(msg, pos) - } - } - - private def getMethod(clazz: Class[_], name: Name, paramClasses: List[Class[_]])(implicit pos: Position): Method = { - try clazz.getMethod(name.toString, paramClasses: _*) - catch { - case _: NoSuchMethodException => - val msg = s"Could not find interpreted method ${clazz.getCanonicalName}.$name with parameters $paramClasses" - throw new StopInterpretation(msg, pos) - } - } - - private def getConstructor(clazz: Class[_], paramClasses: List[Class[_]])(implicit pos: Position): Constructor[Object] = { - try clazz.getConstructor(paramClasses: _*).asInstanceOf[Constructor[Object]] - catch { - case _: NoSuchMethodException => - val msg = s"Could not find interpreted constructor of ${clazz.getCanonicalName} with parameters $paramClasses" - throw new StopInterpretation(msg, pos) - } - } - - private def stopIfRuntimeException[T](thunk: => T)(implicit pos: Position): T = { - try thunk - catch { - case ex: RuntimeException => - val sw = new StringWriter() - sw.write("A runtime exception occurred while interpreting\n") - sw.write(ex.getMessage) - sw.write("\n") - ex.printStackTrace(new PrintWriter(sw)) - sw.write("\n") - throw new StopInterpretation(sw.toString, pos) - } - } - - /** List of classes of the parameters of the signature of `sym` */ - private def paramsSig(sym: Symbol): List[Class[_]] = { - sym.signature.paramsSig.map { param => - defn.valueTypeNameToJavaType(param) match { - case Some(clazz) => clazz - case None => classLoader.loadClass(param.toString) - } - } - } - - /** Exception that stops interpretation if some issue is found */ - private class StopInterpretation(val msg: String, val pos: Position) extends Exception - -} diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 2371bd3e4c70..c5d521d304bd 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -231,7 +231,9 @@ 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). - val callTrace = Ident(call.symbol.topLevelClass.typeRef).withPos(call.pos) + val callTrace = + if (call.symbol.is(Macro)) call + else Ident(call.symbol.topLevelClass.typeRef).withPos(call.pos) cpy.Inlined(tree)(callTrace, transformSub(bindings), transform(expansion)) case tree: Template => withNoCheckNews(tree.parents.flatMap(newPart)) { diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index 96d4ae3ccd69..312ea0d2a4bb 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -13,6 +13,7 @@ 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 @@ -56,8 +57,32 @@ import dotty.tools.dotc.core.quoted._ * ) * ``` * 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]] + * ... + * { ... T1$1.unary_~ ... x ... '(y1$1.unary_~) ... } + * } + * ``` + * 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 { +class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer { import ast.tpd._ override def phaseName: String = "reifyQuotes" @@ -408,13 +433,15 @@ class ReifyQuotes extends MacroTransformWithImplicits { var i = 0 transformWithCapturer(tree)( (captured: mutable.Map[Symbol, Tree]) => { - (tree: RefTree) => { + (tree: Tree) => { def newCapture = { + val tpw = tree.tpe.widen val argTpe = - if (tree.isTerm) defn.QuotedExprType.appliedTo(tree.tpe.widen) - else defn.QuotedTypeType.appliedTo(defn.AnyType) + if (tree.isType) defn.QuotedTypeType.appliedTo(tpw) + else if (tree.symbol.is(Inline)) tpw // inlined term + else defn.QuotedExprType.appliedTo(tpw) val selectArg = arg.select(nme.apply).appliedTo(Literal(Constant(i))).asInstance(argTpe) - val capturedArg = SyntheticValDef(UniqueName.fresh(tree.name.toTermName).toTermName, selectArg) + val capturedArg = SyntheticValDef(UniqueName.fresh(tree.symbol.name.toTermName).toTermName, selectArg) i += 1 embedded += tree captured.put(tree.symbol, capturedArg) @@ -432,11 +459,12 @@ class ReifyQuotes extends MacroTransformWithImplicits { Closure(meth, tss => body(tss.head.head)(ctx.withOwner(meth)).changeOwner(ctx.owner, meth)) } - private def transformWithCapturer(tree: Tree)( - capturer: mutable.Map[Symbol, Tree] => RefTree => Tree)(implicit ctx: Context): Tree = { + 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) outer.enteredSyms.foreach(s => capturers.put(s, captured2)) + if (ctx.owner.owner.is(Macro)) + outer.enteredSyms.reverse.foreach(s => captured2(ref(s))) val tree2 = transform(tree) capturers --= outer.enteredSyms seq(captured.result().valuesIterator.toList, tree2) @@ -445,8 +473,9 @@ class ReifyQuotes extends MacroTransformWithImplicits { /** Returns true if this tree will be captured by `makeLambda` */ private def isCaptured(tree: RefTree, level: Int)(implicit ctx: Context): Boolean = { // Check phase consistency and presence of capturer - level == 1 && !tree.symbol.is(Inline) && levelOf.get(tree.symbol).contains(1) && - capturers.contains(tree.symbol) + ( (level == 1 && levelOf.get(tree.symbol).contains(1)) || + (level == 0 && tree.symbol.is(Inline)) + ) && capturers.contains(tree.symbol) } /** Transform `tree` and return the resulting tree and all `embedded` quotes @@ -485,7 +514,8 @@ class ReifyQuotes extends MacroTransformWithImplicits { splice(tree) case tree: RefTree if isCaptured(tree, level) => val capturer = capturers(tree.symbol) - splice(capturer(tree).select(if (tree.isTerm) nme.UNARY_~ else tpnme.UNARY_~)) + if (tree.symbol.is(Inline)) capturer(tree) + else splice(capturer(tree).select(if (tree.isTerm) nme.UNARY_~ else tpnme.UNARY_~)) case Block(stats, _) => val last = enteredSyms stats.foreach(markDef) @@ -498,7 +528,7 @@ class ReifyQuotes extends MacroTransformWithImplicits { } val tree1 = - if (level == 0) cpy.Inlined(tree)(call, stagedBindings, Splicer.splice(seq(splicedBindings, body).withPos(tree.pos))) + if (level == 0) cpy.Inlined(tree)(call, stagedBindings, Splicer.splice(body, call, splicedBindings, tree.pos).withPos(tree.pos)) else seq(stagedBindings, cpy.Select(expansion)(cpy.Inlined(tree)(call, splicedBindings, body), name)) val tree2 = transform(tree1) @@ -513,9 +543,12 @@ class ReifyQuotes extends MacroTransformWithImplicits { if (!tree.symbol.isStatic) ctx.error("Inline macro method must be a static method.", tree.pos) markDef(tree) - nested(isQuote = true).transform(tree) - // check macro code as it if appeared in a quoted context - cpy.DefDef(tree)(rhs = EmptyTree) + 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. @@ -556,6 +589,27 @@ class ReifyQuotes extends MacroTransformWithImplicits { } } } + + 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 = 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 { diff --git a/compiler/src/dotty/tools/dotc/transform/Splicer.scala b/compiler/src/dotty/tools/dotc/transform/Splicer.scala index 2f6e2d935df6..7eef2edcdeed 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicer.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicer.scala @@ -1,15 +1,24 @@ package dotty.tools.dotc package transform +import java.io.{PrintWriter, StringWriter} +import java.lang.reflect.Method +import java.net.URLClassLoader + import dotty.tools.dotc.ast.tpd import dotty.tools.dotc.core.Contexts._ import dotty.tools.dotc.core.Decorators._ +import dotty.tools.dotc.core.Flags.Package +import dotty.tools.dotc.core.NameKinds.FlatName +import dotty.tools.dotc.core.Names.Name import dotty.tools.dotc.core.quoted._ -import dotty.tools.dotc.interpreter._ +import dotty.tools.dotc.core.Types._ +import dotty.tools.dotc.core.Symbols._ import scala.util.control.NonFatal +import dotty.tools.dotc.util.Positions.Position -import java.lang.reflect.InvocationTargetException +import scala.reflect.ClassTag /** Utility class to splice quoted expressions */ object Splicer { @@ -18,31 +27,176 @@ object Splicer { /** Splice the Tree for a Quoted expression. `~'(xyz)` becomes `xyz` * and for `~xyz` the tree of `xyz` is interpreted for which the * resulting expression is returned as a `Tree` + * + * See: `ReifyQuotes` */ - def splice(tree: Tree)(implicit ctx: Context): Tree = tree match { + def splice(tree: Tree, call: Tree, bindings: List[Tree], pos: Position)(implicit ctx: Context): Tree = tree match { case Quoted(quotedTree) => quotedTree - case _ => reflectiveSplice(tree) + case _ => reflectiveSplice(tree, call, bindings, pos) + } + + private def reflectiveSplice(tree: Tree, call: Tree, bindings: List[Tree], pos: Position)(implicit ctx: Context): Tree = { + val liftedArgs = getLiftedArgs(call, bindings) + val interpreter = new Interpreter(pos) + val interpreted = interpreter.interpretCallToSymbol[Seq[Any] => Object](call.symbol) + interpreted.flatMap(lambda => evaluateLambda(lambda, liftedArgs, pos)).fold(tree)(PickledQuotes.quotedExprToTree) } - /** Splice the Tree for a Quoted expression which is constructed via a reflective call to the given method */ - private def reflectiveSplice(tree: Tree)(implicit ctx: Context): Tree = { - val interpreter = new Interpreter - val interpreted = - try interpreter.interpretTree[scala.quoted.Expr[_]](tree) - catch { case ex: InvocationTargetException => handleTargetException(tree, ex); None } - interpreted.fold(tree)(PickledQuotes.quotedExprToTree) + /** Given the inline code and bindings, compute the lifted arguments that will be used to execute the macro + * - Type parameters are lifted to quoted.Types.TreeType + * - Inline parameters are listed as their value + * - Other parameters are lifted to quoted.Types.TreeExpr (may reference a binding) + */ + private def getLiftedArgs(call: Tree, bindings: List[Tree])(implicit ctx: Context): List[Any] = { + val bindMap = bindings.map { + case vdef: ValDef => (vdef.rhs, ref(vdef.symbol)) + }.toMap + def allArgs(call: Tree, acc: List[List[Tree]]): List[List[Tree]] = call match { + case call: Apply => allArgs(call.fun, call.args :: acc) + case call: TypeApply => allArgs(call.fun, call.args :: acc) + case _ => acc + } + def liftArgs(tpe: Type, args: List[List[Tree]]): List[Any] = tpe match { + case tp: MethodType => + val args1 = args.head.zip(tp.paramInfos).map { + case (arg: Literal, tp) if tp.hasAnnotation(defn.InlineParamAnnot) => arg.const.value + case (arg, tp) => + assert(!tp.hasAnnotation(defn.InlineParamAnnot)) + // Replace argument by its binding + new scala.quoted.Exprs.TreeExpr(bindMap.getOrElse(arg, arg)) + } + args1 ::: liftArgs(tp.resType, args.tail) + case tp: PolyType => + val args1 = args.head.map(tp => new scala.quoted.Types.TreeType(tp)) + args1 ::: liftArgs(tp.resType, args.tail) + case _ => Nil + } + + liftArgs(call.symbol.info, allArgs(call, Nil)) } - private def handleTargetException(tree: Tree, ex: InvocationTargetException)(implicit ctx: Context): Unit = ex.getCause match { - case ex: scala.quoted.QuoteError => ctx.error(ex.getMessage, tree.pos) - case NonFatal(ex) => - val msg = - s"""Failed to evaluate inlined quote. - | Caused by: ${ex.getMessage} - | ${ex.getStackTrace.takeWhile(_.getClassName != "sun.reflect.NativeMethodAccessorImpl").mkString("\n ")} + private def evaluateLambda(lambda: Seq[Any] => Object, args: Seq[Any], pos: Position)(implicit ctx: Context): Option[scala.quoted.Expr[_]] = { + try Some(lambda(args).asInstanceOf[scala.quoted.Expr[_]]) + catch { + case ex: scala.quoted.QuoteError => + ctx.error(ex.getMessage, pos) + None + case NonFatal(ex) => + val msg = + s"""Failed to evaluate inlined quote. + | Caused by: ${ex.getMessage} + | ${ex.getStackTrace.takeWhile(_.getClassName != "dotty.tools.dotc.transform.Splicer$").init.mkString("\n ")} """.stripMargin - ctx.error(msg, tree.pos) - case _ => throw ex + ctx.error(msg, pos) + None + case ex: Throwable => throw ex + } + } + + /** Tree interpreter that can interpret calls to static methods with it's default arguments + * + * The interpreter assumes that all calls in the trees are to code that was + * previously compiled and is present in the classpath of the current context. + */ + private class Interpreter(pos: Position)(implicit ctx: Context) { + + private[this] val classLoader = { + val urls = ctx.settings.classpath.value.split(':').map(cp => java.nio.file.Paths.get(cp).toUri.toURL) + new URLClassLoader(urls, getClass.getClassLoader) + } + + /** Returns the interpreted result of interpreting the code a call to the symbol with default arguments. + * Return Some of the result or None if some error happen during the interpretation. + */ + def interpretCallToSymbol[T](sym: Symbol)(implicit ct: ClassTag[T]): Option[T] = { + try { + val (clazz, instance) = loadModule(sym.owner) + val paramClasses = paramsSig(sym) + val interpretedArgs = paramClasses.map(defaultValue) + val method = getMethod(clazz, sym.name, paramClasses) + stopIfRuntimeException(method.invoke(instance, interpretedArgs: _*)) match { + case obj: T => Some(obj) + case obj => + // TODO upgrade to a full type tag check or something similar + ctx.error(s"Interpreted tree returned a result of an unexpected type. Expected ${ct.runtimeClass} but was ${obj.getClass}", pos) + None + } + } catch { + case ex: StopInterpretation => + ctx.error(ex.msg, ex.pos) + None + } + } + + private def loadModule(sym: Symbol): (Class[_], Object) = { + if (sym.owner.is(Package)) { + // is top level object + (loadClass(sym.companionModule.fullName), null) + } else { + // nested object in an object + val clazz = loadClass(sym.fullNameSeparated(FlatName)) + (clazz, clazz.newInstance().asInstanceOf[Object]) + } + } + + private def loadClass(name: Name): Class[_] = { + try classLoader.loadClass(name.toString) + catch { + case _: ClassNotFoundException => + val msg = s"Could not find interpreted class $name in classpath" + throw new StopInterpretation(msg, pos) + } + } + + private def getMethod(clazz: Class[_], name: Name, paramClasses: List[Class[_]]): Method = { + try clazz.getMethod(name.toString, paramClasses: _*) + catch { + case _: NoSuchMethodException => + val msg = s"Could not find interpreted method ${clazz.getCanonicalName}.$name with parameters $paramClasses" + throw new StopInterpretation(msg, pos) + } + } + + private def stopIfRuntimeException[T](thunk: => T): T = { + try thunk + catch { + case ex: RuntimeException => + val sw = new StringWriter() + sw.write("A runtime exception occurred while interpreting\n") + sw.write(ex.getMessage) + sw.write("\n") + ex.printStackTrace(new PrintWriter(sw)) + sw.write("\n") + throw new StopInterpretation(sw.toString, pos) + } + } + + /** List of classes of the parameters of the signature of `sym` */ + private def paramsSig(sym: Symbol): List[Class[_]] = { + sym.signature.paramsSig.map { param => + defn.valueTypeNameToJavaType(param) match { + case Some(clazz) => clazz + case None => classLoader.loadClass(param.toString) + } + } + } + + /** Get the default value for the given class */ + private def defaultValue(clazz: Class[_]): Object = { + if (clazz == classOf[Boolean]) false.asInstanceOf[Object] + else if (clazz == classOf[Byte]) 0.toByte.asInstanceOf[Object] + else if (clazz == classOf[Char]) 0.toChar.asInstanceOf[Object] + else if (clazz == classOf[Short]) 0.asInstanceOf[Object] + else if (clazz == classOf[Int]) 0.asInstanceOf[Object] + else if (clazz == classOf[Long]) 0L.asInstanceOf[Object] + else if (clazz == classOf[Float]) 0f.asInstanceOf[Object] + else if (clazz == classOf[Double]) 0d.asInstanceOf[Object] + else null + } + + /** Exception that stops interpretation if some issue is found */ + private class StopInterpretation(val msg: String, val pos: Position) extends Exception + } } diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 2d0ae6dd29f8..518ab5dbbab5 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -947,7 +947,9 @@ class RefChecks extends MiniPhase { thisPhase => override def transformDefDef(tree: DefDef)(implicit ctx: Context) = { checkDeprecatedOvers(tree) - if (tree.symbol is Macro) EmptyTree else tree + if (tree.symbol.is(Macro)) + tree.symbol.resetFlag(Macro) + tree } override def transformTemplate(tree: Template)(implicit ctx: Context) = try { diff --git a/tests/neg/quote-non-static-macro.scala b/tests/neg/quote-non-static-macro.scala new file mode 100644 index 000000000000..bf90545bb3b4 --- /dev/null +++ b/tests/neg/quote-non-static-macro.scala @@ -0,0 +1,18 @@ +import scala.quoted._ + +class Foo { + inline def foo: Unit = ~Foo.impl // error + object Bar { + inline def foo: Unit = ~Foo.impl // error + } +} + +object Foo { + class Baz { + inline def foo: Unit = ~impl // error + } + object Quox { + inline def foo: Unit = ~Foo.impl + } + def impl: Expr[Unit] = '() +} diff --git a/tests/pos/i4023/Macro_1.scala b/tests/pos/i4023/Macro_1.scala index c3c0d2100e14..b51c1de87b2e 100644 --- a/tests/pos/i4023/Macro_1.scala +++ b/tests/pos/i4023/Macro_1.scala @@ -2,4 +2,4 @@ import scala.quoted._ object Macro { inline def ff[T: Type](x: T): T = ~impl('(x)) def impl[T](x: Expr[T]): Expr[T] = x -} \ No newline at end of file +} diff --git a/tests/pos/quote-nested-object/Macro_1.scala b/tests/pos/quote-nested-object/Macro_1.scala new file mode 100644 index 000000000000..a24a6dd3f376 --- /dev/null +++ b/tests/pos/quote-nested-object/Macro_1.scala @@ -0,0 +1,25 @@ + +import scala.quoted._ + +object Macro { + + + object Implementation { + + inline def plus(inline n: Int, m: Int): Int = ~plus(n, '(m)) + + def plus(n: Int, m: Expr[Int]): Expr[Int] = + if (n == 0) m + else '{ ~n.toExpr + ~m } + + object Implementation2 { + + inline def plus(inline n: Int, m: Int): Int = ~plus(n, '(m)) + + def plus(n: Int, m: Expr[Int]): Expr[Int] = + if (n == 0) m + else '{ ~n.toExpr + ~m } + } + } + +} diff --git a/tests/pos/quote-nested-object/Test_2.scala b/tests/pos/quote-nested-object/Test_2.scala new file mode 100644 index 000000000000..bcc073c6061a --- /dev/null +++ b/tests/pos/quote-nested-object/Test_2.scala @@ -0,0 +1,12 @@ +object PowerInlined1 { + import Macro.Implementation._ + + plus(0, 2) + plus(1, 3) + + Macro.Implementation.plus(0, 2) + Macro.Implementation.plus(1, 3) + + Macro.Implementation.Implementation2.plus(0, 2) + Macro.Implementation.Implementation2.plus(1, 3) +} diff --git a/tests/run/quote-simple-macro.check b/tests/run/quote-simple-macro.check new file mode 100644 index 000000000000..b8626c4cff28 --- /dev/null +++ b/tests/run/quote-simple-macro.check @@ -0,0 +1 @@ +4 diff --git a/tests/run/quote-simple-macro/quoted_1.scala b/tests/run/quote-simple-macro/quoted_1.scala new file mode 100644 index 000000000000..165889e278ee --- /dev/null +++ b/tests/run/quote-simple-macro/quoted_1.scala @@ -0,0 +1,6 @@ +import scala.quoted._ + +object Macros { + inline def foo(inline i: Int, dummy: Int, j: Int): Int = ~bar(i + 1, '(j)) + def bar(x: Int, y: Expr[Int]): Expr[Int] = '{ ~x.toExpr + ~y } +} diff --git a/tests/run/quote-simple-macro/quoted_2.scala b/tests/run/quote-simple-macro/quoted_2.scala new file mode 100644 index 000000000000..f05e44fed20a --- /dev/null +++ b/tests/run/quote-simple-macro/quoted_2.scala @@ -0,0 +1,9 @@ +import scala.quoted._ +import Macros._ + +object Test { + def main(args: Array[String]): Unit = { + def x = 2 + println(foo(1, 2, x)) + } +} From da8bbea46d59563a7f2db65836113186f9c60b34 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 28 Mar 2018 09:34:38 +0200 Subject: [PATCH 4/5] Add comment on Inlined call simplification Apply the simplification later in ReifyQuotes when call has been processed. --- compiler/src/dotty/tools/dotc/transform/PostTyper.scala | 3 +++ compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala | 8 ++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index c5d521d304bd..ce500a4865a6 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -231,6 +231,9 @@ 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) diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index 312ea0d2a4bb..5be1a48cf74f 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -526,9 +526,13 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer { case vdef: ValDef => vdef.symbol.is(Synthetic) // Assume that only _this bindings are tagged with Synthetic case _ => false } - + // 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 = + if (level == 0) Ident(call.symbol.topLevelClass.typeRef).withPos(call.pos) + else call val tree1 = - if (level == 0) cpy.Inlined(tree)(call, stagedBindings, Splicer.splice(body, call, splicedBindings, tree.pos).withPos(tree.pos)) + if (level == 0) cpy.Inlined(tree)(call2, stagedBindings, Splicer.splice(body, call, splicedBindings, tree.pos).withPos(tree.pos)) else seq(stagedBindings, cpy.Select(expansion)(cpy.Inlined(tree)(call, splicedBindings, body), name)) val tree2 = transform(tree1) From ec164ffbe040dd6d2c0f7328be1af0c1940e3457 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 28 Mar 2018 10:26:08 +0200 Subject: [PATCH 5/5] Do not change stage of bindings This was performed to allow some inlined `this` bindings to be evaluated. It was not always possible and hence inconsistent. --- .../tools/dotc/transform/ReifyQuotes.scala | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index 5be1a48cf74f..a4844b9d3ea7 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -521,20 +521,16 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer { stats.foreach(markDef) mapOverTree(last) case Inlined(call, bindings, InlineSplice(expansion @ Select(body, name))) => - // To maintain phase consistency, we move the binding of the this parameter into the spliced code - val (splicedBindings, stagedBindings) = bindings.partition { - case vdef: ValDef => vdef.symbol.is(Synthetic) // Assume that only _this bindings are tagged with Synthetic - case _ => false - } - // 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 = - if (level == 0) Ident(call.symbol.topLevelClass.typeRef).withPos(call.pos) - else call - val tree1 = - if (level == 0) cpy.Inlined(tree)(call2, stagedBindings, Splicer.splice(body, call, splicedBindings, tree.pos).withPos(tree.pos)) - else seq(stagedBindings, cpy.Select(expansion)(cpy.Inlined(tree)(call, splicedBindings, body), name)) - val tree2 = transform(tree1) + 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).withPos(tree.pos) + 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(())))