diff --git a/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala b/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala index 527c15414365..98096d13d86b 100644 --- a/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala +++ b/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala @@ -131,6 +131,9 @@ object PickledQuotes { val x1 = SyntheticValDef(NameKinds.UniqueName.fresh("x".toTermName), x) def x1Ref() = ref(x1.symbol) def rec(f: Tree): Tree = f match { + case Inlined(call, bindings, expansion) => + // this case must go before closureDef to avoid dropping the inline node + cpy.Inlined(f)(call, bindings, rec(expansion)) case closureDef(ddef) => val paramSym = ddef.vparamss.head.head.symbol new TreeTypeMap( @@ -140,8 +143,6 @@ object PickledQuotes { ).transform(ddef.rhs) case Block(stats, expr) => seq(stats, rec(expr)) - case Inlined(call, bindings, expansion) => - Inlined(call, bindings, rec(expansion)) case _ => f.select(nme.apply).appliedTo(x1Ref()) } diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 355bf11bc3d1..58d1e893252c 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -4,7 +4,9 @@ package transform import dotty.tools.dotc.ast.{Trees, tpd, untpd} import scala.collection.mutable import core._ -import typer.{Checking, VarianceChecker} +import dotty.tools.dotc.typer.Checking +import dotty.tools.dotc.typer.Inliner +import dotty.tools.dotc.typer.VarianceChecker import Types._, Contexts._, Names._, Flags._, DenotTransformers._, Phases._ import SymDenotations._, StdNames._, Annotations._, Trees._, Scopes._ import Decorators._ @@ -237,17 +239,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase super.transform(tree1) } case Inlined(call, bindings, expansion) if !call.isEmpty => - // Leave only a call trace consisting of - // - a reference to the top-level class from which the call was inlined, - // - the call's position - // in the call field of an Inlined node. - // The trace has enough info to completely reconstruct positions. - // The minimization is done for two reasons: - // 1. To save space (calls might contain large inline arguments, which would otherwise - // 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 = Inliner.inlineCallTrace(call.symbol, call.pos) cpy.Inlined(tree)(callTrace, transformSub(bindings), transform(expansion)(inlineContext(call))) case tree: Template => withNoCheckNews(tree.parents.flatMap(newPart)) { diff --git a/compiler/src/dotty/tools/dotc/transform/Splicer.scala b/compiler/src/dotty/tools/dotc/transform/Splicer.scala index e1cc8fa34062..2c9e4ed7a788 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicer.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicer.scala @@ -41,8 +41,8 @@ object Splicer { val interpreter = new Interpreter(pos, classLoader) try { // Some parts of the macro are evaluated during the unpickling performed in quotedExprToTree - val interpreted = interpreter.interpret[scala.quoted.Expr[Any]](tree) - interpreted.fold(tree)(x => PickledQuotes.quotedExprToTree(x)) + val interpretedExpr = interpreter.interpret[scala.quoted.Expr[Any]](tree) + interpretedExpr.fold(tree)(x => PickledQuotes.quotedExprToTree(x)) } catch { case ex: scala.quoted.QuoteError => diff --git a/compiler/src/dotty/tools/dotc/transform/Staging.scala b/compiler/src/dotty/tools/dotc/transform/Staging.scala index 0d027fb135df..6a04d9bc01fa 100644 --- a/compiler/src/dotty/tools/dotc/transform/Staging.scala +++ b/compiler/src/dotty/tools/dotc/transform/Staging.scala @@ -15,6 +15,7 @@ import typer.Implicits.SearchFailureType import scala.collection.mutable import dotty.tools.dotc.core.StdNames._ import dotty.tools.dotc.core.quoted._ +import dotty.tools.dotc.typer.Inliner import dotty.tools.dotc.util.SourcePosition @@ -381,7 +382,12 @@ class Staging extends MacroTransformWithImplicits { capturers(body.symbol)(body) case _=> val (body1, splices) = nested(isQuote = true).split(body) - if (level == 0 && !ctx.inInlineMethod) pickledQuote(body1, splices, body.tpe, isType).withPos(quote.pos) + if (level == 0 && !ctx.inInlineMethod) { + val body2 = + if (body1.isType) body1 + else Inlined(Inliner.inlineCallTrace(ctx.owner, quote.pos), Nil, body1) + pickledQuote(body2, splices, body.tpe, isType).withPos(quote.pos) + } else { // In top-level splice in an inline def. Keep the tree as it is, it will be transformed at inline site. body @@ -432,7 +438,13 @@ class Staging extends MacroTransformWithImplicits { else if (level == 1) { val (body1, quotes) = nested(isQuote = false).split(splice.qualifier) val tpe = outer.embedded.getHoleType(splice) - makeHole(body1, quotes, tpe).withPos(splice.pos) + val hole = makeHole(body1, quotes, tpe).withPos(splice.pos) + // We do not place add the inline marker for trees that where lifted as they come from the same file as their + // enclosing quote. Any intemediate splice will add it's own Inlined node and cancel it before splicig the lifted tree. + // Note that lifted trees are not necessarily expressions and that Inlined nodes are expected to be expressions. + // For example we can have a lifted tree containing the LHS of an assignment (see tests/run-with-compiler/quote-var.scala). + if (splice.isType || outer.embedded.isLiftedSymbol(splice.qualifier.symbol)) hole + else Inlined(EmptyTree, Nil, hole) } else if (enclosingInlineds.nonEmpty) { // level 0 in an inlined call val spliceCtx = ctx.outer // drop the last `inlineContext` @@ -664,7 +676,12 @@ object Staging { map.get(splice.qualifier.symbol).map(_.tpe.widen).getOrElse(splice.tpe) } + def isLiftedSymbol(sym: Symbol)(implicit ctx: Context): Boolean = map.contains(sym) + /** Get the list of embedded trees */ def getTrees: List[tpd.Tree] = trees.toList + + override def toString: String = s"Embedded($trees, $map)" + } } diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 1aef548cd2ae..a2a98ea942c2 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -149,6 +149,15 @@ object Inliner { (new Reposition).transformInline(inlined) } } + + /** Leave only a call trace consisting of + * - a reference to the top-level class from which the call was inlined, + * - the call's position + * in the call field of an Inlined node. + * The trace has enough info to completely reconstruct positions. + */ + def inlineCallTrace(callSym: Symbol, pos: Position)(implicit ctx: Context): Tree = + Ident(callSym.topLevelClass.typeRef).withPos(pos) } /** Produces an inlined version of `call` via its `inlined` method. diff --git a/tests/run-with-compiler/quote-fun-app-1.check b/tests/run-with-compiler/quote-fun-app-1.check new file mode 100644 index 000000000000..6fe5c7f6a8f8 --- /dev/null +++ b/tests/run-with-compiler/quote-fun-app-1.check @@ -0,0 +1,2 @@ +42 +43 diff --git a/tests/run-with-compiler/quote-fun-app-1.scala b/tests/run-with-compiler/quote-fun-app-1.scala new file mode 100644 index 000000000000..cd61c6d95b65 --- /dev/null +++ b/tests/run-with-compiler/quote-fun-app-1.scala @@ -0,0 +1,17 @@ +import scala.quoted._ + +import scala.quoted.Toolbox.Default._ + +object Test { + + def main(args: Array[String]): Unit = { + val f = f1.run + println(f(42)) + println(f(43)) + } + + def f1: Expr[Int => Int] = '{ n => ~f2('(n)) } + def f2: Expr[Int => Int] = '{ n => ~f3('(n)) } + def f3: Expr[Int => Int] = '{ n => ~f4('(n)) } + def f4: Expr[Int => Int] = '{ n => n } +} diff --git a/tests/run-with-compiler/quote-unrolled-foreach.check b/tests/run-with-compiler/quote-unrolled-foreach.check index 6b892dd442bf..a97a6f1d93fe 100644 --- a/tests/run-with-compiler/quote-unrolled-foreach.check +++ b/tests/run-with-compiler/quote-unrolled-foreach.check @@ -33,7 +33,6 @@ var i: scala.Int = 0 while (i.<(size)) { val element: scala.Int = arr.apply(i) - ((i: scala.Int) => java.lang.System.out.println(i)).apply(element) i = i.+(1) } @@ -86,7 +85,6 @@ var i: scala.Int = 0 while (i.<(size)) { val element: scala.Int = arr1.apply(i) - ((x: scala.Int) => scala.Predef.println(x)).apply(element) i = i.+(1) } diff --git a/tests/run-with-compiler/quote-var.check b/tests/run-with-compiler/quote-var.check new file mode 100644 index 000000000000..cd470e619003 --- /dev/null +++ b/tests/run-with-compiler/quote-var.check @@ -0,0 +1 @@ +xyz diff --git a/tests/run-with-compiler/quote-var.scala b/tests/run-with-compiler/quote-var.scala new file mode 100644 index 000000000000..e19d5992c471 --- /dev/null +++ b/tests/run-with-compiler/quote-var.scala @@ -0,0 +1,38 @@ +import scala.quoted._ + +object Test { + + sealed trait Var { + def get: Expr[String] + def update(x: Expr[String]): Expr[Unit] + } + + object Var { + def apply(init: Expr[String])(body: Var => Expr[String]): Expr[String] = '{ + var x = ~init + ~body( + new Var { + def get: Expr[String] = '(x) + def update(e: Expr[String]): Expr[Unit] = '{ x = ~e } + } + ) + } + } + + + def test1(): Expr[String] = Var('("abc")) { x => + '{ + ~x.update('("xyz")) + ~x.get + } + } + + def main(args: Array[String]): Unit = { + implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make + + println(test1().run) + } +} + + + diff --git a/tests/run/i4947e.check b/tests/run/i4947e.check new file mode 100644 index 000000000000..1e67df692f1e --- /dev/null +++ b/tests/run/i4947e.check @@ -0,0 +1,8 @@ +assertImpl: Test$.main(Test_2.scala:7) +true +assertImpl: Test$.main(Test_2.scala:8) +false +assertImpl: Test$.main(Test_2.scala:9) +hi: Test$.main(Test_2.scala:10) +hi again: Test$.main(Test_2.scala:11) +false diff --git a/tests/run/i4947e/Macro_1.scala b/tests/run/i4947e/Macro_1.scala new file mode 100644 index 000000000000..175a642be819 --- /dev/null +++ b/tests/run/i4947e/Macro_1.scala @@ -0,0 +1,11 @@ +import scala.quoted._ + +object Macros { + def printStack(tag: String): Unit = { + println(tag + ": "+ new Exception().getStackTrace().apply(1)) + } + def assertImpl(expr: Expr[Boolean]) = '{ + printStack("assertImpl") + println(~expr) + } +} diff --git a/tests/run/i4947e/Test_2.scala b/tests/run/i4947e/Test_2.scala new file mode 100644 index 000000000000..cc83cd4b4536 --- /dev/null +++ b/tests/run/i4947e/Test_2.scala @@ -0,0 +1,15 @@ +object Test { + + inline def assert2(expr: => Boolean): Unit = ~Macros.assertImpl('(expr)) + + def main(args: Array[String]): Unit = { + val x = 1 + assert2(x != 0) + assert2(x == 0) + assert2 { + Macros.printStack("hi") + Macros.printStack("hi again") + x == 0 + } + } +} diff --git a/tests/run/i4947f.check b/tests/run/i4947f.check new file mode 100644 index 000000000000..1e67df692f1e --- /dev/null +++ b/tests/run/i4947f.check @@ -0,0 +1,8 @@ +assertImpl: Test$.main(Test_2.scala:7) +true +assertImpl: Test$.main(Test_2.scala:8) +false +assertImpl: Test$.main(Test_2.scala:9) +hi: Test$.main(Test_2.scala:10) +hi again: Test$.main(Test_2.scala:11) +false diff --git a/tests/run/i4947f/Macro_1.scala b/tests/run/i4947f/Macro_1.scala new file mode 100644 index 000000000000..c18b3c0a7eba --- /dev/null +++ b/tests/run/i4947f/Macro_1.scala @@ -0,0 +1,14 @@ +import scala.quoted._ + +object Macros { + def printStack(tag: String): Unit = { + println(tag + ": "+ new Exception().getStackTrace().apply(1)) + } + def assertImpl(expr: Expr[Boolean]) = '{ + printStack("assertImpl") + println(~expr) + } + + inline def assert2(expr: => Boolean): Unit = ~Macros.assertImpl('(expr)) + +} diff --git a/tests/run/i4947f/Test_2.scala b/tests/run/i4947f/Test_2.scala new file mode 100644 index 000000000000..6a96b58b4d34 --- /dev/null +++ b/tests/run/i4947f/Test_2.scala @@ -0,0 +1,15 @@ +object Test { + + import Macros._ + + def main(args: Array[String]): Unit = { + val x = 1 + assert2(x != 0) + assert2(x == 0) + assert2 { + Macros.printStack("hi") + Macros.printStack("hi again") + x == 0 + } + } +}