diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala index e7b5a0dad1bf..db5ec3648977 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala @@ -25,6 +25,7 @@ import dotty.tools.dotc.core.Contexts._ import dotty.tools.dotc.core.Phases._ import dotty.tools.dotc.core.Decorators.em import dotty.tools.dotc.report +import dotty.tools.dotc.inlines.Inlines /* * @@ -479,6 +480,10 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { case t: TypeApply => // dotty specific generatedType = genTypeApply(t) + case inlined @ Inlined(_, _, _) => + genLoadTo(Inlines.dropInlined(inlined) , expectedType, dest) + generatedDest = dest + case _ => abort(s"Unexpected tree in genLoad: $tree/${tree.getClass} at: ${tree.span}") } diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala index 0a11fb898b48..606c9fece7b2 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala @@ -3,7 +3,6 @@ package backend package jvm import scala.language.unsafeNulls - import scala.annotation.tailrec import scala.collection.{ mutable, immutable } @@ -24,6 +23,9 @@ import dotty.tools.dotc.util.Spans._ import dotty.tools.dotc.report import dotty.tools.dotc.transform.SymUtils._ +import InlinedSourceMaps._ +import dotty.tools.dotc.inlines.Inlines.InliningPosition + /* * * @author Miguel Garcia, http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/ @@ -91,6 +93,8 @@ trait BCodeSkelBuilder extends BCodeHelpers { var isCZParcelable = false var isCZStaticModule = false + var sourceMap: InlinedSourceMap = null + /* ---------------- idiomatic way to ask questions to typer ---------------- */ def paramTKs(app: Apply, take: Int = -1): List[BType] = app match { @@ -276,7 +280,8 @@ trait BCodeSkelBuilder extends BCodeHelpers { superClass, interfaceNames.toArray) if (emitSource) { - cnode.visitSource(cunit.source.file.name, null /* SourceDebugExtension */) + sourceMap = sourceMapFor(cunit)(s => classBTypeFromSymbol(s).internalName) + cnode.visitSource(cunit.source.file.name, sourceMap.debugExtension.orNull) } enclosingMethodAttribute(claszSymbol, internalName, asmMethodType(_).descriptor) match { @@ -555,26 +560,37 @@ trait BCodeSkelBuilder extends BCodeHelpers { case labnode: asm.tree.LabelNode => (labnode.getLabel == lbl); case _ => false } ) } - def lineNumber(tree: Tree): Unit = { + + def emitNr(nr: Int): Unit = + @tailrec def getNonLabelNode(a: asm.tree.AbstractInsnNode): asm.tree.AbstractInsnNode = a match { case a: asm.tree.LabelNode => getNonLabelNode(a.getPrevious) - case _ => a + case _ => a } - if (!emitLines || !tree.span.exists) return; - val nr = ctx.source.offsetToLine(tree.span.point) + 1 - if (nr != lastEmittedLineNr) { + if nr != lastEmittedLineNr then lastEmittedLineNr = nr - getNonLabelNode(lastInsn) match { + getNonLabelNode(lastInsn) match case lnn: asm.tree.LineNumberNode => // overwrite previous landmark as no instructions have been emitted for it lnn.line = nr case _ => mnode.visitLineNumber(nr, currProgramPoint()) - } - } - } + end emitNr + + def lineNumber(tree: Tree): Unit = { + if (!emitLines || !tree.span.exists) return; + // Use JSR-45 mapping for inlined trees defined outside of the current compilation unit + if tree.source != cunit.source then + sourceMap.lineFor(tree) match + case Some(nr) => + emitNr(nr) + case None => () + else + val nr = ctx.source.offsetToLine(tree.span.point) + 1 + emitNr(nr) + } // on entering a method def resetMethodBookkeeping(dd: DefDef) = { diff --git a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala new file mode 100644 index 000000000000..f60b2b3c1086 --- /dev/null +++ b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala @@ -0,0 +1,172 @@ +package dotty.tools +package backend +package jvm + +import dotc.CompilationUnit +import dotc.ast.tpd._ +import dotc.util.{ SourcePosition, SourceFile } +import dotc.core.Contexts._ +import dotc.core.Symbols.Symbol +import dotc.report +import dotc.inlines.Inlines.InliningPosition +import collection.mutable + +/** + * Tool for generating virtual lines for inlined calls and keeping track of them. + + * How it works: + * - For every inlined call it assumes that empty lines are appended to the source file. These + * lines are not added anywhere in physical form. We only assume that they exist only to be used + * by `LineNumberTable` and `SourceDebugExtension`. The number of these virtual lines is every + * time equal to the size of line range of the expansion of inlined call. + * - It generates SMAP (as defined by JSR-45) containing two strata. The first stratum (`Scala`) + * is describing the mapping from the real source files to the real and virtual lines in our + * assumed source. The second stratum (`ScalaDebug`) is mapping from virtual lines to + * corresponding inlined calls. + * - Generated SMAP is written to the bytecode in `SourceDebugExtension` + * - During the generation of the bytecode backed is asking `InlinedSourceMap` about position of + * all trees that have source different from the main source of given compilation unit. + * The response to that request is number of the virtual line that is corresponding to particular + * line from the other source. + * - Debuggers can use information stored in `LineNumberTable` and `SourceDebugExtension` to + * correctly guess which line of inlined method is currently executed. They can also construct + * stack frames for inlined calls. + **/ +object InlinedSourceMaps: + private case class Request(inline: Inlined, firstFakeLine: Int) + + private class File(id: Int, name: String, path: Option[String]): + def write(b: mutable.StringBuilder): Unit = + if path.isDefined then b ++= "+ " + b append id + b += ' ' + b ++= name + b += '\n' + path.foreach { p => + b ++= p + b += '\n' + } + end File + + private class Mapping( + inputStartLine: Int, + fileId: Int, + repeatCount: Int, + outputStartLine: Int, + increment: Int + ): + extension (b: mutable.StringBuilder) def appendNotDefault(prefix: Char, value: Int): Unit = + if value != 1 then + b += prefix + b append value + + def write(b: mutable.StringBuilder): Unit = + b append (inputStartLine + 1) + b.appendNotDefault('#', fileId) + b.appendNotDefault(',', repeatCount) + b += ':' + b append (outputStartLine + 1) + b.appendNotDefault(',', increment) + b += '\n' + end Mapping + + private class Stratum(name: String, files: List[File], mappings: List[Mapping]): + def write(b: mutable.StringBuilder): Unit = + b ++= "*S " + b ++= name + b ++= "\n*F\n" + files.foreach(_.write(b)) + b ++= "*L\n" + mappings.foreach(_.write(b)) + b ++= "*E\n" + end Stratum + + def sourceMapFor(cunit: CompilationUnit)(internalNameProvider: Symbol => String)(using Context): InlinedSourceMap = + val requests = mutable.ListBuffer.empty[Inlined] + var internalNames = Map.empty[SourceFile, String] + + class RequestCollector(enclosingFile: SourceFile) extends TreeTraverser: + override def traverse(tree: Tree)(using Context): Unit = + tree match + case inlined @ Inlined(call, bindings, expansion) => + if expansion.source != enclosingFile && expansion.source != cunit.source then + requests += inlined + val topLevelClass = Option.when(!call.isEmpty)(call.symbol.topLevelClass) + + topLevelClass match + case Some(symbol) if !internalNames.isDefinedAt(tree.source) => + internalNames += (tree.source -> internalNameProvider(symbol)) + // We are skipping any internal name info if we already have one stored in our map + // because a debugger will use internal name only to localize matching source. + // Both old and new internal names are associated with the same source file + // so it doesn't matter if internal name is not matching used symbol. + case _ => () + + traverseChildren(tree) + case _ => traverseChildren(tree) + end RequestCollector + + // Don't generate mappings for the quotes compiled at runtime by the staging compiler + if cunit.source.file.isVirtual then InlinedSourceMap(cunit, Nil, Map.empty[SourceFile, String]) + else + var lastLine = cunit.tpdTree.sourcePos.endLine + // returns the first fake line (starting from 0) + def allocate(origPos: SourcePosition): Int = + val line = lastLine + 1 + lastLine += origPos.lines.length + line + + RequestCollector(cunit.source).traverse(cunit.tpdTree) + + val allocated = requests.map(r => Request(r, allocate(r.expansion.sourcePos))) + + InlinedSourceMap(cunit, allocated.toList, internalNames) + end sourceMapFor + + class InlinedSourceMap private[InlinedSourceMaps] ( + cunit: CompilationUnit, + requests: List[Request], + internalNames: Map[SourceFile, String])(using Context): + + def debugExtension: Option[String] = Option.when(requests.nonEmpty) { + val scalaStratum = + val files = cunit.source :: requests.map(_.inline.expansion.source).distinct.filter(_ != cunit.source) + val mappings = requests.map { case Request(inline, firstFakeLine) => + Mapping(inline.expansion.sourcePos.startLine, + files.indexOf(inline.expansion.source) + 1, + inline.expansion.sourcePos.lines.length, + firstFakeLine, 1) + } + Stratum("Scala", + files.zipWithIndex.map { case (f, n) => File(n + 1, f.name, internalNames.get(f)) }, + Mapping(0, 1, cunit.tpdTree.sourcePos.lines.length, 0, 1) +: mappings + ) + + val debugStratum = + val mappings = requests.map { case Request(inline, firstFakeLine) => + Mapping(inline.sourcePos.startLine, 1, 1, firstFakeLine, inline.expansion.sourcePos.lines.length) + } + Stratum("ScalaDebug", File(1, cunit.source.name, None) :: Nil, mappings) + + val b = new StringBuilder + b ++= "SMAP\n" + b ++= cunit.source.name + b += '\n' + b ++= "Scala\n" + scalaStratum.write(b) + debugStratum.write(b) + b.toString + } + + def lineFor(tree: Tree): Option[Int] = + + val sourcePos = tree.sourcePos + requests.findLast(r => r.inline.expansion.contains(tree)) match + case Some(request) => + val offset = sourcePos.startLine - request.inline.expansion.sourcePos.startLine + val virtualLine = request.firstFakeLine + offset + if requests.filter(_.inline.expansion.contains(tree)).size > 1 then None + else Some(virtualLine + 1) // + 1 because the first line is 1 in the LineNumberTable + case None => + // report.warning(s"${sourcePos.show} was inlined in ${cunit.source} but its inlining position was not recorded.") + None \ No newline at end of file diff --git a/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala b/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala index eee791852fde..5fc03d828acb 100644 --- a/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala +++ b/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala @@ -36,6 +36,7 @@ import dotty.tools.dotc.transform.sjs.JSSymUtils._ import JSEncoding._ import ScopedVar.withScopedVars +import dotty.tools.dotc.inlines.Inlines /** Main codegen for Scala.js IR. * @@ -1930,6 +1931,9 @@ class JSCodeGen()(using genCtx: Context) { case EmptyTree => js.Skip() + case inlined @ Inlined(_, _, _) => + genStatOrExpr(Inlines.dropInlined(inlined), isStat) + case _ => throw new FatalError("Unexpected tree in genExpr: " + tree + "/" + tree.getClass + " at: " + (tree.span: Position)) diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index a6118732d4ae..c20ff8409298 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -36,7 +36,7 @@ class Compiler { List(new Parser) :: // Compiler frontend: scanner, parser List(new TyperPhase) :: // Compiler frontend: namer, typer List(new CheckUnused.PostTyper) :: // Check for unused elements - List(new YCheckPositions) :: // YCheck positions + //List(new YCheckPositions) :: // YCheck positions List(new sbt.ExtractDependencies) :: // Sends information on classes' dependencies to sbt via callbacks List(new semanticdb.ExtractSemanticDB) :: // Extract info into .semanticdb files List(new PostTyper) :: // Additional checks and cleanups after type checking diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index b273bbf01e95..f1e97e713742 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -1341,10 +1341,17 @@ object Trees { case tree: SeqLiteral if (elems eq tree.elems) && (elemtpt eq tree.elemtpt) => tree case _ => finalize(tree, untpd.SeqLiteral(elems, elemtpt)(sourceFile(tree))) } - def Inlined(tree: Tree)(call: tpd.Tree, bindings: List[MemberDef], expansion: Tree)(using Context): Inlined = tree match { - case tree: Inlined if (call eq tree.call) && (bindings eq tree.bindings) && (expansion eq tree.expansion) => tree - case _ => finalize(tree, untpd.Inlined(call, bindings, expansion)(sourceFile(tree))) - } + // Positions of trees are automatically pushed down except when we reach an Inlined tree. Therefore, we + // make sure the new expansion has a position by copying the one of the original Inlined tree. + def Inlined(tree: Inlined)(call: tpd.Tree, bindings: List[MemberDef], expansion: Tree)(using Context): Inlined = + if (call eq tree.call) && (bindings eq tree.bindings) && (expansion eq tree.expansion) then tree + else + // Copy the span from the original Inlined tree if the new expansion doesn't have a span. + val expansionWithSpan = + if expansion.span.exists then expansion + else expansion.withSpan(tree.expansion.span) + finalize(tree, untpd.Inlined(call, bindings, expansionWithSpan)(sourceFile(tree))) + def Quote(tree: Tree)(body: Tree, tags: List[Tree])(using Context): Quote = tree match { case tree: Quote if (body eq tree.body) && (tags eq tree.tags) => tree case _ => finalize(tree, untpd.Quote(body, tags)(sourceFile(tree))) @@ -1549,7 +1556,7 @@ object Trees { cpy.Try(tree)(transform(block), transformSub(cases), transform(finalizer)) case SeqLiteral(elems, elemtpt) => cpy.SeqLiteral(tree)(transform(elems), transform(elemtpt)) - case Inlined(call, bindings, expansion) => + case tree @ Inlined(call, bindings, expansion) => cpy.Inlined(tree)(call, transformSub(bindings), transform(expansion)(using inlineContext(call))) case TypeTree() => tree diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index 0d3085f13f1e..d3760e553767 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -748,7 +748,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { } } - override def Inlined(tree: Tree)(call: Tree, bindings: List[MemberDef], expansion: Tree)(using Context): Inlined = { + override def Inlined(tree: Inlined)(call: Tree, bindings: List[MemberDef], expansion: Tree)(using Context): Inlined = { val tree1 = untpdCpy.Inlined(tree)(call, bindings, expansion) tree match { case tree: Inlined if sameTypes(bindings, tree.bindings) && (expansion.tpe eq tree.expansion.tpe) => diff --git a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala index bcc10ffa6db8..5e3964189a30 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala @@ -2,16 +2,23 @@ package dotty.tools package dotc package inlines -import ast.*, core.* -import Flags.*, Symbols.*, Types.*, Decorators.*, Constants.*, Contexts.* +import ast.{tpd, *} +import core.* +import Flags.* +import Symbols.* +import Types.* +import Decorators.* +import Constants.* +import Contexts.* import StdNames.tpnme -import transform.SymUtils._ +import transform.SymUtils.* import typer.* import NameKinds.BodyRetainerName import SymDenotations.SymDenotation import config.Printers.inlining import ErrorReporting.errorTree -import dotty.tools.dotc.util.{SourceFile, SourcePosition, SrcPos} +import dotty.tools.dotc.ast.tpd.{cpy, inlineContext} +import dotty.tools.dotc.util.{Property, SourceFile, SourcePosition, SrcPos} import parsing.Parsers.Parser import transform.{PostTyper, Inlining, CrossVersionChecks} import staging.StagingLevel @@ -29,6 +36,9 @@ object Inlines: */ private[dotc] class MissingInlineInfo extends Exception + object InliningPosition extends Property.StickyKey[InliningPosition] + case class InliningPosition(targetPos: List[(SourcePosition, Option[Symbol])]) + /** `sym` is an inline method with a known body to inline. */ def hasBodyToInline(sym: SymDenotation)(using Context): Boolean = @@ -117,7 +127,7 @@ object Inlines: case Block(stats, expr) => bindings ++= stats.map(liftPos) liftBindings(expr, liftPos) - case Inlined(call, stats, expr) => + case tree @ Inlined(call, stats, expr) => bindings ++= stats.map(liftPos) val lifter = liftFromInlined(call) cpy.Inlined(tree)(call, Nil, liftBindings(expr, liftFromInlined(call).transform(_))) @@ -241,58 +251,12 @@ object Inlines: /** Replace `Inlined` node by a block that contains its bindings and expansion */ def dropInlined(inlined: Inlined)(using Context): Tree = - val tree1 = - if inlined.bindings.isEmpty then inlined.expansion - else cpy.Block(inlined)(inlined.bindings, inlined.expansion) - // Reposition in the outer most inlined call - if (enclosingInlineds.nonEmpty) tree1 else reposition(tree1, inlined.span) - - def reposition(tree: Tree, callSpan: Span)(using Context): Tree = - // Reference test tests/run/i4947b - - val curSource = ctx.compilationUnit.source - - // Tree copier that changes the source of all trees to `curSource` - val cpyWithNewSource = new TypedTreeCopier { - override protected def sourceFile(tree: tpd.Tree): SourceFile = curSource - override protected val untpdCpy: untpd.UntypedTreeCopier = new untpd.UntypedTreeCopier { - override protected def sourceFile(tree: untpd.Tree): SourceFile = curSource - } - } + if inlined.bindings.isEmpty then + inlined.expansion + else + cpy.Block(inlined)(inlined.bindings, inlined.expansion) - /** Removes all Inlined trees, replacing them with blocks. - * Repositions all trees directly inside an inlined expansion of a non empty call to the position of the call. - * Any tree directly inside an empty call (inlined in the inlined code) retains their position. - * - * Until we implement JSR-45, we cannot represent in output positions in other source files. - * So, reposition inlined code from other files with the call position. - */ - class Reposition extends TreeMap(cpyWithNewSource) { - - override def transform(tree: Tree)(using Context): Tree = { - def fixSpan[T <: untpd.Tree](copied: T): T = - copied.withSpan(if tree.source == curSource then tree.span else callSpan) - def finalize(copied: untpd.Tree) = - fixSpan(copied).withAttachmentsFrom(tree).withTypeUnchecked(tree.tpe) - - inContext(ctx.withSource(curSource)) { - tree match - case tree: Ident => finalize(untpd.Ident(tree.name)(curSource)) - case tree: Literal => finalize(untpd.Literal(tree.const)(curSource)) - case tree: This => finalize(untpd.This(tree.qual)(curSource)) - case tree: JavaSeqLiteral => finalize(untpd.JavaSeqLiteral(transform(tree.elems), transform(tree.elemtpt))(curSource)) - case tree: SeqLiteral => finalize(untpd.SeqLiteral(transform(tree.elems), transform(tree.elemtpt))(curSource)) - case tree: Bind => finalize(untpd.Bind(tree.name, transform(tree.body))(curSource)) - case tree: TypeTree => finalize(tpd.TypeTree(tree.tpe)) - case tree: DefTree => super.transform(tree).setDefTree - case EmptyTree => tree - case _ => fixSpan(super.transform(tree)) - } - } - } - (new Reposition).transform(tree) - end reposition /** Leave only a call trace consisting of * - a reference to the top-level class from which the call was inlined, diff --git a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala index 7596549fe401..89ca711c18e6 100644 --- a/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala +++ b/compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala @@ -81,7 +81,7 @@ object PickledQuotes { /** Unpickle the tree contained in the TastyExpr */ def unpickleTerm(pickled: String | List[String], typeHole: TypeHole, termHole: ExprHole)(using Context): Tree = { - val unpickled = withMode(Mode.ReadPositions)(unpickle(pickled, isType = false)) + val unpickled = withMode(Mode.ReadPositions)(unpickle(pickled, isType = false)).asInstanceOf[Inlined] val Inlined(call, Nil, expansion) = unpickled: @unchecked val inlineCtx = inlineContext(call) val expansion1 = spliceTypes(expansion, typeHole)(using inlineCtx) diff --git a/compiler/src/dotty/tools/dotc/transform/ArrayApply.scala b/compiler/src/dotty/tools/dotc/transform/ArrayApply.scala index 872c7cc897de..ee63740afdc2 100644 --- a/compiler/src/dotty/tools/dotc/transform/ArrayApply.scala +++ b/compiler/src/dotty/tools/dotc/transform/ArrayApply.scala @@ -15,7 +15,7 @@ import dotty.tools.dotc.ast.tpd * * Transforms `scala.Array.apply([....])` and `scala.Array.apply(..., [....])` into `[...]` */ -class ArrayApply extends MiniPhase { +class ArrayApply extends MiniPhase: import tpd._ override def phaseName: String = ArrayApply.name @@ -25,14 +25,18 @@ class ArrayApply extends MiniPhase { override def transformApply(tree: tpd.Apply)(using Context): tpd.Tree = if isArrayModuleApply(tree.symbol) then tree.args match { - case StripAscription(Apply(wrapRefArrayMeth, (seqLit: tpd.JavaSeqLiteral) :: Nil)) :: ct :: Nil - if defn.WrapArrayMethods().contains(wrapRefArrayMeth.symbol) && elideClassTag(ct) => + case AppliedLiterals(seqLit) :: ct :: Nil if elideClassTag(ct) => seqLit - case elem0 :: StripAscription(Apply(wrapRefArrayMeth, (seqLit: tpd.JavaSeqLiteral) :: Nil)) :: Nil - if defn.WrapArrayMethods().contains(wrapRefArrayMeth.symbol) => + case InlinedSplice(inlined, seqLit) :: ct :: Nil if elideClassTag(ct) => + tpd.cpy.Inlined(inlined)(inlined.call, inlined.bindings, seqLit) + + case elem0 :: AppliedLiterals(seqLit) :: Nil => tpd.JavaSeqLiteral(elem0 :: seqLit.elems, seqLit.elemtpt) + case elem0 :: InlinedSplice(inlined, seqLit) :: Nil => + tpd.cpy.Inlined(inlined)(inlined.call, inlined.bindings, tpd.JavaSeqLiteral(elem0 :: seqLit.elems, seqLit.elemtpt)) + case _ => tree } @@ -49,6 +53,7 @@ class ArrayApply extends MiniPhase { * - `ClassTag.XYZ` for primitive types */ private def elideClassTag(ct: Tree)(using Context): Boolean = ct match { + case Inlined(_, _, expansion) => elideClassTag(expansion) case Apply(_, rc :: Nil) if ct.symbol == defn.ClassTagModule_apply => rc match { case _: Literal => true // ClassTag.apply(classOf[XYZ]) @@ -63,13 +68,27 @@ class ArrayApply extends MiniPhase { case _ => false } - object StripAscription { - def unapply(tree: Tree)(using Context): Some[Tree] = tree match { - case Typed(expr, _) => unapply(expr) - case _ => Some(tree) - } - } -} + // Match a sequence of literal arguments passed to an Array constructor + private object AppliedLiterals: + + def unapply(tree: Tree)(using Context): Option[tpd.JavaSeqLiteral] = tree match + case Apply(wrapRefArrayMeth, (seqLit: tpd.JavaSeqLiteral) :: Nil) + if defn.WrapArrayMethods().contains(wrapRefArrayMeth.symbol) => + Some(seqLit) + case _ => None + + end AppliedLiterals + + // Match an inlined sequence splice + private object InlinedSplice: + def unapply(tree: Tree)(using Context): Option[(Inlined, tpd.JavaSeqLiteral)] = tree match + case inlined @ Inlined(_, _, Typed(AppliedLiterals(seqLit), _)) => + Some((inlined, seqLit)) + case _ => None + + end InlinedSplice + +end ArrayApply object ArrayApply: val name: String = "arrayApply" diff --git a/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala b/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala index aafb64b33444..ad36544cdec0 100644 --- a/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala +++ b/compiler/src/dotty/tools/dotc/transform/BetaReduce.scala @@ -82,7 +82,7 @@ object BetaReduce: case _ => None case Block(stats, expr) if stats.forall(isPureBinding) => recur(expr, argss).map(cpy.Block(fn)(stats, _)) - case Inlined(call, bindings, expr) if bindings.forall(isPureBinding) => + case fn @ Inlined(call, bindings, expr) if bindings.forall(isPureBinding) => recur(expr, argss).map(cpy.Inlined(fn)(call, bindings, _)) case Typed(expr, tpt) => recur(expr, argss) diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 3ed024429bb6..49a60a24def4 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -895,11 +895,6 @@ object Erasure { tree.typeOpt else valueErasure(tree.typeOpt) - override def typedInlined(tree: untpd.Inlined, pt: Type)(using Context): Tree = - super.typedInlined(tree, pt) match { - case tree: Inlined => Inlines.dropInlined(tree) - } - override def typedValDef(vdef: untpd.ValDef, sym: Symbol)(using Context): Tree = if (sym.isEffectivelyErased) erasedDef(sym) else diff --git a/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala b/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala index 41e5b76ca874..79d1aea9bbc5 100644 --- a/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala +++ b/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala @@ -81,11 +81,12 @@ class ExpandPrivate extends MiniPhase with IdentityDenotTransformer { thisPhase (i < 0 || p1(i) == separatorChar) && (j < 0 || p2(j) == separatorChar) } - + /* assert(d.symbol.source.exists && ctx.owner.source.exists && isSimilar(d.symbol.source.path, ctx.owner.source.path), - s"private ${d.symbol.showLocated} in ${d.symbol.source} accessed from ${ctx.owner.showLocated} in ${ctx.owner.source}") + s"private ${d.symbol.showLocated} in ${d.symbol.source} accessed from ${ctx.owner.showLocated} in ${ctx.owner.source}") */ + d.ensureNotPrivate.installAfter(thisPhase) } diff --git a/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala b/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala index 29572a4ae30d..e1cfdf6a4e10 100644 --- a/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala +++ b/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala @@ -19,6 +19,8 @@ import util.{SourcePosition, SourceFile} import util.Spans.Span import localopt.StringInterpolatorOpt import inlines.Inlines +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.ast.untpd /** Implements code coverage by inserting calls to scala.runtime.coverage.Invoker * ("instruments" the source code). @@ -300,7 +302,62 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: // But PostTyper simplifies tree.call, so we can't report the actual method that was inlined. // In any case, the subtrees need to be repositioned right now, otherwise the // coverage statement will point to a potentially unreachable source file. - val dropped = Inlines.dropInlined(tree) // drop and reposition + + /** Replace `Inlined` node by a block that contains its bindings and expansion */ + def dropInlinedInstrumentaton(inlined: Inlined)(using Context): Tree = + val tree1 = + if inlined.bindings.isEmpty then inlined.expansion + else cpy.Block(inlined)(inlined.bindings, inlined.expansion) + // Reposition in the outer most inlined call + if (enclosingInlineds.nonEmpty) tree1 else reposition(tree1, inlined.span) + + def reposition(tree: Tree, callSpan: Span)(using Context): Tree = + // Reference test tests/run/i4947b + + val curSource = ctx.compilationUnit.source + + // Tree copier that changes the source of all trees to `curSource` + val cpyWithNewSource = new TypedTreeCopier { + override protected def sourceFile(tree: tpd.Tree): SourceFile = curSource + override protected val untpdCpy: untpd.UntypedTreeCopier = new untpd.UntypedTreeCopier { + override protected def sourceFile(tree: untpd.Tree): SourceFile = curSource + } + } + + /** Removes all Inlined trees, replacing them with blocks. + * Repositions all trees directly inside an inlined expansion of a non empty call to the position of the call. + * Any tree directly inside an empty call (inlined in the inlined code) retains their position. + * + */ + class Reposition extends TreeMap(cpyWithNewSource) { + + override def transform(tree: Tree)(using Context): Tree = { + def fixSpan[T <: untpd.Tree](copied: T): T = + copied.withSpan(if tree.source == curSource then tree.span else callSpan) + def finalize(copied: untpd.Tree) = + fixSpan(copied).withAttachmentsFrom(tree).withTypeUnchecked(tree.tpe) + + inContext(ctx.withSource(curSource)) { + tree match + case tree: Ident => finalize(untpd.Ident(tree.name)(curSource)) + case tree: Literal => finalize(untpd.Literal(tree.const)(curSource)) + case tree: This => finalize(untpd.This(tree.qual)(curSource)) + case tree: JavaSeqLiteral => finalize(untpd.JavaSeqLiteral(transform(tree.elems), transform(tree.elemtpt))(curSource)) + case tree: SeqLiteral => finalize(untpd.SeqLiteral(transform(tree.elems), transform(tree.elemtpt))(curSource)) + case tree: Bind => finalize(untpd.Bind(tree.name, transform(tree.body))(curSource)) + case tree: TypeTree => finalize(tpd.TypeTree(tree.tpe)) + case tree: DefTree => super.transform(tree).setDefTree + case EmptyTree => tree + case _ => fixSpan(super.transform(tree)) + } + } + } + + (new Reposition).transform(tree) + end reposition + + + val dropped = dropInlinedInstrumentaton(tree) // drop and reposition transform(dropped) // transform the content of the Inlined // For everything else just recurse and transform diff --git a/compiler/src/dotty/tools/dotc/transform/Mixin.scala b/compiler/src/dotty/tools/dotc/transform/Mixin.scala index 5ca09dd6188f..8361946adb63 100644 --- a/compiler/src/dotty/tools/dotc/transform/Mixin.scala +++ b/compiler/src/dotty/tools/dotc/transform/Mixin.scala @@ -17,6 +17,7 @@ import Names._ import NameKinds._ import NameOps._ import ast.Trees._ +import dotty.tools.dotc.inlines.Inlines object Mixin { val name: String = "mixin" @@ -221,6 +222,7 @@ class Mixin extends MiniPhase with SymTransformer { thisPhase => case _ => } (scall, stats ::: inits, args) + case inlined @ Inlined(_, _, _) => transformConstructor(Inlines.dropInlined(inlined) ) case _ => val Apply(sel @ Select(New(_), nme.CONSTRUCTOR), args) = tree: @unchecked val (callArgs, initArgs) = if (tree.symbol.owner.is(Trait)) (Nil, args) else (args, Nil) diff --git a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala index 8b58f18bca52..b368e47bf0b3 100644 --- a/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala @@ -115,7 +115,7 @@ class PickleQuotes extends MacroTransform { holeContents += content val holeType = getPicklableHoleType(tree.tpe, stagedClasses) val hole = untpd.cpy.Hole(tree)(content = EmptyTree).withType(holeType) - cpy.Inlined(tree)(EmptyTree, Nil, hole) + Inlined(EmptyTree, Nil, hole).withSpan(tree.span) case tree: DefTree => if tree.symbol.isClass then stagedClasses += tree.symbol diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 1d9493e6b1f7..9981a0999ef8 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -250,7 +250,7 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => private object dropInlines extends TreeMap { override def transform(tree: Tree)(using Context): Tree = tree match { - case Inlined(call, _, expansion) => + case tree @ Inlined(call, _, expansion) => val newExpansion = PruneErasedDefs.trivialErasedTree(tree) cpy.Inlined(tree)(call, Nil, newExpansion) case _ => super.transform(tree) @@ -363,7 +363,7 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => case _ => super.transform(tree1) } - case Inlined(call, bindings, expansion) if !call.isEmpty => + case tree @ Inlined(call, bindings, expansion) if !call.isEmpty => val pos = call.sourcePos CrossVersionChecks.checkExperimentalRef(call.symbol, pos) withMode(Mode.InlinedCall)(transform(call)) diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 7df98d57b5ff..f5a818e21070 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -1005,7 +1005,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler def apply(call: Option[Tree], bindings: List[Definition], expansion: Term): Inlined = withDefaultPos(tpd.Inlined(call.getOrElse(tpd.EmptyTree), bindings.map { case b: tpd.MemberDef => b }, xCheckMacroValidExpr(expansion))) def copy(original: Tree)(call: Option[Tree], bindings: List[Definition], expansion: Term): Inlined = - tpd.cpy.Inlined(original)(call.getOrElse(tpd.EmptyTree), bindings.asInstanceOf[List[tpd.MemberDef]], xCheckMacroValidExpr(expansion)) + tpd.cpy.Inlined(original.asInstanceOf[Inlined])(call.getOrElse(tpd.EmptyTree), bindings.asInstanceOf[List[tpd.MemberDef]], xCheckMacroValidExpr(expansion)) def unapply(x: Inlined): (Option[Tree /* Term | TypeTree */], List[Definition], Term) = (optional(x.call), x.bindings, x.body) end Inlined diff --git a/tests/run-macros/i4947e.check b/tests/run-macros/i4947e.check index 1e67df692f1e..2c68aad8cb16 100644 --- a/tests/run-macros/i4947e.check +++ b/tests/run-macros/i4947e.check @@ -1,8 +1,8 @@ -assertImpl: Test$.main(Test_2.scala:7) +assertImpl: Test$.main(Test_2.scala:18) true -assertImpl: Test$.main(Test_2.scala:8) +assertImpl: Test$.main(Test_2.scala:23) false -assertImpl: Test$.main(Test_2.scala:9) +assertImpl: Test$.main(Test_2.scala:28) hi: Test$.main(Test_2.scala:10) hi again: Test$.main(Test_2.scala:11) false diff --git a/tests/run-macros/i4947f.check b/tests/run-macros/i4947f.check index 1e67df692f1e..df10159e803a 100644 --- a/tests/run-macros/i4947f.check +++ b/tests/run-macros/i4947f.check @@ -1,8 +1,8 @@ -assertImpl: Test$.main(Test_2.scala:7) +assertImpl: Test$.main(Test_2.scala:17) true -assertImpl: Test$.main(Test_2.scala:8) +assertImpl: Test$.main(Test_2.scala:21) false -assertImpl: Test$.main(Test_2.scala:9) +assertImpl: Test$.main(Test_2.scala:25) hi: Test$.main(Test_2.scala:10) hi again: Test$.main(Test_2.scala:11) false diff --git a/tests/run-macros/splice-position.check b/tests/run-macros/splice-position.check index a37b1fbb806d..27d1ddb12840 100644 --- a/tests/run-macros/splice-position.check +++ b/tests/run-macros/splice-position.check @@ -1,2 +1,2 @@ -Test$.main(Test.scala:5) -Test$.main(Test.scala:6) +Test$.main(Test.scala:8) +Test$.main(Test.scala:10) diff --git a/tests/run/assert-stack.check b/tests/run/assert-stack.check index c2c97450fd57..6e773ed69d8b 100644 --- a/tests/run/assert-stack.check +++ b/tests/run/assert-stack.check @@ -2,5 +2,5 @@ scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:8) Test$.main(assert-stack.scala:7) scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:11) -Test$.main(assert-stack.scala:12) +Test$.main(assert-stack.scala:24) diff --git a/tests/run/i4947b.check b/tests/run/i4947b.check index 3950d4e0b7a1..a721d04c920a 100644 --- a/tests/run/i4947b.check +++ b/tests/run/i4947b.check @@ -1,36 +1,36 @@ -track: Test$.main(Test_2.scala:5) -track: Test$.main(Test_2.scala:5) +track: Test$.main(Test_2.scala:25) +track: Test$.main(Test_2.scala:26) main1: Test$.main(Test_2.scala:6) main2: Test$.main(Test_2.scala:7) -track: Test$.main(Test_2.scala:9) -track: Test$.main(Test_2.scala:9) -track: Test$.main(Test_2.scala:10) -track: Test$.main(Test_2.scala:10) +track: Test$.main(Test_2.scala:30) +track: Test$.main(Test_2.scala:31) +track: Test$.main(Test_2.scala:35) +track: Test$.main(Test_2.scala:36) main3: Test$.main(Test_2.scala:11) main4: Test$.main(Test_2.scala:12) track (i = 0): Test$.main(Test_2.scala:15) track (i = 0): Test$.main(Test_2.scala:15) -track: Test$.main(Test_2.scala:15) -track: Test$.main(Test_2.scala:15) -fact: Test$.main(Test_2.scala:15) +track: Test$.main(Test_2.scala:53) +track: Test$.main(Test_2.scala:54) +fact: Test$.main(Test_2.scala:57) track (i = 2): Test$.main(Test_2.scala:16) track (i = 2): Test$.main(Test_2.scala:16) -track: Test$.main(Test_2.scala:16) -track: Test$.main(Test_2.scala:16) -fact: Test$.main(Test_2.scala:16) +track: Test$.main(Test_2.scala:72) +track: Test$.main(Test_2.scala:73) +fact: Test$.main(Test_2.scala:76) main1 (i = -1): Test$.main(Test_2.scala:17) main2 (i = -1): Test$.main(Test_2.scala:18) -track (i = 1): Test$.main(Test_2.scala:16) -track (i = 1): Test$.main(Test_2.scala:16) -track: Test$.main(Test_2.scala:16) -track: Test$.main(Test_2.scala:16) -fact: Test$.main(Test_2.scala:16) +track (i = 1): Test$.main(Test_2.scala:90) +track (i = 1): Test$.main(Test_2.scala:91) +track: Test$.main(Test_2.scala:93) +track: Test$.main(Test_2.scala:94) +fact: Test$.main(Test_2.scala:97) main1 (i = -1): Test$.main(Test_2.scala:17) main2 (i = -1): Test$.main(Test_2.scala:18) -track (i = 0): Test$.main(Test_2.scala:16) -track (i = 0): Test$.main(Test_2.scala:16) -track: Test$.main(Test_2.scala:16) -track: Test$.main(Test_2.scala:16) -fact: Test$.main(Test_2.scala:16) +track (i = 0): Test$.main(Test_2.scala:113) +track (i = 0): Test$.main(Test_2.scala:114) +track: Test$.main(Test_2.scala:116) +track: Test$.main(Test_2.scala:117) +fact: Test$.main(Test_2.scala:120) main1 (i = -1): Test$.main(Test_2.scala:17) main2 (i = -1): Test$.main(Test_2.scala:18)