Skip to content

Fix quote positions (missing Inlined nodes) #5505

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 12, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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())
}
Expand Down
16 changes: 4 additions & 12 deletions compiler/src/dotty/tools/dotc/transform/PostTyper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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._
Expand Down Expand Up @@ -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)) {
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/transform/Splicer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 =>
Expand Down
21 changes: 19 additions & 2 deletions compiler/src/dotty/tools/dotc/transform/Staging.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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`
Expand Down Expand Up @@ -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)"

}
}
9 changes: 9 additions & 0 deletions compiler/src/dotty/tools/dotc/typer/Inliner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions tests/run-with-compiler/quote-fun-app-1.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
42
43
17 changes: 17 additions & 0 deletions tests/run-with-compiler/quote-fun-app-1.scala
Original file line number Diff line number Diff line change
@@ -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 }
}
2 changes: 0 additions & 2 deletions tests/run-with-compiler/quote-unrolled-foreach.check
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand Down
1 change: 1 addition & 0 deletions tests/run-with-compiler/quote-var.check
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
xyz
38 changes: 38 additions & 0 deletions tests/run-with-compiler/quote-var.scala
Original file line number Diff line number Diff line change
@@ -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)
}
}



8 changes: 8 additions & 0 deletions tests/run/i4947e.check
Original file line number Diff line number Diff line change
@@ -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
11 changes: 11 additions & 0 deletions tests/run/i4947e/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -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)
}
}
15 changes: 15 additions & 0 deletions tests/run/i4947e/Test_2.scala
Original file line number Diff line number Diff line change
@@ -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
}
}
}
8 changes: 8 additions & 0 deletions tests/run/i4947f.check
Original file line number Diff line number Diff line change
@@ -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
14 changes: 14 additions & 0 deletions tests/run/i4947f/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -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))

}
15 changes: 15 additions & 0 deletions tests/run/i4947f/Test_2.scala
Original file line number Diff line number Diff line change
@@ -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
}
}
}