From a099d7ef03fecaefcb3957bbfcff23545b48349e Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Thu, 12 Jun 2025 17:12:18 +0200 Subject: [PATCH] Fix issue with macro results having unexpected source switches Problem seems to have been derived from the fact that we would only remap the source of the outermost tree inserted in a Hole, instead of going through the whole tree inserted there. This meant that when inserting `Block` with `Imports` in the contents, the Block node would have a new source assigned (causing a source change encoded in the TASTy), and all contents would still have the previous source assigned (causing more source changes encoded). Now we reassign sources to nodes recursively, meaning that only the outermost node has a source change assigned in TASTy. --- .../src/dotty/tools/dotc/quoted/PickledQuotes.scala | 12 ++++++++++-- scaladoc-testcases/src/tests/i22265/macro.scala | 13 +++++++++++++ scaladoc-testcases/src/tests/i22265/main.scala | 4 ++++ 3 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 scaladoc-testcases/src/tests/i22265/macro.scala create mode 100644 scaladoc-testcases/src/tests/i22265/main.scala diff --git a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala index b47c20b3e5cd..260840f8baa3 100644 --- a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala +++ b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala @@ -115,8 +115,16 @@ object PickledQuotes { // We need to make sure a hole is created with the source file of the surrounding context, even if // it filled with contents a different source file. - if filled.source == ctx.source then filled - else filled.cloneIn(ctx.source).withSpan(tree.span) + val sourceReassigner = new TreeMapWithImplicits { + override def transform(innerTree: tpd.Tree)(using Context) = { + innerTree match + case EmptyTree => innerTree // cannot change span of EmptyTree + case _ => + if innerTree.source == tree.source then innerTree + else super.transform(innerTree.cloneIn(tree.source).withSpan(tree.span)) + } + } + sourceReassigner.transform(filled) else // For backwards compatibility with 3.0.x and 3.1.x // In 3.2.0+ all these holes are handled by `spliceTypes` before we call `spliceTerms`. diff --git a/scaladoc-testcases/src/tests/i22265/macro.scala b/scaladoc-testcases/src/tests/i22265/macro.scala new file mode 100644 index 000000000000..039fdf40f754 --- /dev/null +++ b/scaladoc-testcases/src/tests/i22265/macro.scala @@ -0,0 +1,13 @@ +import scala.quoted._ + +object TestBuilder: + // transparent is needed + transparent inline def apply(inline expr: Unit): Any = + ${ TestBuilder.processTests('expr) } + + def processTests(using Quotes)(body: Expr[Unit]): Expr[Any] = + import quotes.reflect._ + body.asTerm match { + case Inlined(_, _, bindings) => + '{ ${bindings.asExpr}; () } + } diff --git a/scaladoc-testcases/src/tests/i22265/main.scala b/scaladoc-testcases/src/tests/i22265/main.scala new file mode 100644 index 000000000000..7642047a175b --- /dev/null +++ b/scaladoc-testcases/src/tests/i22265/main.scala @@ -0,0 +1,4 @@ +object breaks { + TestBuilder: + import List.empty +}