Skip to content

Demonstrate regression in #4949 for positions of quote-inlined code #5048

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

Closed
Closed
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
6 changes: 0 additions & 6 deletions compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,6 @@ import scala.reflect.ClassTag
object PickledQuotes {
import tpd._

/** Pickle the tree of the quoted.Expr */
def pickleExpr(tree: Tree)(implicit ctx: Context): scala.quoted.Expr[Any] = {
val pickled = pickleQuote(tree)
scala.runtime.quoted.Unpickler.unpickleExpr(pickled, Nil)
}

/** Pickle the tree of the quote into strings */
def pickleQuote(tree: Tree)(implicit ctx: Context): scala.runtime.quoted.Unpickler.Pickled = {
if (ctx.reporter.hasErrors) Nil
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -391,8 +391,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
case SeqLiteral(elems, elemtpt) =>
"[" ~ toTextGlobal(elems, ",") ~ " : " ~ toText(elemtpt) ~ "]"
case tree @ Inlined(call, bindings, body) =>
(("/* inlined from " ~ toText(call) ~ " */ ") `provided`
!call.isEmpty && !homogenizedView && !ctx.settings.YshowNoInline.value) ~
(("/* inlined from " ~ (if (call.isEmpty) "outside" else toText(call)) ~ " */ ") `provided`
!homogenizedView && !ctx.settings.YshowNoInline.value) ~
blockText(bindings :+ body)
case tpt: untpd.DerivedTypeTree =>
"<derived typetree watching " ~ summarized(toText(tpt.watched)) ~ ">"
Expand Down
11 changes: 0 additions & 11 deletions compiler/src/dotty/tools/dotc/quoted/TreeCleaner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,6 @@ class TreeCleaner extends tpd.TreeMap {
}

super.transform(tree0) match {
case Block(Nil, expr1) => expr1
case Block(stats1, expr1) =>
val flatStats = stats1.flatMap {
case Block(stats2, expr2) => stats2 ::: expr2 :: Nil
case Literal(Constant(())) => Nil
case stat => stat :: Nil
}
expr1 match {
case Block(stats3, expr3) => Block(flatStats ::: stats3, expr3)
case expr3 => Block(flatStats, expr3)
}
case tree1: TypeTree => TypeTree(tree1.tpe.subst(aliasesSyms, aliasesTypes))
case tree1: Ident => aliases.get(tree1.symbol).getOrElse(tree1)
case tree1 => tree1
Expand Down
15 changes: 3 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,8 @@ package transform
import dotty.tools.dotc.ast.{Trees, tpd, untpd}
import scala.collection.mutable
import core._
import typer.Checking
import dotty.tools.dotc.typer.Checking
import dotty.tools.dotc.typer.Inliner
import Types._, Contexts._, Names._, Flags._, DenotTransformers._
import SymDenotations._, StdNames._, Annotations._, Trees._, Scopes._
import Decorators._
Expand Down Expand Up @@ -234,17 +235,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
11 changes: 9 additions & 2 deletions compiler/src/dotty/tools/dotc/transform/ReifyQuotes.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 @@ -372,7 +373,12 @@ class ReifyQuotes extends MacroTransformWithImplicits {
capturers(body.symbol)(body)
case _=>
val (body1, splices) = nested(isQuote = true).split(body)
if (level == 0 && !ctx.inRewriteMethod) pickledQuote(body1, splices, body.tpe, isType).withPos(quote.pos)
if (level == 0 && !ctx.inRewriteMethod) {
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 a rewrite def. Keep the tree as it is, it will be transformed at inline site.
body
Expand Down Expand Up @@ -422,7 +428,8 @@ class ReifyQuotes extends MacroTransformWithImplicits {
}
else if (level == 1) {
val (body1, quotes) = nested(isQuote = false).split(splice.qualifier)
makeHole(body1, quotes, splice.tpe).withPos(splice.pos)
val hole = makeHole(body1, quotes, splice.tpe).withPos(splice.pos)
if (splice.isType) 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
6 changes: 2 additions & 4 deletions compiler/src/dotty/tools/dotc/transform/Splicer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,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 Expand Up @@ -286,8 +286,6 @@ object Splicer {
case NamedArg(_, arg) => interpretTree(arg)
case Ident(name) if env.contains(name) => env(name)

case Inlined(EmptyTree, Nil, expansion) => interpretTree(expansion)

case _ => unexpectedTree(tree)
}

Expand Down
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 @@ -171,6 +171,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 compiler/test/dotc/run-test-pickling.blacklist
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ i4803d
i4803e
i4803f
i4947b
i4947e
i4947f
implicitMatch.scala
implicitShortcut
lazy-implicit-lists.scala
Expand Down
107 changes: 77 additions & 30 deletions library/src/scala/tasty/util/ShowSourceCode.scala
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,17 @@ class ShowSourceCode[T <: Tasty with Singleton](tasty0: T) extends Show[T](tasty
printTree(body) += " while "
inParens(printTree(cond))

case IsDefDef(ddef @ DefDef(name, targs, argss, _, rhsOpt)) if name.startsWith("$anonfun") =>
// Decompile lambda definition
assert(targs.isEmpty)
val args :: Nil = argss
val Some(rhs) = rhsOpt
inParens {
printArgsDefs(args)
this += " => "
printTree(rhs)
}

case IsDefDef(ddef @ DefDef(name, targs, argss, tpt, rhs)) =>
printDefAnnotations(ddef)

Expand Down Expand Up @@ -363,34 +374,13 @@ class ShowSourceCode[T <: Tasty with Singleton](tasty0: T) extends Show[T](tasty
case IsValDef(tree) => !tree.symbol.flags.isObject
case _ => true
}
printFlatBlock(stats, expr)

expr match {
case Term.Lambda(_, _) =>
// Decompile lambda from { def annon$(...) = ...; closure(annon$, ...)}
assert(stats.size == 1)
val DefDef(_, _, args :: Nil, _, Some(rhs)) :: Nil = stats
inParens {
printArgsDefs(args)
this += " => "
printTree(rhs)
}
case _ =>
this += "{"
indented {
printStats(stats, expr)
}
this += lineBreak() += "}"
}

case Term.Inlined(call, bindings, expansion) => // FIXME: Don't print Inlined with empty calls?
this += "{ // inlined"
indented {
printStats(bindings, expansion)
}
this += lineBreak() += "}"
case Term.Inlined(_, bindings, expansion) =>
printFlatBlock(bindings, expansion)

case Term.Lambda(meth, tpt) =>
// Printed in Term.Block branch
// Printed in by it's DefDef
this

case Term.If(cond, thenp, elsep) =>
Expand Down Expand Up @@ -433,15 +423,72 @@ class ShowSourceCode[T <: Tasty with Singleton](tasty0: T) extends Show[T](tasty

}

def flatBlock(stats: List[Statement], expr: Term): (List[Statement], Term) = {
val flatStats = List.newBuilder[Statement]
def extractFlatStats(stat: Statement): Unit = stat match {
case Term.Block(stats1, expr1) =>
stats1.foreach(extractFlatStats)
extractFlatStats(expr1)
case Term.Inlined(_, bindings, expansion) =>
bindings.foreach(extractFlatStats)
extractFlatStats(expansion)
case Term.Literal(Constant.Unit()) => // ignore
case stat => flatStats += stat
}
def extractFlatExpr(term: Term): Term = term match {
case Term.Block(stats1, expr1) =>
stats1.foreach(extractFlatStats)
extractFlatExpr(expr1)
case Term.Inlined(_, bindings, expansion) =>
bindings.foreach(extractFlatStats)
extractFlatExpr(expansion)
case term => term
}
stats.foreach(extractFlatStats)
val flatExpr = extractFlatExpr(expr)
(flatStats.result(), flatExpr)
}

def printFlatBlock(stats: List[Statement], expr: Term): Buffer = {
val (stats1, expr1) = flatBlock(stats, expr)

// Remove Term.Lambda nodes, lambdas are printed by their definition
val stats2 = stats1.filter { case Term.Lambda(_, _) => false; case _ => true }
val (stats3, expr3) = expr1 match {
case Term.Lambda(_, _) =>
val init :+ last = stats2
(init, last)
case _ => (stats2, expr1)
}

if (stats3.isEmpty) {
printTree(expr3)
} else {
this += "{"
indented {
printStats(stats3, expr3)
}
this += lineBreak() += "}"
}
}

def printStats(stats: List[Tree], expr: Tree): Unit = {
def printSeparator(next: Tree): Unit = {
// Avoid accidental application of opening `{` on next line with a double break
def rec(next: Tree): Unit = next match {
case Term.Block(stats, _) if stats.nonEmpty => this += doubleLineBreak()
case Term.Inlined(_, bindings, _) if bindings.nonEmpty => this += doubleLineBreak()
case Term.Select(qual, _, _) => rec(qual)
case Term.Apply(fn, _) => rec(fn)
case Term.TypeApply(fn, _) => rec(fn)
case _ => this += lineBreak()
}
next match {
case Term.Block(_, _) => this += doubleLineBreak()
case Term.Inlined(_, _, _) => this += doubleLineBreak()
case Term.Select(qual, _, _) => printSeparator(qual)
case Term.Apply(fn, _) => printSeparator(fn)
case Term.TypeApply(fn, _) => printSeparator(fn)
case IsTerm(term) =>
flatBlock(Nil, term) match {
case (next :: _, _) => rec(next)
case (Nil, next) => rec(next)
}
case _ => this += lineBreak()
}
}
Expand Down
10 changes: 4 additions & 6 deletions tests/pos/i2104b.decompiled
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,8 @@ case class Pair[A, B](_1: A, _2: B) {
object Pair extends scala.AnyRef()
/** Decompiled from out/posTestFromTasty/pos/i2104b/Test.class */
object Test {
def main(args: scala.Array[scala.Predef.String]): scala.Unit = {
Cons.apply[scala.Option[scala.Int], scala.None.type](scala.Option.apply[scala.Int](1), scala.None) match {
case Cons(scala.Some(i), scala.None) =>
()
}
def main(args: scala.Array[scala.Predef.String]): scala.Unit = Cons.apply[scala.Option[scala.Int], scala.None.type](scala.Option.apply[scala.Int](1), scala.None) match {
case Cons(scala.Some(i), scala.None) =>
()
}
}
}
12 changes: 5 additions & 7 deletions tests/pos/i4526b.decompiled
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
/** Decompiled from out/posTestFromTasty/pos/i4526b/Foo.class */
class Foo() {
def justdoit(f: scala.Either[scala.Int, scala.Predef.String]): scala.Predef.String = {
f match {
case scala.Left(i) =>
i.toString()
case scala.Right(s) =>
(s: java.lang.String)
}
def justdoit(f: scala.Either[scala.Int, scala.Predef.String]): scala.Predef.String = f match {
case scala.Left(i) =>
i.toString()
case scala.Right(s) =>
(s: java.lang.String)
}
}
6 changes: 1 addition & 5 deletions tests/pos/lambda.decompiled
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
/** Decompiled from out/posTestFromTasty/pos/lambda/foo/Foo.class */
package foo {
class Foo() {
{
((x: scala.Int) => {
2
})
}
((x: scala.Int) => 2)
val a: scala.Function1[scala.Int, scala.Int] = ((x: scala.Int) => x.*(x))
}
}
4 changes: 1 addition & 3 deletions tests/pos/simpleDoWhile.decompiled
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
class Foo() {
def foo: scala.Unit = {
var i: scala.Int = 1
do {
i = 0
} while (i.!=(0))
do i = 0 while (i.!=(0))
}
}
4 changes: 1 addition & 3 deletions tests/pos/simpleInline.decompiled
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
/** Decompiled from out/posTestFromTasty/pos/simpleInline/Foo.class */
class Foo() {
rewrite def foo: scala.Int = 9
def bar: scala.Int = { // inlined
9
}
def bar: scala.Int = 9
}
10 changes: 4 additions & 6 deletions tests/pos/simpleMatchCase.decompiled
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
/** Decompiled from out/posTestFromTasty/pos/simpleMatchCase/Foo.class */
class Foo() {
def foo: scala.Unit = {
"c" match {
case x =>
scala.Predef.println("a")
scala.Predef.println("b")
}
def foo: scala.Unit = "c" match {
case x =>
scala.Predef.println("a")
scala.Predef.println("b")
}
}
4 changes: 1 addition & 3 deletions tests/pos/simpleWhile.decompiled
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
class Foo() {
def foo: scala.Unit = {
var i: scala.Int = 1
while (i.!=(0)) {
i = 0
}
while (i.!=(0)) i = 0
}
}
4 changes: 1 addition & 3 deletions tests/pos/t3869.decompiled
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
/** Decompiled from out/posTestFromTasty/pos/t3869/Test.class */
object Test {
def f: scala.Unit = try return () finally while (true) ()
def main(args: scala.Array[scala.Predef.String]): scala.Unit = {
Test.f
}
def main(args: scala.Array[scala.Predef.String]): scala.Unit = Test.f
}
7 changes: 2 additions & 5 deletions tests/pos/t704.decompiled
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,8 @@ trait E() extends java.lang.Object with D {
val x1: scala.AnyRef = E.this.get_xxxx
scala.Console.println(y)
}

{
yyyy
()
}
yyyy
()
}
}
/** Decompiled from out/posTestFromTasty/pos/t704/Go.class */
Expand Down
Loading