From 209c0c33dc5fd16cc53147a0d0cdb38fda945a49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Thu, 11 Feb 2021 16:28:50 +0100 Subject: [PATCH 01/44] Include duplicates of inlined calls in SMAP --- .../tools/backend/jvm/InlinedPositioner.scala | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala diff --git a/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala b/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala new file mode 100644 index 000000000000..3ad75a7f0a00 --- /dev/null +++ b/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala @@ -0,0 +1,109 @@ +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.typer.Inliner.InliningPosition +import collection.mutable + +import scala.collection.mutable.StringBuilder + +class InlinedsPositioner(cunit: CompilationUnit)(using Context): + private case class Request(targetPos: SourcePosition, origPos: SourcePosition, 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" + + private val requests = mutable.ListBuffer.empty[Request] + private var lastLine = cunit.tpdTree.sourcePos.endLine + + private def allocate(origPos: SourcePosition): Int = + val line = lastLine + 1 + lastLine += origPos.lines.length + line + + private class RequestCollector(enclosingFile: SourceFile) extends TreeTraverser: + override def traverse(tree: Tree)(using Context): Unit = + if tree.source != enclosingFile then + tree.getAttachment(InliningPosition) match + case Some(targetPos) => + val firstFakeLine = allocate(tree.sourcePos) + requests += Request(targetPos, tree.sourcePos, firstFakeLine) + RequestCollector(tree.source).traverseChildren(tree) + case None => + // Not exactly sure in which cases it is happening. Should we report warning? + RequestCollector(tree.source).traverseChildren(tree) + else traverseChildren(tree) + end RequestCollector + + RequestCollector(cunit.source).traverse(cunit.tpdTree) + + def debugExtension: Option[String] = Option.when(requests.nonEmpty) { + val scalaStratum = + val files = cunit.source :: requests.map(_.origPos.source).distinct.filter(_ != cunit.source).toList + val mappings = requests.map { case Request(_, origPos, firstFakeLine) => + Mapping(origPos.startLine, files.indexOf(origPos.source) + 1, origPos.lines.length, firstFakeLine, 1) + }.toList + Stratum("Scala", files.zipWithIndex.map { case (f, n) => File(n + 1, f.name, None) }, Mapping(0, 1, cunit.tpdTree.sourcePos.lines.length, 0, 1) +: mappings) + + val debugStratum = + val mappings = requests.map { case Request(targetPos, origPos, firstFakeLine) => + Mapping(targetPos.startLine, 1, 1, firstFakeLine, origPos.lines.length) + }.toList + Stratum("ScalaDebug", File(1, cunit.source.name, None) :: Nil, mappings) + + + val b = new StringBuilder + b ++= "SMAP\n" + scalaStratum.write(b) + debugStratum.write(b) + b.toString + } + + + From 3ffee6d556cfe108d9afdfc1d973682443f8a355 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Thu, 11 Feb 2021 16:32:37 +0100 Subject: [PATCH 02/44] Add missing info to SMAP header --- compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala b/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala index 3ad75a7f0a00..66ae3a2c5260 100644 --- a/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala +++ b/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala @@ -100,6 +100,9 @@ class InlinedsPositioner(cunit: CompilationUnit)(using Context): val b = new StringBuilder b ++= "SMAP\n" + b ++= cunit.source.name + b += '\n' + b ++= "Scala\n" scalaStratum.write(b) debugStratum.write(b) b.toString From 99312999eb1d2b7a42266b2dfcabbee7ea215d87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Mon, 15 Feb 2021 13:03:33 +0100 Subject: [PATCH 03/44] Update information about lines for inlined content --- .../src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala | 8 ++++++-- .../src/dotty/tools/backend/jvm/InlinedPositioner.scala | 5 +++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala index 0a11fb898b48..ae9d24cb9630 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala @@ -91,6 +91,8 @@ trait BCodeSkelBuilder extends BCodeHelpers { var isCZParcelable = false var isCZStaticModule = false + var inlinedsPositioner: InlinedsPositioner = null + /* ---------------- idiomatic way to ask questions to typer ---------------- */ def paramTKs(app: Apply, take: Int = -1): List[BType] = app match { @@ -276,7 +278,8 @@ trait BCodeSkelBuilder extends BCodeHelpers { superClass, interfaceNames.toArray) if (emitSource) { - cnode.visitSource(cunit.source.file.name, null /* SourceDebugExtension */) + inlinedsPositioner = InlinedsPositioner(cunit) + cnode.visitSource(cunit.source.file.name, inlinedsPositioner.debugExtension.orNull) } enclosingMethodAttribute(claszSymbol, internalName, asmMethodType(_).descriptor) match { @@ -563,7 +566,8 @@ trait BCodeSkelBuilder extends BCodeHelpers { } if (!emitLines || !tree.span.exists) return; - val nr = ctx.source.offsetToLine(tree.span.point) + 1 + val nr = if (tree.source != cunit.source) inlinedsPositioner.lineFor(tree.sourcePos) else ctx.source.offsetToLine(tree.span.point) + 1 + if (nr != lastEmittedLineNr) { lastEmittedLineNr = nr getNonLabelNode(lastInsn) match { diff --git a/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala b/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala index 66ae3a2c5260..c8d0edd4cf07 100644 --- a/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala +++ b/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala @@ -108,5 +108,10 @@ class InlinedsPositioner(cunit: CompilationUnit)(using Context): b.toString } + def lineFor(sourcePos: SourcePosition): Int = + val request = requests.find(_.origPos.contains(sourcePos)).get + val offset = sourcePos.startLine - request.origPos.startLine + request.firstFakeLine + offset + 1 + From 43e063ffe350340df86f52e69af9125923b20de8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Mon, 15 Feb 2021 14:22:53 +0100 Subject: [PATCH 04/44] Make InlinePositioner not crash on not found inline request --- .../dotty/tools/backend/jvm/InlinedPositioner.scala | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala b/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala index c8d0edd4cf07..7733161b386c 100644 --- a/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala +++ b/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala @@ -6,6 +6,7 @@ import dotc.CompilationUnit import dotc.ast.tpd._ import dotc.util.{ SourcePosition, SourceFile } import dotc.core.Contexts._ +import dotc.report import dotc.typer.Inliner.InliningPosition import collection.mutable @@ -108,10 +109,14 @@ class InlinedsPositioner(cunit: CompilationUnit)(using Context): b.toString } - def lineFor(sourcePos: SourcePosition): Int = - val request = requests.find(_.origPos.contains(sourcePos)).get - val offset = sourcePos.startLine - request.origPos.startLine - request.firstFakeLine + offset + 1 + def lineFor(sourcePos: SourcePosition): Option[Int] = + requests.find(_.origPos.contains(sourcePos)) match + case Some(request) => + val offset = sourcePos.startLine - request.origPos.startLine + Some(request.firstFakeLine + offset + 1) + case None => + report.inform(s"${sourcePos.show} was supposed to be inlined in ${cunit.source} but it cannot be found.") + None From 903a760ea5c1be3230f30c4db29a71de5281fb79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Sun, 21 Feb 2021 11:18:08 +0100 Subject: [PATCH 05/44] Solve not registering inlining position for tuples also removes code that was relocationg inlined calls but now is unreachable --- .../tools/backend/jvm/InlinedPositioner.scala | 7 +- .../src/dotty/tools/dotc/typer/Inliner.scala | 1585 +++++++++++++++++ 2 files changed, 1588 insertions(+), 4 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/typer/Inliner.scala diff --git a/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala b/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala index 7733161b386c..332bf18b0ef6 100644 --- a/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala +++ b/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala @@ -68,6 +68,8 @@ class InlinedsPositioner(cunit: CompilationUnit)(using Context): lastLine += origPos.lines.length line +// if (source.name contains "Tuple.sca") && startLine == 35 && endLine == 35 then + private class RequestCollector(enclosingFile: SourceFile) extends TreeTraverser: override def traverse(tree: Tree)(using Context): Unit = if tree.source != enclosingFile then @@ -115,8 +117,5 @@ class InlinedsPositioner(cunit: CompilationUnit)(using Context): val offset = sourcePos.startLine - request.origPos.startLine Some(request.firstFakeLine + offset + 1) case None => - report.inform(s"${sourcePos.show} was supposed to be inlined in ${cunit.source} but it cannot be found.") + report.warning(s"${sourcePos.show} was inlined in ${cunit.source} but its inlining position was not recorded.") None - - - diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala new file mode 100644 index 000000000000..d3ad10dbde9e --- /dev/null +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -0,0 +1,1585 @@ +package dotty.tools +package dotc +package typer + +import ast.{TreeInfo, tpd, _} +import Trees._ +import core._ +import Flags._ +import Symbols._ +import Types._ +import Decorators._ +import Constants._ +import StagingContext._ +import StdNames._ +import transform.SymUtils._ +import Contexts._ +import Names.{Name, TermName} +import NameKinds.{InlineAccessorName, InlineBinderName, InlineScrutineeName, BodyRetainerName} +import ProtoTypes.shallowSelectionProto +import Annotations.Annotation +import SymDenotations.SymDenotation +import Inferencing.isFullyDefined +import config.Printers.inlining +import ErrorReporting.errorTree +import dotty.tools.dotc.util.{SimpleIdentityMap, SimpleIdentitySet, EqHashMap, SourceFile, SourcePosition, SrcPos} +import dotty.tools.dotc.parsing.Parsers.Parser +import Nullables._ +import transform.{PostTyper, Inlining} + +import collection.mutable +import reporting.trace +import util.Spans.Span +import util.Property +import dotty.tools.dotc.transform.{Splicer, TreeMapWithStages} +import quoted.QuoteUtils + +object Inliner { + import tpd._ + + val InliningPosition = new Property.StickyKey[SourcePosition] + + /** `sym` is an inline method with a known body to inline. + */ + def hasBodyToInline(sym: SymDenotation)(using Context): Boolean = + sym.isInlineMethod && sym.hasAnnotation(defn.BodyAnnot) + + /** The body to inline for method `sym`, or `EmptyTree` if none exists. + * @pre hasBodyToInline(sym) + */ + def bodyToInline(sym: SymDenotation)(using Context): Tree = + if hasBodyToInline(sym) then + sym.getAnnotation(defn.BodyAnnot).get.tree + else + EmptyTree + + /** Are we in an inline method body? */ + def inInlineMethod(using Context): Boolean = + ctx.owner.ownersIterator.exists(_.isInlineMethod) + + /** Can a call to method `meth` be inlined? */ + def isInlineable(meth: Symbol)(using Context): Boolean = + meth.is(Inline) && meth.hasAnnotation(defn.BodyAnnot) && !inInlineMethod + + /** Should call be inlined in this context? */ + def needsInlining(tree: Tree)(using Context): Boolean = tree match { + case Block(_, expr) => needsInlining(expr) + case _ => + isInlineable(tree.symbol) + && !tree.tpe.widenTermRefExpr.isInstanceOf[MethodOrPoly] + && StagingContext.level == 0 + && ( + ctx.phase == Phases.inliningPhase + || (ctx.phase == Phases.typerPhase && needsTransparentInlining(tree)) + ) + && !ctx.typer.hasInliningErrors + } + + private def needsTransparentInlining(tree: Tree)(using Context): Boolean = + tree.symbol.is(Transparent) + || ctx.mode.is(Mode.ForceInline) + || ctx.settings.YforceInlineWhileTyping.value + + /** Try to inline a call to an inline method. Fail with error if the maximal + * inline depth is exceeded. + * + * @param tree The call to inline + * @param pt The expected type of the call. + * @return An `Inlined` node that refers to the original call and the inlined bindings + * and body that replace it. + */ + def inlineCall(tree: Tree)(using Context): Tree = { + if tree.symbol.denot != SymDenotations.NoDenotation + && tree.symbol.effectiveOwner == defn.CompiletimeTestingPackage.moduleClass + then + if (tree.symbol == defn.CompiletimeTesting_typeChecks) return Intrinsics.typeChecks(tree) + if (tree.symbol == defn.CompiletimeTesting_typeCheckErrors) return Intrinsics.typeCheckErrors(tree) + + + /** Set the position of all trees logically contained in the expansion of + * inlined call `call` to the position of `call`. This transform is necessary + * when lifting bindings from the expansion to the outside of the call. + */ + def liftFromInlined(call: Tree) = new TreeMap: + override def transform(t: Tree)(using Context) = + if call.span.exists then + t match + case Inlined(t, Nil, expr) if t.isEmpty => expr + case _ if t.isEmpty => t + case _ => super.transform(t.withSpan(call.span)) + else t + end liftFromInlined + + val bindings = new mutable.ListBuffer[Tree] + + /** Lift bindings around inline call or in its function part to + * the `bindings` buffer. This is done as an optimization to keep + * inline call expansions smaller. + */ + def liftBindings(tree: Tree, liftPos: Tree => Tree): Tree = tree match { + case Block(stats, expr) => + bindings ++= stats.map(liftPos) + liftBindings(expr, liftPos) + case Inlined(call, stats, expr) => + bindings ++= stats.map(liftPos) + val lifter = liftFromInlined(call) + cpy.Inlined(tree)(call, Nil, liftBindings(expr, liftFromInlined(call).transform(_))) + case Apply(fn, args) => + cpy.Apply(tree)(liftBindings(fn, liftPos), args) + case TypeApply(fn, args) => + cpy.TypeApply(tree)(liftBindings(fn, liftPos), args) + case Select(qual, name) => + cpy.Select(tree)(liftBindings(qual, liftPos), name) + case _ => + tree + } + + // assertAllPositioned(tree) // debug + val tree1 = liftBindings(tree, identity) + val tree2 = + if bindings.nonEmpty then + cpy.Block(tree)(bindings.toList, inlineCall(tree1)) + else if enclosingInlineds.length < ctx.settings.XmaxInlines.value && !reachedInlinedTreesLimit then + val body = bodyToInline(tree.symbol) // can typecheck the tree and thereby produce errors + new Inliner(tree, body).inlined(tree.srcPos) + else + val (reason, setting) = + if reachedInlinedTreesLimit then ("inlined trees", ctx.settings.XmaxInlinedTrees) + else ("successive inlines", ctx.settings.XmaxInlines) + errorTree( + tree, + i"""|Maximal number of $reason (${setting.value}) exceeded, + |Maybe this is caused by a recursive inline method? + |You can use ${setting.name} to change the limit.""", + (tree :: enclosingInlineds).last.srcPos + ) + tree2 + } + + /** Try to inline a pattern with an inline unapply method. Fail with error if the maximal + * inline depth is exceeded. + * + * @param unapp The tree of the pattern to inline + * @return An `Unapply` with a `fun` containing the inlined call to the unapply + */ + def inlinedUnapply(unapp: tpd.UnApply)(using Context): Tree = { + // We cannot inline the unapply directly, since the pattern matcher relies on unapply applications + // as signposts what to do. On the other hand, we can do the inlining only in typer, not afterwards. + // So the trick is to create a "wrapper" unapply in an anonymous class that has the inlined unapply + // as its right hand side. The call to the wrapper unapply serves as the signpost for pattern matching. + // After pattern matching, the anonymous class is removed in phase InlinePatterns with a beta reduction step. + // + // An inline unapply `P.unapply` in a plattern `P(x1,x2,...)` is transformed into + // `{ class $anon { def unapply(t0: T0)(using t1: T1, t2: T2, ...): R = P.unapply(t0)(using t1, t2, ...) }; new $anon }.unapply` + // and the call `P.unapply(x1, x2, ...)` is inlined. + // This serves as a placeholder for the inlined body until the `patternMatcher` phase. After pattern matcher + // transforms the patterns into terms, the `inlinePatterns` phase removes this anonymous class by β-reducing + // the call to the `unapply`. + + val UnApply(fun, implicits, patterns) = unapp + val sym = unapp.symbol + val cls = newNormalizedClassSymbol(ctx.owner, tpnme.ANON_CLASS, Synthetic | Final, List(defn.ObjectType), coord = sym.coord) + val constr = newConstructor(cls, Synthetic, Nil, Nil, coord = sym.coord).entered + + val targs = fun match + case TypeApply(_, targs) => targs + case _ => Nil + val unapplyInfo = sym.info match + case info: PolyType => info.instantiate(targs.map(_.tpe)) + case info => info + + val unappplySym = newSymbol(cls, sym.name.toTermName, Synthetic | Method, unapplyInfo, coord = sym.coord).entered + val unapply = DefDef(unappplySym, argss => + inlineCall(fun.appliedToArgss(argss).withSpan(unapp.span))(using ctx.withOwner(unappplySym)) + ) + val cdef = ClassDef(cls, DefDef(constr), List(unapply)) + val newUnapply = Block(cdef :: Nil, New(cls.typeRef, Nil)) + val newFun = newUnapply.select(unappplySym).withSpan(unapp.span) + cpy.UnApply(unapp)(newFun, implicits, patterns) + } + + /** For a retained inline method, another method that keeps track of + * the body that is kept at runtime. For instance, an inline method + * + * inline override def f(x: T) = b + * + * is complemented by the body retainer method + * + * private def f$retainedBody(x: T) = f(x) + * + * where the call `f(x)` is inline-expanded. This body is then transferred + * back to `f` at erasure, using method addRetainedInlineBodies. + */ + def bodyRetainer(mdef: DefDef)(using Context): DefDef = + val meth = mdef.symbol.asTerm + + val retainer = meth.copy( + name = BodyRetainerName(meth.name), + flags = meth.flags &~ (Inline | Macro | Override) | Private, + coord = mdef.rhs.span.startPos).asTerm + retainer.deriveTargetNameAnnotation(meth, name => BodyRetainerName(name.asTermName)) + DefDef(retainer, prefss => + inlineCall( + ref(meth).appliedToArgss(prefss).withSpan(mdef.rhs.span.startPos))( + using ctx.withOwner(retainer))) + .showing(i"retainer for $meth: $result", inlining) + + /** Replace `Inlined` node by a block that contains its bindings and expansion */ + def dropInlined(inlined: Inlined)(using Context): Tree = + // In case that bindings are present we are adding SourcePosition attachement both to the resulting block and to the expansion + // as in some cases the block is removed in one of later phases and attachment is lost. + val tree1 = + if inlined.bindings.isEmpty then inlined.expansion + else cpy.Block(inlined)(inlined.bindings, inlined.expansion.withAttachment(InliningPosition, inlined.sourcePos)) + tree1.withAttachment(InliningPosition, inlined.sourcePos) + + /** 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. + * Note: For macros it returns a Select and for other inline methods it returns an Ident (this distinction is only temporary to be able to run YCheckPositions) + */ + def inlineCallTrace(callSym: Symbol, pos: SourcePosition)(using Context): Tree = { + assert(ctx.source == pos.source) + if (callSym.is(Macro)) ref(callSym.topLevelClass.owner).select(callSym.topLevelClass.name).withSpan(pos.span) + else Ident(callSym.topLevelClass.typeRef).withSpan(pos.span) + } + + object Intrinsics { + import dotty.tools.dotc.reporting.Diagnostic.Error + private enum ErrorKind: + case Parser, Typer + + private def compileForErrors(tree: Tree)(using Context): List[(ErrorKind, Error)] = + assert(tree.symbol == defn.CompiletimeTesting_typeChecks || tree.symbol == defn.CompiletimeTesting_typeCheckErrors) + def stripTyped(t: Tree): Tree = t match { + case Typed(t2, _) => stripTyped(t2) + case Block(Nil, t2) => stripTyped(t2) + case Inlined(_, Nil, t2) => stripTyped(t2) + case _ => t + } + + val Apply(_, codeArg :: Nil) = tree + val codeArg1 = stripTyped(codeArg.underlying) + val underlyingCodeArg = + if Inliner.isInlineable(codeArg1.symbol) then stripTyped(Inliner.inlineCall(codeArg1)) + else codeArg1 + + ConstFold(underlyingCodeArg).tpe.widenTermRefExpr match { + case ConstantType(Constant(code: String)) => + val source2 = SourceFile.virtual("tasty-reflect", code) + inContext(ctx.fresh.setNewTyperState().setTyper(new Typer).setSource(source2)) { + val tree2 = new Parser(source2).block() + if ctx.reporter.allErrors.nonEmpty then + ctx.reporter.allErrors.map((ErrorKind.Parser, _)) + else + val tree3 = ctx.typer.typed(tree2) + ctx.base.postTyperPhase match + case postTyper: PostTyper if ctx.reporter.allErrors.isEmpty => + val tree4 = atPhase(postTyper) { postTyper.newTransformer.transform(tree3) } + ctx.base.inliningPhase match + case inlining: Inlining if ctx.reporter.allErrors.isEmpty => + atPhase(inlining) { inlining.newTransformer.transform(tree4) } + case _ => + case _ => + ctx.reporter.allErrors.map((ErrorKind.Typer, _)) + } + case t => + report.error(em"argument to compileError must be a statically known String but was: $codeArg", codeArg1.srcPos) + Nil + } + + private def packError(kind: ErrorKind, error: Error)(using Context): Tree = + def lit(x: Any) = Literal(Constant(x)) + val constructor: Tree = ref(defn.CompiletimeTesting_Error_apply) + val parserErrorKind: Tree = ref(defn.CompiletimeTesting_ErrorKind_Parser) + val typerErrorKind: Tree = ref(defn.CompiletimeTesting_ErrorKind_Typer) + + constructor.appliedTo( + lit(error.message), + lit(error.pos.lineContent.reverse.dropWhile("\n ".contains).reverse), + lit(error.pos.column), + if kind == ErrorKind.Parser then parserErrorKind else typerErrorKind) + + private def packErrors(errors: List[(ErrorKind, Error)])(using Context): Tree = + val individualErrors: List[Tree] = errors.map(packError) + val errorTpt = ref(defn.CompiletimeTesting_ErrorClass) + mkList(individualErrors, errorTpt) + + /** Expand call to scala.compiletime.testing.typeChecks */ + def typeChecks(tree: Tree)(using Context): Tree = + val errors = compileForErrors(tree) + Literal(Constant(errors.isEmpty)).withSpan(tree.span) + + /** Expand call to scala.compiletime.testing.typeCheckErrors */ + def typeCheckErrors(tree: Tree)(using Context): Tree = + val errors = compileForErrors(tree) + packErrors(errors) + + /** Expand call to scala.compiletime.codeOf */ + def codeOf(arg: Tree, pos: SrcPos)(using Context): Tree = + Literal(Constant(arg.show)).withSpan(pos.span) + } + + extension (tp: Type) { + + /** same as widenTermRefExpr, but preserves modules and singleton enum values */ + private final def widenInlineScrutinee(using Context): Type = tp.stripTypeVar match { + case tp: TermRef => + val sym = tp.termSymbol + if sym.isAllOf(EnumCase, butNot=JavaDefined) || sym.is(Module) then tp + else if !tp.isOverloaded then tp.underlying.widenExpr.widenInlineScrutinee + else tp + case _ => tp + } + + } + +} + +/** Produces an inlined version of `call` via its `inlined` method. + * + * @param call the original call to an inlineable method + * @param rhsToInline the body of the inlineable method that replaces the call. + */ +class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { + import tpd._ + import Inliner._ + + private val methPart = funPart(call) + private val callTypeArgs = typeArgss(call).flatten + private val callValueArgss = termArgss(call) + private val inlinedMethod = methPart.symbol + private val inlineCallPrefix = + qualifier(methPart).orElse(This(inlinedMethod.enclosingClass.asClass)) + + inlining.println(i"-----------------------\nInlining $call\nWith RHS $rhsToInline") + + // Make sure all type arguments to the call are fully determined, + // but continue if that's not achievable (or else i7459.scala would crash). + for arg <- callTypeArgs do + isFullyDefined(arg.tpe, ForceDegree.flipBottom) + + /** A map from parameter names of the inlineable method to references of the actual arguments. + * For a type argument this is the full argument type. + * For a value argument, it is a reference to either the argument value + * (if the argument is a pure expression of singleton type), or to `val` or `def` acting + * as a proxy (if the argument is something else). + */ + private val paramBinding = new mutable.HashMap[Name, Type] + + /** A map from parameter names of the inlineable method to spans of the actual arguments */ + private val paramSpan = new mutable.HashMap[Name, Span] + + /** A map from references to (type and value) parameters of the inlineable method + * to their corresponding argument or proxy references, as given by `paramBinding`. + */ + private val paramProxy = new mutable.HashMap[Type, Type] + + /** A map from the classes of (direct and outer) this references in `rhsToInline` + * to references of their proxies. + * Note that we can't index by the ThisType itself since there are several + * possible forms to express what is logicaly the same ThisType. E.g. + * + * ThisType(TypeRef(ThisType(p), cls)) + * + * vs + * + * ThisType(TypeRef(TermRef(ThisType(), p), cls)) + * + * These are different (wrt ==) types but represent logically the same key + */ + private val thisProxy = new mutable.HashMap[ClassSymbol, TermRef] + + /** A buffer for bindings that define proxies for actual arguments */ + private val bindingsBuf = new mutable.ListBuffer[ValOrDefDef] + + private def newSym(name: Name, flags: FlagSet, info: Type)(using Context): Symbol = + newSymbol(ctx.owner, name, flags, info, coord = call.span) + + /** A binding for the parameter of an inline method. This is a `val` def for + * by-value parameters and a `def` def for by-name parameters. `val` defs inherit + * inline annotations from their parameters. The generated `def` is appended + * to `bindingsBuf`. + * @param name the name of the parameter + * @param formal the type of the parameter + * @param arg the argument corresponding to the parameter + * @param bindingsBuf the buffer to which the definition should be appended + */ + private def paramBindingDef(name: Name, formal: Type, arg0: Tree, + bindingsBuf: mutable.ListBuffer[ValOrDefDef])(using Context): ValOrDefDef = { + val isByName = formal.dealias.isInstanceOf[ExprType] + val arg = arg0 match { + case Typed(arg1, tpt) if tpt.tpe.isRepeatedParam && arg1.tpe.derivesFrom(defn.ArrayClass) => + wrapArray(arg1, arg0.tpe.elemType) + case _ => arg0 + } + val argtpe = arg.tpe.dealiasKeepAnnots.translateFromRepeated(toArray = false) + val argIsBottom = argtpe.isBottomTypeAfterErasure + val bindingType = + if argIsBottom then formal + else if isByName then ExprType(argtpe.widen) + else argtpe.widen + var bindingFlags: FlagSet = InlineProxy + if formal.widenExpr.hasAnnotation(defn.InlineParamAnnot) then + bindingFlags |= Inline + if isByName then + bindingFlags |= Method + val boundSym = newSym(InlineBinderName.fresh(name.asTermName), bindingFlags, bindingType).asTerm + val binding = { + var newArg = arg.changeOwner(ctx.owner, boundSym) + if bindingFlags.is(Inline) && argIsBottom then + newArg = Typed(newArg, TypeTree(formal)) // type ascribe RHS to avoid type errors in expansion. See i8612.scala + if isByName then DefDef(boundSym, newArg) + else ValDef(boundSym, newArg) + }.withSpan(boundSym.span) + inlining.println(i"parameter binding: $binding, $argIsBottom") + bindingsBuf += binding + binding + } + + /** Populate `paramBinding` and `bindingsBuf` by matching parameters with + * corresponding arguments. `bindingbuf` will be further extended later by + * proxies to this-references. Issue an error if some arguments are missing. + */ + private def computeParamBindings( + tp: Type, targs: List[Tree], argss: List[List[Tree]], formalss: List[List[Type]]): Boolean = + tp match + case tp: PolyType => + tp.paramNames.lazyZip(targs).foreach { (name, arg) => + paramSpan(name) = arg.span + paramBinding(name) = arg.tpe.stripTypeVar + } + computeParamBindings(tp.resultType, targs.drop(tp.paramNames.length), argss, formalss) + case tp: MethodType => + if argss.isEmpty then + report.error(i"missing arguments for inline method $inlinedMethod", call.srcPos) + false + else + tp.paramNames.lazyZip(formalss.head).lazyZip(argss.head).foreach { (name, formal, arg) => + paramSpan(name) = arg.span + paramBinding(name) = arg.tpe.dealias match + case _: SingletonType if isIdempotentPath(arg) => + arg.tpe + case _ => + paramBindingDef(name, formal, arg, bindingsBuf).symbol.termRef + } + computeParamBindings(tp.resultType, targs, argss.tail, formalss.tail) + case _ => + assert(targs.isEmpty) + assert(argss.isEmpty) + true + + // Compute val-definitions for all this-proxies and append them to `bindingsBuf` + private def computeThisBindings() = { + // All needed this-proxies, paired-with and sorted-by nesting depth of + // the classes they represent (innermost first) + val sortedProxies = thisProxy.toList + .map((cls, proxy) => (cls.ownersIterator.length, proxy.symbol)) + .sortBy(-_._1) + + var lastSelf: Symbol = NoSymbol + var lastLevel: Int = 0 + for ((level, selfSym) <- sortedProxies) { + lazy val rhsClsSym = selfSym.info.widenDealias.classSymbol + val rhs = selfSym.info.dealias match + case info: TermRef if info.isStable => + ref(info) + case info => + val rhsClsSym = info.widenDealias.classSymbol + if rhsClsSym.is(Module) && rhsClsSym.isStatic then + ref(rhsClsSym.sourceModule) + else if lastSelf.exists then + ref(lastSelf).outerSelect(lastLevel - level, selfSym.info) + else + inlineCallPrefix + val binding = ValDef(selfSym.asTerm, QuoteUtils.changeOwnerOfTree(rhs, selfSym)).withSpan(selfSym.span) + bindingsBuf += binding + inlining.println(i"proxy at $level: $selfSym = ${bindingsBuf.last}") + lastSelf = selfSym + lastLevel = level + } + } + + private def canElideThis(tpe: ThisType): Boolean = + inlineCallPrefix.tpe == tpe && ctx.owner.isContainedIn(tpe.cls) || + tpe.cls.isContainedIn(inlinedMethod) || + tpe.cls.is(Package) + + /** Very similar to TreeInfo.isPureExpr, but with the following inliner-only exceptions: + * - synthetic case class apply methods, when the case class constructor is empty, are + * elideable but not pure. Elsewhere, accessing the apply method might cause the initialization + * of a containing object so they are merely idempotent. + */ + object isElideableExpr { + def isStatElideable(tree: Tree)(using Context): Boolean = unsplice(tree) match { + case EmptyTree + | TypeDef(_, _) + | Import(_, _) + | DefDef(_, _, _, _) => + true + case vdef @ ValDef(_, _, _) => + if (vdef.symbol.flags is Mutable) false else apply(vdef.rhs) + case _ => + false + } + + def apply(tree: Tree): Boolean = unsplice(tree) match { + case EmptyTree + | This(_) + | Super(_, _) + | Literal(_) => + true + case Ident(_) => + isPureRef(tree) || tree.symbol.isAllOf(Inline | Param) + case Select(qual, _) => + if (tree.symbol.is(Erased)) true + else isPureRef(tree) && apply(qual) + case New(_) | Closure(_, _, _) => + true + case TypeApply(fn, _) => + if (fn.symbol.is(Erased) || fn.symbol == defn.QuotedTypeModule_of) true else apply(fn) + case Apply(fn, args) => + val isCaseClassApply = { + val cls = tree.tpe.classSymbol + val meth = fn.symbol + meth.name == nme.apply && + meth.flags.is(Synthetic) && + meth.owner.linkedClass.is(Case) && + cls.isNoInitsRealClass + } + if isPureApply(tree, fn) then + apply(fn) && args.forall(apply) + else if (isCaseClassApply) + args.forall(apply) + else if (fn.symbol.is(Erased)) true + else false + case Typed(expr, _) => + apply(expr) + case Block(stats, expr) => + apply(expr) && stats.forall(isStatElideable) + case Inlined(_, bindings, expr) => + apply(expr) && bindings.forall(isStatElideable) + case NamedArg(_, expr) => + apply(expr) + case _ => + false + } + } + + /** Populate `thisProxy` and `paramProxy` as follows: + * + * 1a. If given type refers to a static this, thisProxy binds it to corresponding global reference, + * 1b. If given type refers to an instance this to a class that is not contained in the + * inline method, create a proxy symbol and bind the thistype to refer to the proxy. + * The proxy is not yet entered in `bindingsBuf`; that will come later. + * 2. If given type refers to a parameter, make `paramProxy` refer to the entry stored + * in `paramNames` under the parameter's name. This roundabout way to bind parameter + * references to proxies is done because we don't know a priori what the parameter + * references of a method are (we only know the method's type, but that contains TypeParamRefs + * and MethodParams, not TypeRefs or TermRefs. + */ + private def registerType(tpe: Type): Unit = tpe match { + case tpe: ThisType if !canElideThis(tpe) && !thisProxy.contains(tpe.cls) => + val proxyName = s"${tpe.cls.name}_this".toTermName + def adaptToPrefix(tp: Type) = tp.asSeenFrom(inlineCallPrefix.tpe, inlinedMethod.owner) + val proxyType = inlineCallPrefix.tpe.dealias.tryNormalize match { + case typeMatchResult if typeMatchResult.exists => typeMatchResult + case _ => adaptToPrefix(tpe).widenIfUnstable + } + thisProxy(tpe.cls) = newSym(proxyName, InlineProxy, proxyType).termRef + if (!tpe.cls.isStaticOwner) + registerType(inlinedMethod.owner.thisType) // make sure we have a base from which to outer-select + for (param <- tpe.cls.typeParams) + paramProxy(param.typeRef) = adaptToPrefix(param.typeRef) + case tpe: NamedType + if tpe.symbol.is(Param) && tpe.symbol.owner == inlinedMethod && !paramProxy.contains(tpe) => + paramBinding.get(tpe.name) match + case Some(bound) => paramProxy(tpe) = bound + case _ => // can happen for params bound by type-lambda trees. + case _ => + } + + /** Register type of leaf node */ + private def registerLeaf(tree: Tree): Unit = tree match { + case _: This | _: Ident | _: TypeTree => + tree.typeOpt.foreachPart(registerType, stopAtStatic = true) + case _ => + } + + /** Make `tree` part of inlined expansion. This means its owner has to be changed + * from its `originalOwner`, and, if it comes from outside the inlined method + * itself, it has to be marked as an inlined argument. + */ + def integrate(tree: Tree, originalOwner: Symbol)(using Context): Tree = + // assertAllPositioned(tree) // debug + tree.changeOwner(originalOwner, ctx.owner) + + def tryConstValue: Tree = + TypeComparer.constValue(callTypeArgs.head.tpe) match { + case Some(c) => Literal(c).withSpan(call.span) + case _ => EmptyTree + } + + /** The Inlined node representing the inlined call */ + def inlined(sourcePos: SrcPos): Tree = { + + // Special handling of `requireConst` and `codeOf` + callValueArgss match + case (arg :: Nil) :: Nil => + if inlinedMethod == defn.Compiletime_requireConst then + arg match + case ConstantValue(_) | Inlined(_, Nil, Typed(ConstantValue(_), _)) => // ok + case _ => report.error(em"expected a constant value but found: $arg", arg.srcPos) + return Literal(Constant(())).withSpan(sourcePos.span) + else if inlinedMethod == defn.Compiletime_codeOf then + return Intrinsics.codeOf(arg, call.srcPos) + case _ => + + // Special handling of `constValue[T]` and `constValueOpt[T]` + if (callTypeArgs.length == 1) + if (inlinedMethod == defn.Compiletime_constValue) { + val constVal = tryConstValue + if (!constVal.isEmpty) return constVal + report.error(em"not a constant type: ${callTypeArgs.head}; cannot take constValue", call.srcPos) + } + else if (inlinedMethod == defn.Compiletime_constValueOpt) { + val constVal = tryConstValue + return ( + if (constVal.isEmpty) ref(defn.NoneModule.termRef) + else New(defn.SomeClass.typeRef.appliedTo(constVal.tpe), constVal :: Nil) + ) + } + + def paramTypess(call: Tree, acc: List[List[Type]]): List[List[Type]] = call match + case Apply(fn, args) => + fn.tpe.widen.match + case mt: MethodType => paramTypess(fn, mt.instantiateParamInfos(args.tpes) :: acc) + case _ => Nil + case TypeApply(fn, _) => paramTypess(fn, acc) + case _ => acc + + // Compute bindings for all parameters, appending them to bindingsBuf + if !computeParamBindings(inlinedMethod.info, callTypeArgs, callValueArgss, paramTypess(call, Nil)) then + return call + + // make sure prefix is executed if it is impure + if (!isIdempotentExpr(inlineCallPrefix)) registerType(inlinedMethod.owner.thisType) + + // Register types of all leaves of inlined body so that the `paramProxy` and `thisProxy` maps are defined. + rhsToInline.foreachSubTree(registerLeaf) + + // Compute bindings for all this-proxies, appending them to bindingsBuf + computeThisBindings() + + val inlineTyper = new InlineTyper(ctx.reporter.errorCount) + + val inlineCtx = inlineContext(call).fresh.setTyper(inlineTyper).setNewScope + + def inlinedFromOutside(tree: Tree)(span: Span): Tree = + Inlined(EmptyTree, Nil, tree)(using ctx.withSource(inlinedMethod.topLevelClass.source)).withSpan(span) + + // InlinerMap is a TreeTypeMap with special treatment for inlined arguments: + // They are generally left alone (not mapped further, and if they wrap a type + // the type Inlined wrapper gets dropped + class InlinerMap( + typeMap: Type => Type, + treeMap: Tree => Tree, + oldOwners: List[Symbol], + newOwners: List[Symbol], + substFrom: List[Symbol], + substTo: List[Symbol])(using Context) + extends TreeTypeMap(typeMap, treeMap, oldOwners, newOwners, substFrom, substTo): + + override def copy( + typeMap: Type => Type, + treeMap: Tree => Tree, + oldOwners: List[Symbol], + newOwners: List[Symbol], + substFrom: List[Symbol], + substTo: List[Symbol])(using Context) = + new InlinerMap(typeMap, treeMap, oldOwners, newOwners, substFrom, substTo) + + override def transformInlined(tree: Inlined)(using Context) = + if tree.call.isEmpty then + tree.expansion match + case expansion: TypeTree => expansion + case _ => tree + else super.transformInlined(tree) + end InlinerMap + + // A tree type map to prepare the inlined body for typechecked. + // The translation maps references to `this` and parameters to + // corresponding arguments or proxies on the type and term level. It also changes + // the owner from the inlined method to the current owner. + val inliner = new InlinerMap( + typeMap = + new DeepTypeMap { + def apply(t: Type) = t match { + case t: ThisType => thisProxy.getOrElse(t.cls, t) + case t: TypeRef => paramProxy.getOrElse(t, mapOver(t)) + case t: SingletonType => + if t.termSymbol.isAllOf(Inline | Param) then mapOver(t.widenTermRefExpr) + else paramProxy.getOrElse(t, mapOver(t)) + case t => mapOver(t) + } + }, + treeMap = { + case tree: This => + tree.tpe match { + case thistpe: ThisType => + thisProxy.get(thistpe.cls) match { + case Some(t) => + val thisRef = ref(t).withSpan(call.span) + inlinedFromOutside(thisRef)(tree.span) + case None => tree + } + case _ => tree + } + case tree: Ident => + /* Span of the argument. Used when the argument is inlined directly without a binding */ + def argSpan = + if (tree.name == nme.WILDCARD) tree.span // From type match + else if (tree.symbol.isTypeParam && tree.symbol.owner.isClass) tree.span // TODO is this the correct span? + else paramSpan(tree.name) + val inlinedCtx = ctx.withSource(inlinedMethod.topLevelClass.source) + paramProxy.get(tree.tpe) match { + case Some(t) if tree.isTerm && t.isSingleton => + val inlinedSingleton = singleton(t).withSpan(argSpan) + inlinedFromOutside(inlinedSingleton)(tree.span) + case Some(t) if tree.isType => + inlinedFromOutside(TypeTree(t).withSpan(argSpan))(tree.span) + case _ => tree + } + case tree => tree + }, + oldOwners = inlinedMethod :: Nil, + newOwners = ctx.owner :: Nil, + substFrom = Nil, + substTo = Nil + )(using inlineCtx) + + // Apply inliner to `rhsToInline`, split off any implicit bindings from result, and + // make them part of `bindingsBuf`. The expansion is then the tree that remains. + val expansion = inliner.transform(rhsToInline) + + def issueError() = callValueArgss match { + case (msgArg :: Nil) :: Nil => + val message = msgArg.tpe match { + case ConstantType(Constant(msg: String)) => msg + case _ => s"A literal string is expected as an argument to `compiletime.error`. Got ${msgArg.show}" + } + // Usually `error` is called from within a rewrite method. In this + // case we need to report the error at the point of the outermost enclosing inline + // call. This way, a defensively written rewrite methid can always + // report bad inputs at the point of call instead of revealing its internals. + val callToReport = if (enclosingInlineds.nonEmpty) enclosingInlineds.last else call + val ctxToReport = ctx.outersIterator.dropWhile(enclosingInlineds(using _).nonEmpty).next + inContext(ctxToReport) { + report.error(message, callToReport.srcPos) + } + case _ => + } + + /** The number of nodes in this tree, excluding code in nested inline + * calls and annotations of definitions. + */ + def treeSize(x: Any): Int = + var siz = 0 + x match + case x: Trees.Inlined[_] => + case x: Positioned => + var i = 0 + while i < x.productArity do + siz += treeSize(x.productElement(i)) + i += 1 + case x: List[_] => + var xs = x + while xs.nonEmpty do + siz += treeSize(xs.head) + xs = xs.tail + case _ => + siz + + trace(i"inlining $call", inlining, show = true) { + + // The normalized bindings collected in `bindingsBuf` + bindingsBuf.mapInPlace { binding => + // Set trees to symbols allow macros to see the definition tree. + // This is used by `underlyingArgument`. + val binding1 = reducer.normalizeBinding(binding)(using inlineCtx).setDefTree + binding1.foreachSubTree { + case tree: MemberDef => tree.setDefTree + case _ => + } + binding1 + } + + // Run a typing pass over the inlined tree. See InlineTyper for details. + val expansion1 = inlineTyper.typed(expansion)(using inlineCtx) + + if (ctx.settings.verbose.value) { + inlining.println(i"to inline = $rhsToInline") + inlining.println(i"original bindings = ${bindingsBuf.toList}%\n%") + inlining.println(i"original expansion = $expansion1") + } + + // Drop unused bindings + val (finalBindings, finalExpansion) = dropUnusedDefs(bindingsBuf.toList, expansion1) + + if (inlinedMethod == defn.Compiletime_error) issueError() + + addInlinedTrees(treeSize(finalExpansion)) + + // Take care that only argument bindings go into `bindings`, since positions are + // different for bindings from arguments and bindings from body. + tpd.Inlined(call, finalBindings, finalExpansion) + } + } + + /** A utility object offering methods for rewriting inlined code */ + object reducer { + + /** An extractor for terms equivalent to `new C(args)`, returning the class `C`, + * a list of bindings, and the arguments `args`. Can see inside blocks and Inlined nodes and can + * follow a reference to an inline value binding to its right hand side. + * + * @return optionally, a triple consisting of + * - the class `C` + * - the arguments `args` + * - any bindings that wrap the instance creation + * - whether the instance creation is precomputed or by-name + */ + private object NewInstance { + def unapply(tree: Tree)(using Context): Option[(Symbol, List[Tree], List[Tree], Boolean)] = { + def unapplyLet(bindings: List[Tree], expr: Tree) = + unapply(expr) map { + case (cls, reduced, prefix, precomputed) => (cls, reduced, bindings ::: prefix, precomputed) + } + tree match { + case Apply(fn, args) => + fn match { + case Select(New(tpt), nme.CONSTRUCTOR) => + Some((tpt.tpe.classSymbol, args, Nil, false)) + case TypeApply(Select(New(tpt), nme.CONSTRUCTOR), _) => + Some((tpt.tpe.classSymbol, args, Nil, false)) + case _ => + val meth = fn.symbol + if (meth.name == nme.apply && + meth.flags.is(Synthetic) && + meth.owner.linkedClass.is(Case)) + Some(meth.owner.linkedClass, args, Nil, false) + else None + } + case Typed(inner, _) => + // drop the ascribed tpt. We only need it if we can't find a NewInstance + unapply(inner) + case Ident(_) => + val binding = tree.symbol.defTree + for ((cls, reduced, prefix, precomputed) <- unapply(binding)) + yield (cls, reduced, prefix, precomputed || binding.isInstanceOf[ValDef]) + case Inlined(_, bindings, expansion) => + unapplyLet(bindings, expansion) + case Block(stats, expr) if isElideableExpr(tree) => + unapplyLet(stats, expr) + case _ => + None + } + } + } + + /** If `tree` is equivalent to `new C(args).x` where class `C` does not have + * initialization code and `x` is a parameter corresponding to one of the + * arguments `args`, the corresponding argument, otherwise `tree` itself. + * Side effects of original arguments need to be preserved. + */ + def reduceProjection(tree: Tree)(using Context): Tree = { + if (ctx.debug) inlining.println(i"try reduce projection $tree") + tree match { + case Select(NewInstance(cls, args, prefix, precomputed), field) if cls.isNoInitsRealClass => + def matches(param: Symbol, selection: Symbol): Boolean = + param == selection || { + selection.name match { + case InlineAccessorName(underlying) => + param.name == underlying && selection.info.isInstanceOf[ExprType] + case _ => + false + } + } + val idx = cls.asClass.paramAccessors.indexWhere(matches(_, tree.symbol)) + if (idx >= 0 && idx < args.length) { + def finish(arg: Tree) = + new TreeTypeMap().transform(arg) // make sure local bindings in argument have fresh symbols + .showing(i"projecting $tree -> $result", inlining) + val arg = args(idx) + if (precomputed) + if (isElideableExpr(arg)) finish(arg) + else tree // nothing we can do here, projection would duplicate side effect + else { + // newInstance is evaluated in place, need to reflect side effects of + // arguments in the order they were written originally + def collectImpure(from: Int, end: Int) = + (from until end).filterNot(i => isElideableExpr(args(i))).toList.map(args) + val leading = collectImpure(0, idx) + val trailing = collectImpure(idx + 1, args.length) + val argInPlace = + if (trailing.isEmpty) arg + else + def argsSpan = trailing.map(_.span).foldLeft(arg.span)(_.union(_)) + letBindUnless(TreeInfo.Pure, arg)(Block(trailing, _).withSpan(argsSpan)) + val blockSpan = (prefix ::: leading).map(_.span).foldLeft(argInPlace.span)(_.union(_)) + finish(seq(prefix, seq(leading, argInPlace)).withSpan(blockSpan)) + } + } + else tree + case Block(stats, expr) if stats.forall(isPureBinding) => + cpy.Block(tree)(stats, reduceProjection(expr)) + case _ => tree + } + } + + /** If this is a value binding: + * - reduce its rhs if it is a projection and adjust its type accordingly, + * - record symbol -> rhs in the InlineBindings context propery. + */ + def normalizeBinding(binding: ValOrDefDef)(using Context) = { + val binding1 = binding match { + case binding: ValDef => + val rhs1 = reduceProjection(binding.rhs) + binding.symbol.defTree = rhs1 + if (rhs1 `eq` binding.rhs) binding + else { + binding.symbol.info = rhs1.tpe + cpy.ValDef(binding)(tpt = TypeTree(rhs1.tpe), rhs = rhs1) + } + case _ => + binding + } + binding1.withSpan(call.span) + } + + /** An extractor for references to inlineable arguments. These are : + * - by-value arguments marked with `inline` + * - all by-name arguments + */ + private object InlineableArg { + lazy val paramProxies = paramProxy.values.toSet + def unapply(tree: Trees.Ident[?])(using Context): Option[Tree] = { + def search(buf: mutable.ListBuffer[ValOrDefDef]) = buf.find(_.name == tree.name) + if (paramProxies.contains(tree.typeOpt)) + search(bindingsBuf) match { + case Some(bind: ValOrDefDef) if bind.symbol.is(Inline) => + Some(integrate(bind.rhs, bind.symbol)) + case _ => None + } + else None + } + } + + def tryInlineArg(tree: Tree)(using Context): Tree = tree match { + case InlineableArg(rhs) => + inlining.println(i"inline arg $tree -> $rhs") + rhs + case _ => + EmptyTree + } + + /** Rewrite an application + * + * ((x1, ..., xn) => b)(e1, ..., en) + * + * to + * + * val/def x1 = e1; ...; val/def xn = en; b + * + * where `def` is used for call-by-name parameters. However, we shortcut any NoPrefix + * refs among the ei's directly without creating an intermediate binding. + */ + def betaReduce(tree: Tree)(using Context): Tree = tree match { + case Apply(Select(cl @ closureDef(ddef), nme.apply), args) if defn.isFunctionType(cl.tpe) => + // closureDef also returns a result for closures wrapped in Inlined nodes. + // These need to be preserved. + def recur(cl: Tree): Tree = cl match + case Inlined(call, bindings, expr) => + cpy.Inlined(cl)(call, bindings, recur(expr)) + case _ => ddef.tpe.widen match + case mt: MethodType if ddef.paramss.head.length == args.length => + val bindingsBuf = new mutable.ListBuffer[ValOrDefDef] + val argSyms = mt.paramNames.lazyZip(mt.paramInfos).lazyZip(args).map { (name, paramtp, arg) => + arg.tpe.dealias match { + case ref @ TermRef(NoPrefix, _) => ref.symbol + case _ => + paramBindingDef(name, paramtp, arg, bindingsBuf)( + using ctx.withSource(cl.source) + ).symbol + } + } + val expander = new TreeTypeMap( + oldOwners = ddef.symbol :: Nil, + newOwners = ctx.owner :: Nil, + substFrom = ddef.paramss.head.map(_.symbol), + substTo = argSyms) + Block(bindingsBuf.toList, expander.transform(ddef.rhs)).withSpan(tree.span) + case _ => tree + recur(cl) + case _ => tree + } + + /** The result type of reducing a match. It consists optionally of a list of bindings + * for the pattern-bound variables and the RHS of the selected case. + * Returns `None` if no case was selected. + */ + type MatchRedux = Option[(List[MemberDef], Tree)] + + /** Reduce an inline match + * @param mtch the match tree + * @param scrutinee the scrutinee expression, assumed to be pure, or + * EmptyTree for a summonFrom + * @param scrutType its fully defined type, or + * ImplicitScrutineeTypeRef for a summonFrom + * @param typer The current inline typer + * @return optionally, if match can be reduced to a matching case: A pair of + * bindings for all pattern-bound variables and the RHS of the case. + */ + def reduceInlineMatch(scrutinee: Tree, scrutType: Type, cases: List[CaseDef], typer: Typer)(using Context): MatchRedux = { + + val isImplicit = scrutinee.isEmpty + + /** Try to match pattern `pat` against scrutinee reference `scrut`. If successful add + * bindings for variables bound in this pattern to `caseBindingMap`. + */ + def reducePattern( + caseBindingMap: mutable.ListBuffer[(Symbol, MemberDef)], + scrut: TermRef, + pat: Tree + )(using Context): Boolean = { + + /** Create a binding of a pattern bound variable with matching part of + * scrutinee as RHS and type that corresponds to RHS. + */ + def newTermBinding(sym: TermSymbol, rhs: Tree): Unit = { + val copied = sym.copy(info = rhs.tpe.widenInlineScrutinee, coord = sym.coord, flags = sym.flags &~ Case).asTerm + caseBindingMap += ((sym, ValDef(copied, constToLiteral(rhs)).withSpan(sym.span))) + } + + def newTypeBinding(sym: TypeSymbol, alias: Type): Unit = { + val copied = sym.copy(info = TypeAlias(alias), coord = sym.coord).asType + caseBindingMap += ((sym, TypeDef(copied))) + } + + def searchImplicit(sym: TermSymbol, tpt: Tree) = { + val evTyper = new Typer + val evCtx = ctx.fresh.setTyper(evTyper) + val evidence = evTyper.inferImplicitArg(tpt.tpe, tpt.span)(using evCtx) + evidence.tpe match { + case fail: Implicits.AmbiguousImplicits => + report.error(evTyper.missingArgMsg(evidence, tpt.tpe, ""), tpt.srcPos) + true // hard error: return true to stop implicit search here + case fail: Implicits.SearchFailureType => + false + case _ => + //inliner.println(i"inferred implicit $sym: ${sym.info} with $evidence: ${evidence.tpe.widen}, ${evCtx.gadt.constraint}, ${evCtx.typerState.constraint}") + newTermBinding(sym, evidence) + true + } + } + + type TypeBindsMap = SimpleIdentityMap[TypeSymbol, java.lang.Boolean] + + def getTypeBindsMap(pat: Tree, tpt: Tree): TypeBindsMap = { + val getBinds = new TreeAccumulator[Set[TypeSymbol]] { + def apply(syms: Set[TypeSymbol], t: Tree)(using Context): Set[TypeSymbol] = { + val syms1 = t match { + case t: Bind if t.symbol.isType => + syms + t.symbol.asType + case _ => syms + } + foldOver(syms1, t) + } + } + + // Extractors contain Bind nodes in type parameter lists, the tree looks like this: + // UnApply[t @ t](pats)(implicits): T[t] + // Test case is pos/inline-caseclass.scala. + val binds: Set[TypeSymbol] = pat match { + case UnApply(TypeApply(_, tpts), _, _) => getBinds(Set.empty[TypeSymbol], tpts) + case _ => getBinds(Set.empty[TypeSymbol], tpt) + } + + val extractBindVariance = new TypeAccumulator[TypeBindsMap] { + def apply(syms: TypeBindsMap, t: Type) = { + val syms1 = t match { + // `binds` is used to check if the symbol was actually bound by the pattern we're processing + case tr: TypeRef if tr.symbol.is(Case) && binds.contains(tr.symbol.asType) => + val trSym = tr.symbol.asType + // Exact same logic as in IsFullyDefinedAccumulator: + // the binding is to be maximized iff it only occurs contravariantly in the type + val wasToBeMinimized: Boolean = { + val v = syms(trSym) + if (v ne null) v else false + } + syms.updated(trSym, wasToBeMinimized || variance >= 0 : java.lang.Boolean) + case _ => + syms + } + foldOver(syms1, t) + } + } + + extractBindVariance(SimpleIdentityMap.empty, tpt.tpe) + } + + def addTypeBindings(typeBinds: TypeBindsMap)(using Context): Unit = + typeBinds.foreachBinding { case (sym, shouldBeMinimized) => + newTypeBinding(sym, ctx.gadt.approximation(sym, fromBelow = shouldBeMinimized)) + } + + def registerAsGadtSyms(typeBinds: TypeBindsMap)(using Context): Unit = + if (typeBinds.size > 0) ctx.gadt.addToConstraint(typeBinds.keys) + + pat match { + case Typed(pat1, tpt) => + val typeBinds = getTypeBindsMap(pat1, tpt) + registerAsGadtSyms(typeBinds) + scrut <:< tpt.tpe && { + addTypeBindings(typeBinds) + reducePattern(caseBindingMap, scrut, pat1) + } + case pat @ Bind(name: TermName, Typed(_, tpt)) if isImplicit => + val typeBinds = getTypeBindsMap(tpt, tpt) + registerAsGadtSyms(typeBinds) + searchImplicit(pat.symbol.asTerm, tpt) && { + addTypeBindings(typeBinds) + true + } + case pat @ Bind(name: TermName, body) => + reducePattern(caseBindingMap, scrut, body) && { + if (name != nme.WILDCARD) newTermBinding(pat.symbol.asTerm, ref(scrut)) + true + } + case Ident(nme.WILDCARD) => + true + case pat: Literal => + scrut.widenTermRefExpr =:= pat.tpe + case pat: RefTree => + scrut =:= pat.tpe || + scrut.classSymbol.is(Module) && scrut.widen =:= pat.tpe.widen && { + scrut.prefix match { + case _: SingletonType | NoPrefix => true + case _ => false + } + } + case UnApply(unapp, _, pats) => + unapp.tpe.widen match { + case mt: MethodType if mt.paramInfos.length == 1 => + + def reduceSubPatterns(pats: List[Tree], selectors: List[Tree]): Boolean = (pats, selectors) match { + case (Nil, Nil) => true + case (pat :: pats1, selector :: selectors1) => + val elem = newSym(InlineBinderName.fresh(), Synthetic, selector.tpe.widenInlineScrutinee).asTerm + val rhs = constToLiteral(selector) + elem.defTree = rhs + caseBindingMap += ((NoSymbol, ValDef(elem, rhs).withSpan(elem.span))) + reducePattern(caseBindingMap, elem.termRef, pat) && + reduceSubPatterns(pats1, selectors1) + case _ => false + } + + val paramType = mt.paramInfos.head + val paramCls = paramType.classSymbol + if (paramCls.is(Case) && unapp.symbol.is(Synthetic) && scrut <:< paramType) { + val caseAccessors = + if (paramCls.is(Scala2x)) paramCls.caseAccessors.filter(_.is(Method)) + else paramCls.asClass.paramAccessors + val selectors = + for (accessor <- caseAccessors) + yield constToLiteral(reduceProjection(ref(scrut).select(accessor).ensureApplied)) + caseAccessors.length == pats.length && reduceSubPatterns(pats, selectors) + } + else false + case _ => + false + } + case Inlined(EmptyTree, Nil, ipat) => + reducePattern(caseBindingMap, scrut, ipat) + case _ => false + } + } + + /** The initial scrutinee binding: `val $scrutineeN = ` */ + val scrutineeSym = newSym(InlineScrutineeName.fresh(), Synthetic, scrutType).asTerm + val scrutineeBinding = normalizeBinding(ValDef(scrutineeSym, scrutinee)) + + def reduceCase(cdef: CaseDef): MatchRedux = { + val caseBindingMap = new mutable.ListBuffer[(Symbol, MemberDef)]() + + def substBindings( + bindings: List[(Symbol, MemberDef)], + bbuf: mutable.ListBuffer[MemberDef], + from: List[Symbol], to: List[Symbol]): (List[MemberDef], List[Symbol], List[Symbol]) = + bindings match { + case (sym, binding) :: rest => + bbuf += binding.subst(from, to).asInstanceOf[MemberDef] + if (sym.exists) substBindings(rest, bbuf, sym :: from, binding.symbol :: to) + else substBindings(rest, bbuf, from, to) + case Nil => (bbuf.toList, from, to) + } + + if (!isImplicit) caseBindingMap += ((NoSymbol, scrutineeBinding)) + val gadtCtx = ctx.fresh.setFreshGADTBounds.addMode(Mode.GadtConstraintInference) + if (reducePattern(caseBindingMap, scrutineeSym.termRef, cdef.pat)(using gadtCtx)) { + val (caseBindings, from, to) = substBindings(caseBindingMap.toList, mutable.ListBuffer(), Nil, Nil) + val guardOK = cdef.guard.isEmpty || { + typer.typed(cdef.guard.subst(from, to), defn.BooleanType) match { + case ConstantValue(true) => true + case _ => false + } + } + if (guardOK) Some((caseBindings.map(_.subst(from, to)), cdef.body.subst(from, to))) + else None + } + else None + } + + def recur(cases: List[CaseDef]): MatchRedux = cases match { + case Nil => None + case cdef :: cases1 => reduceCase(cdef) `orElse` recur(cases1) + } + + recur(cases) + } + } + + /** A typer for inlined bodies. Beyond standard typing, an inline typer performs + * the following functions: + * + * 1. Implement constant folding over inlined code + * 2. Selectively expand ifs with constant conditions + * 3. Inline arguments that are by-name closures + * 4. Make sure inlined code is type-correct. + * 5. Make sure that the tree's typing is idempotent (so that future -Ycheck passes succeed) + */ + class InlineTyper(initialErrorCount: Int) extends ReTyper { + import reducer._ + + override def ensureAccessible(tpe: Type, superAccess: Boolean, pos: SrcPos)(using Context): Type = { + tpe match { + case tpe: NamedType if tpe.symbol.exists && !tpe.symbol.isAccessibleFrom(tpe.prefix, superAccess) => + tpe.info match { + case TypeAlias(alias) => return ensureAccessible(alias, superAccess, pos) + case info: ConstantType if tpe.symbol.isStableMember => return info + case _ => + } + case _ => + } + super.ensureAccessible(tpe, superAccess, pos) + } + + override def typedIdent(tree: untpd.Ident, pt: Type)(using Context): Tree = + inlineIfNeeded(tryInlineArg(tree.asInstanceOf[tpd.Tree]) `orElse` super.typedIdent(tree, pt)) + + override def typedSelect(tree: untpd.Select, pt: Type)(using Context): Tree = { + assert(tree.hasType, tree) + val qual1 = typed(tree.qualifier, shallowSelectionProto(tree.name, pt, this)) + val resNoReduce = untpd.cpy.Select(tree)(qual1, tree.name).withType(tree.typeOpt) + val resMaybeReduced = constToLiteral(reducer.reduceProjection(resNoReduce)) + if (resNoReduce ne resMaybeReduced) + typed(resMaybeReduced, pt) // redo typecheck if reduction changed something + else + val res = resMaybeReduced + ensureAccessible(res.tpe, tree.qualifier.isInstanceOf[untpd.Super], tree.srcPos) + inlineIfNeeded(res) + } + + override def typedIf(tree: untpd.If, pt: Type)(using Context): Tree = + val condCtx = if tree.isInline then ctx.addMode(Mode.ForceInline) else ctx + typed(tree.cond, defn.BooleanType)(using condCtx) match { + case cond1 @ ConstantValue(b: Boolean) => + val selected0 = if (b) tree.thenp else tree.elsep + val selected = if (selected0.isEmpty) tpd.Literal(Constant(())) else typed(selected0, pt) + if (isIdempotentExpr(cond1)) selected + else Block(cond1 :: Nil, selected) + case cond1 => + if (tree.isInline) + errorTree(tree, + em"Cannot reduce `inline if` because its condition is not a constant value: $cond1") + else + cond1.computeNullableDeeply() + val if1 = untpd.cpy.If(tree)(cond = untpd.TypedSplice(cond1)) + super.typedIf(if1, pt) + } + + override def typedValDef(vdef: untpd.ValDef, sym: Symbol)(using Context): Tree = + val vdef1 = + if sym.is(Inline) then + val rhs = typed(vdef.rhs) + sym.info = rhs.tpe + untpd.cpy.ValDef(vdef)(vdef.name, untpd.TypeTree(rhs.tpe), untpd.TypedSplice(rhs)) + else vdef + super.typedValDef(vdef1, sym) + + override def typedApply(tree: untpd.Apply, pt: Type)(using Context): Tree = + val res = constToLiteral(betaReduce(super.typedApply(tree, pt))) match { + case res: Apply if res.symbol == defn.QuotedRuntime_exprSplice + && level == 0 + && !hasInliningErrors => + val expanded = expandMacro(res.args.head, tree.srcPos) + typedExpr(expanded) // Inline calls and constant fold code generated by the macro + case res => + inlineIfNeeded(res) + } + if res.symbol == defn.QuotedRuntime_exprQuote then + ctx.compilationUnit.needsQuotePickling = true + res + + override def typedTypeApply(tree: untpd.TypeApply, pt: Type)(using Context): Tree = + inlineIfNeeded(constToLiteral(betaReduce(super.typedTypeApply(tree, pt)))) + + override def typedMatch(tree: untpd.Match, pt: Type)(using Context): Tree = + val tree1 = + if tree.isInline then + // TODO this might not be useful if we do not support #11291 + val sel1 = typedExpr(tree.selector)(using ctx.addMode(Mode.ForceInline)) + untpd.cpy.Match(tree)(sel1, tree.cases) + else tree + super.typedMatch(tree1, pt) + + override def typedMatchFinish(tree: untpd.Match, sel: Tree, wideSelType: Type, cases: List[untpd.CaseDef], pt: Type)(using Context) = + if (!tree.isInline || ctx.owner.isInlineMethod) // don't reduce match of nested inline method yet + super.typedMatchFinish(tree, sel, wideSelType, cases, pt) + else { + def selTyped(sel: Tree): Type = sel match { + case Typed(sel2, _) => selTyped(sel2) + case Block(Nil, sel2) => selTyped(sel2) + case Inlined(_, Nil, sel2) => selTyped(sel2) + case _ => sel.tpe + } + val selType = if (sel.isEmpty) wideSelType else selTyped(sel) + reduceInlineMatch(sel, selType, cases.asInstanceOf[List[CaseDef]], this) match { + case Some((caseBindings, rhs0)) => + // drop type ascriptions/casts hiding pattern-bound types (which are now aliases after reducing the match) + // note that any actually necessary casts will be reinserted by the typing pass below + val rhs1 = rhs0 match { + case Block(stats, t) if t.span.isSynthetic => + t match { + case Typed(expr, _) => + Block(stats, expr) + case TypeApply(sel@Select(expr, _), _) if sel.symbol.isTypeCast => + Block(stats, expr) + case _ => + rhs0 + } + case _ => rhs0 + } + val (usedBindings, rhs2) = dropUnusedDefs(caseBindings, rhs1) + val rhs = seq(usedBindings, rhs2) + inlining.println(i"""--- reduce: + |$tree + |--- to: + |$rhs""") + typedExpr(rhs, pt) + case None => + def guardStr(guard: untpd.Tree) = if (guard.isEmpty) "" else i" if $guard" + def patStr(cdef: untpd.CaseDef) = i"case ${cdef.pat}${guardStr(cdef.guard)}" + val msg = + if (tree.selector.isEmpty) + em"""cannot reduce summonFrom with + | patterns : ${tree.cases.map(patStr).mkString("\n ")}""" + else + em"""cannot reduce inline match with + | scrutinee: $sel : ${selType} + | patterns : ${tree.cases.map(patStr).mkString("\n ")}""" + errorTree(tree, msg) + } + } + + override def newLikeThis: Typer = new InlineTyper(initialErrorCount) + + /** True if this inline typer has already issued errors */ + override def hasInliningErrors(using Context) = ctx.reporter.errorCount > initialErrorCount + + private def inlineIfNeeded(tree: Tree)(using Context): Tree = + if Inliner.needsInlining(tree) then Inliner.inlineCall(tree) + else tree + + override def typedUnadapted(tree: untpd.Tree, pt: Type, locked: TypeVars)(using Context): Tree = + super.typedUnadapted(tree, pt, locked) match + case member: MemberDef => member.setDefTree + case tree => tree + } + + /** Drop any side-effect-free bindings that are unused in expansion or other reachable bindings. + * Inline def bindings that are used only once. + */ + def dropUnusedDefs(bindings: List[MemberDef], tree: Tree)(using Context): (List[MemberDef], Tree) = { + // inlining.println(i"drop unused $bindings%, % in $tree") + val (termBindings, typeBindings) = bindings.partition(_.symbol.isTerm) + if (typeBindings.nonEmpty) { + val typeBindingsSet = typeBindings.foldLeft[SimpleIdentitySet[Symbol]](SimpleIdentitySet.empty)(_ + _.symbol) + val inlineTypeBindings = new TreeTypeMap( + typeMap = new TypeMap() { + override def apply(tp: Type): Type = tp match { + case tr: TypeRef if tr.prefix.eq(NoPrefix) && typeBindingsSet.contains(tr.symbol) => + val TypeAlias(res) = tr.info + res + case tp => mapOver(tp) + } + }, + treeMap = { + case ident: Ident if ident.isType && typeBindingsSet.contains(ident.symbol) => + val TypeAlias(r) = ident.symbol.info + TypeTree(r).withSpan(ident.span) + case tree => tree + } + ) + val Block(termBindings1, tree1) = inlineTypeBindings(Block(termBindings, tree)) + dropUnusedDefs(termBindings1.asInstanceOf[List[ValOrDefDef]], tree1) + } + else { + val refCount = MutableSymbolMap[Int]() + val bindingOfSym = MutableSymbolMap[MemberDef]() + + def isInlineable(binding: MemberDef) = binding match { + case ddef @ DefDef(_, Nil, _, _) => isElideableExpr(ddef.rhs) + case vdef @ ValDef(_, _, _) => isElideableExpr(vdef.rhs) + case _ => false + } + for (binding <- bindings if isInlineable(binding)) { + refCount(binding.symbol) = 0 + bindingOfSym(binding.symbol) = binding + } + + val countRefs = new TreeTraverser { + override def traverse(t: Tree)(using Context) = { + def updateRefCount(sym: Symbol, inc: Int) = + for (x <- refCount.get(sym)) refCount(sym) = x + inc + def updateTermRefCounts(t: Tree) = + t.typeOpt.foreachPart { + case ref: TermRef => updateRefCount(ref.symbol, 2) // can't be inlined, so make sure refCount is at least 2 + case _ => + } + + t match { + case t: RefTree => + updateRefCount(t.symbol, 1) + updateTermRefCounts(t) + case _: New | _: TypeTree => + updateTermRefCounts(t) + case _ => + } + traverseChildren(t) + } + } + countRefs.traverse(tree) + for (binding <- bindings) countRefs.traverse(binding) + + def retain(boundSym: Symbol) = { + refCount.get(boundSym) match { + case Some(x) => x > 1 || x == 1 && !boundSym.is(Method) + case none => true + } + } && !boundSym.is(Inline) + + val inlineBindings = new TreeMap { + override def transform(t: Tree)(using Context) = t match { + case t: RefTree => + val sym = t.symbol + val t1 = refCount.get(sym) match { + case Some(1) => + bindingOfSym(sym) match { + case binding: ValOrDefDef => integrate(binding.rhs, sym) + } + case none => t + } + super.transform(t1) + case t: Apply => + val t1 = super.transform(t) + if (t1 `eq` t) t else reducer.betaReduce(t1) + case Block(Nil, expr) => + super.transform(expr) + case _ => + super.transform(t) + } + } + + val retained = bindings.filterConserve(binding => retain(binding.symbol)) + if (retained `eq` bindings) + (bindings, tree) + else { + val expanded = inlineBindings.transform(tree) + dropUnusedDefs(retained, expanded) + } + } + } + + private def expandMacro(body: Tree, splicePos: SrcPos)(using Context) = { + assert(level == 0) + val inlinedFrom = enclosingInlineds.last + val dependencies = macroDependencies(body) + val suspendable = ctx.compilationUnit.isSuspendable + if dependencies.nonEmpty && !ctx.reporter.errorsReported then + for sym <- dependencies do + if ctx.compilationUnit.source.file == sym.associatedFile then + report.error(em"Cannot call macro $sym defined in the same source file", call.srcPos) + if (suspendable && ctx.settings.XprintSuspension.value) + report.echo(i"suspension triggered by macro call to ${sym.showLocated} in ${sym.associatedFile}", call.srcPos) + if suspendable then + ctx.compilationUnit.suspend() // this throws a SuspendException + + val evaluatedSplice = inContext(quoted.MacroExpansion.context(inlinedFrom)) { + Splicer.splice(body, splicePos, inlinedFrom.srcPos, MacroClassLoader.fromContext) + } + val inlinedNormailizer = new TreeMap { + override def transform(tree: tpd.Tree)(using Context): tpd.Tree = tree match { + case Inlined(EmptyTree, Nil, expr) if enclosingInlineds.isEmpty => transform(expr) + case _ => super.transform(tree) + } + } + val normalizedSplice = inlinedNormailizer.transform(evaluatedSplice) + if (normalizedSplice.isEmpty) normalizedSplice + else normalizedSplice.withSpan(splicePos.span) + } + + /** Return the set of symbols that are referred at level -1 by the tree and defined in the current run. + * This corresponds to the symbols that will need to be interpreted. + */ + private def macroDependencies(tree: Tree)(using Context) = + new TreeAccumulator[List[Symbol]] { + private var level = -1 + override def apply(syms: List[Symbol], tree: tpd.Tree)(using Context): List[Symbol] = + if (level != -1) foldOver(syms, tree) + else tree match { + case tree: RefTree if level == -1 && tree.symbol.isDefinedInCurrentRun && !tree.symbol.isLocal => + foldOver(tree.symbol :: syms, tree) + case Quoted(body) => + level += 1 + try apply(syms, body) + finally level -= 1 + case Spliced(body) => + level -= 1 + try apply(syms, body) + finally level += 1 + case SplicedType(body) => + level -= 1 + try apply(syms, body) + finally level += 1 + case _ => + foldOver(syms, tree) + } + }.apply(Nil, tree) + + object ConstantValue { + def unapply(tree: Tree)(using Context): Option[Any] = + tree match + case Typed(expr, _) => unapply(expr) + case Inlined(_, Nil, expr) => unapply(expr) + case Block(Nil, expr) => unapply(expr) + case _ => + tree.tpe.widenTermRefExpr.normalized match + case ConstantType(Constant(x)) => Some(x) + case _ => None + } + + +} From 95334165551094731729c19d89e3efa562ac7a35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Sun, 21 Feb 2021 13:54:46 +0100 Subject: [PATCH 06/44] Refactor InlinedsPositioner into InlinedSourceMap --- .../tools/backend/jvm/BCodeSkelBuilder.scala | 8 +- .../tools/backend/jvm/InlinedPositioner.scala | 121 ----------------- .../tools/backend/jvm/InlinedSourceMaps.scala | 126 ++++++++++++++++++ 3 files changed, 131 insertions(+), 124 deletions(-) delete mode 100644 compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala create mode 100644 compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala index ae9d24cb9630..d3b459903f1f 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala @@ -24,6 +24,8 @@ import dotty.tools.dotc.util.Spans._ import dotty.tools.dotc.report import dotty.tools.dotc.transform.SymUtils._ +import InlinedSourceMaps._ + /* * * @author Miguel Garcia, http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/ @@ -91,7 +93,7 @@ trait BCodeSkelBuilder extends BCodeHelpers { var isCZParcelable = false var isCZStaticModule = false - var inlinedsPositioner: InlinedsPositioner = null + var sourceMap: InlinedSourceMap = null /* ---------------- idiomatic way to ask questions to typer ---------------- */ @@ -278,8 +280,8 @@ trait BCodeSkelBuilder extends BCodeHelpers { superClass, interfaceNames.toArray) if (emitSource) { - inlinedsPositioner = InlinedsPositioner(cunit) - cnode.visitSource(cunit.source.file.name, inlinedsPositioner.debugExtension.orNull) + sourceMap = sourceMapFor(cunit) + cnode.visitSource(cunit.source.file.name, sourceMap.debugExtension.orNull) } enclosingMethodAttribute(claszSymbol, internalName, asmMethodType(_).descriptor) match { diff --git a/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala b/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala deleted file mode 100644 index 332bf18b0ef6..000000000000 --- a/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala +++ /dev/null @@ -1,121 +0,0 @@ -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.report -import dotc.typer.Inliner.InliningPosition -import collection.mutable - -import scala.collection.mutable.StringBuilder - -class InlinedsPositioner(cunit: CompilationUnit)(using Context): - private case class Request(targetPos: SourcePosition, origPos: SourcePosition, 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" - - private val requests = mutable.ListBuffer.empty[Request] - private var lastLine = cunit.tpdTree.sourcePos.endLine - - private def allocate(origPos: SourcePosition): Int = - val line = lastLine + 1 - lastLine += origPos.lines.length - line - -// if (source.name contains "Tuple.sca") && startLine == 35 && endLine == 35 then - - private class RequestCollector(enclosingFile: SourceFile) extends TreeTraverser: - override def traverse(tree: Tree)(using Context): Unit = - if tree.source != enclosingFile then - tree.getAttachment(InliningPosition) match - case Some(targetPos) => - val firstFakeLine = allocate(tree.sourcePos) - requests += Request(targetPos, tree.sourcePos, firstFakeLine) - RequestCollector(tree.source).traverseChildren(tree) - case None => - // Not exactly sure in which cases it is happening. Should we report warning? - RequestCollector(tree.source).traverseChildren(tree) - else traverseChildren(tree) - end RequestCollector - - RequestCollector(cunit.source).traverse(cunit.tpdTree) - - def debugExtension: Option[String] = Option.when(requests.nonEmpty) { - val scalaStratum = - val files = cunit.source :: requests.map(_.origPos.source).distinct.filter(_ != cunit.source).toList - val mappings = requests.map { case Request(_, origPos, firstFakeLine) => - Mapping(origPos.startLine, files.indexOf(origPos.source) + 1, origPos.lines.length, firstFakeLine, 1) - }.toList - Stratum("Scala", files.zipWithIndex.map { case (f, n) => File(n + 1, f.name, None) }, Mapping(0, 1, cunit.tpdTree.sourcePos.lines.length, 0, 1) +: mappings) - - val debugStratum = - val mappings = requests.map { case Request(targetPos, origPos, firstFakeLine) => - Mapping(targetPos.startLine, 1, 1, firstFakeLine, origPos.lines.length) - }.toList - 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(sourcePos: SourcePosition): Option[Int] = - requests.find(_.origPos.contains(sourcePos)) match - case Some(request) => - val offset = sourcePos.startLine - request.origPos.startLine - Some(request.firstFakeLine + offset + 1) - case None => - report.warning(s"${sourcePos.show} was inlined in ${cunit.source} but its inlining position was not recorded.") - None 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..851248837800 --- /dev/null +++ b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala @@ -0,0 +1,126 @@ +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.report +import dotc.typer.Inliner.InliningPosition +import collection.mutable + +import scala.collection.mutable.StringBuilder + +object InlinedSourceMaps: + private case class Request(targetPos: SourcePosition, origPos: SourcePosition, 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)(using Context): InlinedSourceMap = + val requests = mutable.ListBuffer.empty[Request] + var lastLine = cunit.tpdTree.sourcePos.endLine + + class RequestCollector(enclosingFile: SourceFile) extends TreeTraverser: + override def traverse(tree: Tree)(using Context): Unit = + if tree.source != enclosingFile then + tree.getAttachment(InliningPosition) match + case Some(targetPos) => + val firstFakeLine = allocate(tree.sourcePos) + requests += Request(targetPos, tree.sourcePos, firstFakeLine) + RequestCollector(tree.source).traverseChildren(tree) + case None => + // Not exactly sure in which cases it is happening. Should we report warning? + RequestCollector(tree.source).traverseChildren(tree) + else traverseChildren(tree) + end RequestCollector + + def allocate(origPos: SourcePosition): Int = + val line = lastLine + 1 + lastLine += origPos.lines.length + line + + RequestCollector(cunit.source).traverse(cunit.tpdTree) + InlinedSourceMap(cunit, requests.toList) + end sourceMapFor + + class InlinedSourceMap private[InlinedSourceMaps] (cunit: CompilationUnit, requests: List[Request])(using Context): + def debugExtension: Option[String] = Option.when(requests.nonEmpty) { + val scalaStratum = + val files = cunit.source :: requests.map(_.origPos.source).distinct.filter(_ != cunit.source) + val mappings = requests.map { case Request(_, origPos, firstFakeLine) => + Mapping(origPos.startLine, files.indexOf(origPos.source) + 1, origPos.lines.length, firstFakeLine, 1) + } + Stratum("Scala", files.zipWithIndex.map { case (f, n) => File(n + 1, f.name, None) }, Mapping(0, 1, cunit.tpdTree.sourcePos.lines.length, 0, 1) +: mappings) + + val debugStratum = + val mappings = requests.map { case Request(targetPos, origPos, firstFakeLine) => + Mapping(targetPos.startLine, 1, 1, firstFakeLine, origPos.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(sourcePos: SourcePosition): Option[Int] = + requests.find(_.origPos.contains(sourcePos)) match + case Some(request) => + val offset = sourcePos.startLine - request.origPos.startLine + Some(request.firstFakeLine + offset + 1) + case None => + report.warning(s"${sourcePos.show} was inlined in ${cunit.source} but its inlining position was not recorded.") + None + + From adf534cf7041531a46014a6c10ed948a60bd017a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Sun, 21 Feb 2021 14:11:30 +0100 Subject: [PATCH 07/44] Filter out information about inlining in the same file --- compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala index 851248837800..c17225c3f767 100644 --- a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala +++ b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala @@ -67,7 +67,7 @@ object InlinedSourceMaps: class RequestCollector(enclosingFile: SourceFile) extends TreeTraverser: override def traverse(tree: Tree)(using Context): Unit = - if tree.source != enclosingFile then + if tree.source != enclosingFile && tree.source != cunit.source then tree.getAttachment(InliningPosition) match case Some(targetPos) => val firstFakeLine = allocate(tree.sourcePos) From 67902ff89dedf7b46d644a13d5bb7fe8494d5751 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Sun, 21 Feb 2021 17:51:57 +0100 Subject: [PATCH 08/44] Add information aobut internal class name to SMAP --- .../tools/backend/jvm/BCodeSkelBuilder.scala | 2 +- .../tools/backend/jvm/InlinedSourceMaps.scala | 28 +++++++++++++++---- .../src/dotty/tools/dotc/typer/Inliner.scala | 12 +++++--- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala index d3b459903f1f..07423d92ae4c 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala @@ -280,7 +280,7 @@ trait BCodeSkelBuilder extends BCodeHelpers { superClass, interfaceNames.toArray) if (emitSource) { - sourceMap = sourceMapFor(cunit) + sourceMap = sourceMapFor(cunit)(s => classBTypeFromSymbol(s).internalName) cnode.visitSource(cunit.source.file.name, sourceMap.debugExtension.orNull) } diff --git a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala index c17225c3f767..92c54051dabc 100644 --- a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala +++ b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala @@ -6,11 +6,11 @@ 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.typer.Inliner.InliningPosition import collection.mutable -import scala.collection.mutable.StringBuilder object InlinedSourceMaps: private case class Request(targetPos: SourcePosition, origPos: SourcePosition, firstFakeLine: Int) @@ -61,17 +61,26 @@ object InlinedSourceMaps: b ++= "*E\n" end Stratum - def sourceMapFor(cunit: CompilationUnit)(using Context): InlinedSourceMap = + def sourceMapFor(cunit: CompilationUnit)(internalNameProvider: Symbol => String)(using Context): InlinedSourceMap = val requests = mutable.ListBuffer.empty[Request] var lastLine = cunit.tpdTree.sourcePos.endLine + var internalNames = Map.empty[SourceFile, String] class RequestCollector(enclosingFile: SourceFile) extends TreeTraverser: override def traverse(tree: Tree)(using Context): Unit = if tree.source != enclosingFile && tree.source != cunit.source then tree.getAttachment(InliningPosition) match - case Some(targetPos) => + case Some(InliningPosition(targetPos, cls)) => val firstFakeLine = allocate(tree.sourcePos) requests += Request(targetPos, tree.sourcePos, firstFakeLine) + cls 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 _ => () RequestCollector(tree.source).traverseChildren(tree) case None => // Not exactly sure in which cases it is happening. Should we report warning? @@ -85,17 +94,24 @@ object InlinedSourceMaps: line RequestCollector(cunit.source).traverse(cunit.tpdTree) - InlinedSourceMap(cunit, requests.toList) + InlinedSourceMap(cunit, requests.toList, internalNames) end sourceMapFor - class InlinedSourceMap private[InlinedSourceMaps] (cunit: CompilationUnit, requests: List[Request])(using Context): + 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(_.origPos.source).distinct.filter(_ != cunit.source) val mappings = requests.map { case Request(_, origPos, firstFakeLine) => Mapping(origPos.startLine, files.indexOf(origPos.source) + 1, origPos.lines.length, firstFakeLine, 1) } - Stratum("Scala", files.zipWithIndex.map { case (f, n) => File(n + 1, f.name, None) }, Mapping(0, 1, cunit.tpdTree.sourcePos.lines.length, 0, 1) +: mappings) + 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(targetPos, origPos, firstFakeLine) => diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index d3ad10dbde9e..769acae2ade4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -37,7 +37,8 @@ import quoted.QuoteUtils object Inliner { import tpd._ - val InliningPosition = new Property.StickyKey[SourcePosition] + object InliningPosition extends Property.StickyKey[InliningPosition] + case class InliningPosition(sourcePos: SourcePosition, topLevelSymbol: Option[Symbol]) /** `sym` is an inline method with a known body to inline. */ @@ -226,12 +227,15 @@ object Inliner { /** Replace `Inlined` node by a block that contains its bindings and expansion */ def dropInlined(inlined: Inlined)(using Context): Tree = - // In case that bindings are present we are adding SourcePosition attachement both to the resulting block and to the expansion + val topLevelClass = Some(inlined.call.symbol.topLevelClass).filter(_.exists) + val inliningPosition = InliningPosition(inlined.sourcePos, topLevelClass) + + // In case that bindings are present we are adding InliningPosition attachement both to the resulting block and to the expansion // as in some cases the block is removed in one of later phases and attachment is lost. val tree1 = if inlined.bindings.isEmpty then inlined.expansion - else cpy.Block(inlined)(inlined.bindings, inlined.expansion.withAttachment(InliningPosition, inlined.sourcePos)) - tree1.withAttachment(InliningPosition, inlined.sourcePos) + else cpy.Block(inlined)(inlined.bindings, inlined.expansion.withAttachment(InliningPosition, inliningPosition)) + tree1.withAttachment(InliningPosition, inliningPosition) /** Leave only a call trace consisting of * - a reference to the top-level class from which the call was inlined, From 5de29f6c861f0c0718237b2c5f13ec09b41968f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Sun, 21 Feb 2021 18:42:20 +0100 Subject: [PATCH 09/44] Fix problems with only first instance of inlined call being found --- .../tools/backend/jvm/BCodeSkelBuilder.scala | 30 +++++++++++++------ .../tools/backend/jvm/InlinedSourceMaps.scala | 4 +-- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala index 07423d92ae4c..0453c7d9f39c 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala @@ -377,6 +377,8 @@ trait BCodeSkelBuilder extends BCodeHelpers { var stackHeight = 0 // line numbers var lastEmittedLineNr = -1 + // by real line number we mean line number that is not pointing to virtual lines added by inlined calls + var lastRealLineNr = -1 object bc extends JCodeMethodN { override def jmethod = PlainSkelBuilder.this.mnode @@ -560,26 +562,36 @@ 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 = if (tree.source != cunit.source) inlinedsPositioner.lineFor(tree.sourcePos) else 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; + if tree.source != cunit.source then + sourceMap.lineFor(tree.sourcePos, lastRealLineNr) match + case Some(nr) => emitNr(nr) + case None => () + else + val nr = ctx.source.offsetToLine(tree.span.point) + 1 + lastRealLineNr = nr + emitNr(nr) } // on entering a method diff --git a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala index 92c54051dabc..8062c80f4f5d 100644 --- a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala +++ b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala @@ -130,8 +130,8 @@ object InlinedSourceMaps: b.toString } - def lineFor(sourcePos: SourcePosition): Option[Int] = - requests.find(_.origPos.contains(sourcePos)) match + def lineFor(sourcePos: SourcePosition, lastRealNr: Int): Option[Int] = + requests.find(r => r.origPos.contains(sourcePos) && r.targetPos.endLine + 1 >= lastRealNr) match case Some(request) => val offset = sourcePos.startLine - request.origPos.startLine Some(request.firstFakeLine + offset + 1) From c37e569d27cd0f1a43bb66132d56930c6db09353 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Sun, 21 Feb 2021 18:50:00 +0100 Subject: [PATCH 10/44] Disable YCheckPosition --- compiler/src/dotty/tools/dotc/Compiler.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From d68a3a84403410907f618199a97a5772ca81cb51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Sun, 21 Feb 2021 19:16:02 +0100 Subject: [PATCH 11/44] Add explanation to InlinedSourceMaps --- .../tools/backend/jvm/InlinedSourceMaps.scala | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala index 8062c80f4f5d..de476aaf50e2 100644 --- a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala +++ b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala @@ -11,7 +11,27 @@ import dotc.report import dotc.typer.Inliner.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(targetPos: SourcePosition, origPos: SourcePosition, firstFakeLine: Int) From 1ff59ed6b08ecd4a83c2977a01494097e293ceb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Mon, 22 Mar 2021 14:42:10 +0100 Subject: [PATCH 12/44] Skip inline proxies during accessibility assertions --- compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala b/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala index 41e5b76ca874..545fe70488a2 100644 --- a/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala +++ b/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala @@ -66,7 +66,7 @@ class ExpandPrivate extends MiniPhase with IdentityDenotTransformer { thisPhase private def ensurePrivateAccessible(d: SymDenotation)(using Context) = if (isVCPrivateParamAccessor(d)) d.ensureNotPrivate.installAfter(thisPhase) - else if (d.is(PrivateTerm) && !d.owner.is(Package) && d.owner != ctx.owner.lexicallyEnclosingClass) { + else if (d.is(PrivateTerm) && !d.owner.is(Package) && d.owner != ctx.owner.lexicallyEnclosingClass && !d.is(InlineProxy)) { // Paths `p1` and `p2` are similar if they have a common suffix that follows // possibly different directory paths. That is, their common suffix extends // in both cases either to the start of the path or to a file separator character. From fd200c8e85ae8606431c2bf5e642a7c0eb30abca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Wed, 24 Mar 2021 09:53:22 +0100 Subject: [PATCH 13/44] Sort inlining requests before creating SMAP Fixes SMAP generation breaking bytecode idempotency assertions --- .../dotty/tools/backend/jvm/InlinedSourceMaps.scala | 11 ++++++----- compiler/src/dotty/tools/dotc/typer/Inliner.scala | 8 ++------ 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala index de476aaf50e2..9db3bc0bc443 100644 --- a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala +++ b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala @@ -82,8 +82,7 @@ object InlinedSourceMaps: end Stratum def sourceMapFor(cunit: CompilationUnit)(internalNameProvider: Symbol => String)(using Context): InlinedSourceMap = - val requests = mutable.ListBuffer.empty[Request] - var lastLine = cunit.tpdTree.sourcePos.endLine + val requests = mutable.ListBuffer.empty[(SourcePosition, SourcePosition)] var internalNames = Map.empty[SourceFile, String] class RequestCollector(enclosingFile: SourceFile) extends TreeTraverser: @@ -91,8 +90,8 @@ object InlinedSourceMaps: if tree.source != enclosingFile && tree.source != cunit.source then tree.getAttachment(InliningPosition) match case Some(InliningPosition(targetPos, cls)) => - val firstFakeLine = allocate(tree.sourcePos) - requests += Request(targetPos, tree.sourcePos, firstFakeLine) + requests += (targetPos -> tree.sourcePos) + cls match case Some(symbol) if !internalNames.isDefinedAt(tree.source) => internalNames += (tree.source -> internalNameProvider(symbol)) @@ -108,13 +107,15 @@ object InlinedSourceMaps: else traverseChildren(tree) end RequestCollector + var lastLine = cunit.tpdTree.sourcePos.endLine def allocate(origPos: SourcePosition): Int = val line = lastLine + 1 lastLine += origPos.lines.length line RequestCollector(cunit.source).traverse(cunit.tpdTree) - InlinedSourceMap(cunit, requests.toList, internalNames) + val allocated = requests.sortBy(_._1.start).map(r => Request(r._1, r._2, allocate(r._2))) + InlinedSourceMap(cunit, allocated.toList, internalNames) end sourceMapFor class InlinedSourceMap private[InlinedSourceMaps] ( diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 769acae2ade4..c93abcff87af 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -230,12 +230,8 @@ object Inliner { val topLevelClass = Some(inlined.call.symbol.topLevelClass).filter(_.exists) val inliningPosition = InliningPosition(inlined.sourcePos, topLevelClass) - // In case that bindings are present we are adding InliningPosition attachement both to the resulting block and to the expansion - // as in some cases the block is removed in one of later phases and attachment is lost. - val tree1 = - if inlined.bindings.isEmpty then inlined.expansion - else cpy.Block(inlined)(inlined.bindings, inlined.expansion.withAttachment(InliningPosition, inliningPosition)) - tree1.withAttachment(InliningPosition, inliningPosition) + val withPos = inlined.expansion.withAttachment(InliningPosition, inliningPosition) + if inlined.bindings.isEmpty then withPos else cpy.Block(inlined)(inlined.bindings, withPos) /** Leave only a call trace consisting of * - a reference to the top-level class from which the call was inlined, From b0ebeed721cbc24a5c1c042fa1088811f6b9fe95 Mon Sep 17 00:00:00 2001 From: Anatolii Kmetiuk Date: Fri, 15 Jul 2022 11:24:39 +0200 Subject: [PATCH 14/44] Adapt Inliner to the most recent refactoring on the `main` --- .../tools/backend/jvm/InlinedSourceMaps.scala | 2 +- .../dotty/tools/dotc/inlines/Inlines.scala | 61 +- .../src/dotty/tools/dotc/typer/Inliner.scala | 1585 ----------------- 3 files changed, 9 insertions(+), 1639 deletions(-) delete mode 100644 compiler/src/dotty/tools/dotc/typer/Inliner.scala diff --git a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala index 9db3bc0bc443..4bd47ab1a0e3 100644 --- a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala +++ b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala @@ -8,7 +8,7 @@ import dotc.util.{ SourcePosition, SourceFile } import dotc.core.Contexts._ import dotc.core.Symbols.Symbol import dotc.report -import dotc.typer.Inliner.InliningPosition +import dotc.inlines.Inlines.InliningPosition import collection.mutable /** diff --git a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala index bcc10ffa6db8..38b4047a9643 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala @@ -11,7 +11,7 @@ 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.util.{SourceFile, SourcePosition, SrcPos, Property} import parsing.Parsers.Parser import transform.{PostTyper, Inlining, CrossVersionChecks} import staging.StagingLevel @@ -29,6 +29,9 @@ object Inlines: */ private[dotc] class MissingInlineInfo extends Exception + object InliningPosition extends Property.StickyKey[InliningPosition] + case class InliningPosition(sourcePos: SourcePosition, topLevelSymbol: Option[Symbol]) + /** `sym` is an inline method with a known body to inline. */ def hasBodyToInline(sym: SymDenotation)(using Context): Boolean = @@ -241,58 +244,10 @@ 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 - } - } - - /** 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 + val topLevelClass = Option.when(!inlined.call.isEmpty)(inlined.call.symbol.topLevelClass) + val inliningPosition = InliningPosition(inlined.sourcePos, topLevelClass) + val withPos = inlined.expansion.withAttachment(InliningPosition, inliningPosition) + if inlined.bindings.isEmpty then withPos else cpy.Block(inlined)(inlined.bindings, withPos) /** 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/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala deleted file mode 100644 index c93abcff87af..000000000000 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ /dev/null @@ -1,1585 +0,0 @@ -package dotty.tools -package dotc -package typer - -import ast.{TreeInfo, tpd, _} -import Trees._ -import core._ -import Flags._ -import Symbols._ -import Types._ -import Decorators._ -import Constants._ -import StagingContext._ -import StdNames._ -import transform.SymUtils._ -import Contexts._ -import Names.{Name, TermName} -import NameKinds.{InlineAccessorName, InlineBinderName, InlineScrutineeName, BodyRetainerName} -import ProtoTypes.shallowSelectionProto -import Annotations.Annotation -import SymDenotations.SymDenotation -import Inferencing.isFullyDefined -import config.Printers.inlining -import ErrorReporting.errorTree -import dotty.tools.dotc.util.{SimpleIdentityMap, SimpleIdentitySet, EqHashMap, SourceFile, SourcePosition, SrcPos} -import dotty.tools.dotc.parsing.Parsers.Parser -import Nullables._ -import transform.{PostTyper, Inlining} - -import collection.mutable -import reporting.trace -import util.Spans.Span -import util.Property -import dotty.tools.dotc.transform.{Splicer, TreeMapWithStages} -import quoted.QuoteUtils - -object Inliner { - import tpd._ - - object InliningPosition extends Property.StickyKey[InliningPosition] - case class InliningPosition(sourcePos: SourcePosition, topLevelSymbol: Option[Symbol]) - - /** `sym` is an inline method with a known body to inline. - */ - def hasBodyToInline(sym: SymDenotation)(using Context): Boolean = - sym.isInlineMethod && sym.hasAnnotation(defn.BodyAnnot) - - /** The body to inline for method `sym`, or `EmptyTree` if none exists. - * @pre hasBodyToInline(sym) - */ - def bodyToInline(sym: SymDenotation)(using Context): Tree = - if hasBodyToInline(sym) then - sym.getAnnotation(defn.BodyAnnot).get.tree - else - EmptyTree - - /** Are we in an inline method body? */ - def inInlineMethod(using Context): Boolean = - ctx.owner.ownersIterator.exists(_.isInlineMethod) - - /** Can a call to method `meth` be inlined? */ - def isInlineable(meth: Symbol)(using Context): Boolean = - meth.is(Inline) && meth.hasAnnotation(defn.BodyAnnot) && !inInlineMethod - - /** Should call be inlined in this context? */ - def needsInlining(tree: Tree)(using Context): Boolean = tree match { - case Block(_, expr) => needsInlining(expr) - case _ => - isInlineable(tree.symbol) - && !tree.tpe.widenTermRefExpr.isInstanceOf[MethodOrPoly] - && StagingContext.level == 0 - && ( - ctx.phase == Phases.inliningPhase - || (ctx.phase == Phases.typerPhase && needsTransparentInlining(tree)) - ) - && !ctx.typer.hasInliningErrors - } - - private def needsTransparentInlining(tree: Tree)(using Context): Boolean = - tree.symbol.is(Transparent) - || ctx.mode.is(Mode.ForceInline) - || ctx.settings.YforceInlineWhileTyping.value - - /** Try to inline a call to an inline method. Fail with error if the maximal - * inline depth is exceeded. - * - * @param tree The call to inline - * @param pt The expected type of the call. - * @return An `Inlined` node that refers to the original call and the inlined bindings - * and body that replace it. - */ - def inlineCall(tree: Tree)(using Context): Tree = { - if tree.symbol.denot != SymDenotations.NoDenotation - && tree.symbol.effectiveOwner == defn.CompiletimeTestingPackage.moduleClass - then - if (tree.symbol == defn.CompiletimeTesting_typeChecks) return Intrinsics.typeChecks(tree) - if (tree.symbol == defn.CompiletimeTesting_typeCheckErrors) return Intrinsics.typeCheckErrors(tree) - - - /** Set the position of all trees logically contained in the expansion of - * inlined call `call` to the position of `call`. This transform is necessary - * when lifting bindings from the expansion to the outside of the call. - */ - def liftFromInlined(call: Tree) = new TreeMap: - override def transform(t: Tree)(using Context) = - if call.span.exists then - t match - case Inlined(t, Nil, expr) if t.isEmpty => expr - case _ if t.isEmpty => t - case _ => super.transform(t.withSpan(call.span)) - else t - end liftFromInlined - - val bindings = new mutable.ListBuffer[Tree] - - /** Lift bindings around inline call or in its function part to - * the `bindings` buffer. This is done as an optimization to keep - * inline call expansions smaller. - */ - def liftBindings(tree: Tree, liftPos: Tree => Tree): Tree = tree match { - case Block(stats, expr) => - bindings ++= stats.map(liftPos) - liftBindings(expr, liftPos) - case Inlined(call, stats, expr) => - bindings ++= stats.map(liftPos) - val lifter = liftFromInlined(call) - cpy.Inlined(tree)(call, Nil, liftBindings(expr, liftFromInlined(call).transform(_))) - case Apply(fn, args) => - cpy.Apply(tree)(liftBindings(fn, liftPos), args) - case TypeApply(fn, args) => - cpy.TypeApply(tree)(liftBindings(fn, liftPos), args) - case Select(qual, name) => - cpy.Select(tree)(liftBindings(qual, liftPos), name) - case _ => - tree - } - - // assertAllPositioned(tree) // debug - val tree1 = liftBindings(tree, identity) - val tree2 = - if bindings.nonEmpty then - cpy.Block(tree)(bindings.toList, inlineCall(tree1)) - else if enclosingInlineds.length < ctx.settings.XmaxInlines.value && !reachedInlinedTreesLimit then - val body = bodyToInline(tree.symbol) // can typecheck the tree and thereby produce errors - new Inliner(tree, body).inlined(tree.srcPos) - else - val (reason, setting) = - if reachedInlinedTreesLimit then ("inlined trees", ctx.settings.XmaxInlinedTrees) - else ("successive inlines", ctx.settings.XmaxInlines) - errorTree( - tree, - i"""|Maximal number of $reason (${setting.value}) exceeded, - |Maybe this is caused by a recursive inline method? - |You can use ${setting.name} to change the limit.""", - (tree :: enclosingInlineds).last.srcPos - ) - tree2 - } - - /** Try to inline a pattern with an inline unapply method. Fail with error if the maximal - * inline depth is exceeded. - * - * @param unapp The tree of the pattern to inline - * @return An `Unapply` with a `fun` containing the inlined call to the unapply - */ - def inlinedUnapply(unapp: tpd.UnApply)(using Context): Tree = { - // We cannot inline the unapply directly, since the pattern matcher relies on unapply applications - // as signposts what to do. On the other hand, we can do the inlining only in typer, not afterwards. - // So the trick is to create a "wrapper" unapply in an anonymous class that has the inlined unapply - // as its right hand side. The call to the wrapper unapply serves as the signpost for pattern matching. - // After pattern matching, the anonymous class is removed in phase InlinePatterns with a beta reduction step. - // - // An inline unapply `P.unapply` in a plattern `P(x1,x2,...)` is transformed into - // `{ class $anon { def unapply(t0: T0)(using t1: T1, t2: T2, ...): R = P.unapply(t0)(using t1, t2, ...) }; new $anon }.unapply` - // and the call `P.unapply(x1, x2, ...)` is inlined. - // This serves as a placeholder for the inlined body until the `patternMatcher` phase. After pattern matcher - // transforms the patterns into terms, the `inlinePatterns` phase removes this anonymous class by β-reducing - // the call to the `unapply`. - - val UnApply(fun, implicits, patterns) = unapp - val sym = unapp.symbol - val cls = newNormalizedClassSymbol(ctx.owner, tpnme.ANON_CLASS, Synthetic | Final, List(defn.ObjectType), coord = sym.coord) - val constr = newConstructor(cls, Synthetic, Nil, Nil, coord = sym.coord).entered - - val targs = fun match - case TypeApply(_, targs) => targs - case _ => Nil - val unapplyInfo = sym.info match - case info: PolyType => info.instantiate(targs.map(_.tpe)) - case info => info - - val unappplySym = newSymbol(cls, sym.name.toTermName, Synthetic | Method, unapplyInfo, coord = sym.coord).entered - val unapply = DefDef(unappplySym, argss => - inlineCall(fun.appliedToArgss(argss).withSpan(unapp.span))(using ctx.withOwner(unappplySym)) - ) - val cdef = ClassDef(cls, DefDef(constr), List(unapply)) - val newUnapply = Block(cdef :: Nil, New(cls.typeRef, Nil)) - val newFun = newUnapply.select(unappplySym).withSpan(unapp.span) - cpy.UnApply(unapp)(newFun, implicits, patterns) - } - - /** For a retained inline method, another method that keeps track of - * the body that is kept at runtime. For instance, an inline method - * - * inline override def f(x: T) = b - * - * is complemented by the body retainer method - * - * private def f$retainedBody(x: T) = f(x) - * - * where the call `f(x)` is inline-expanded. This body is then transferred - * back to `f` at erasure, using method addRetainedInlineBodies. - */ - def bodyRetainer(mdef: DefDef)(using Context): DefDef = - val meth = mdef.symbol.asTerm - - val retainer = meth.copy( - name = BodyRetainerName(meth.name), - flags = meth.flags &~ (Inline | Macro | Override) | Private, - coord = mdef.rhs.span.startPos).asTerm - retainer.deriveTargetNameAnnotation(meth, name => BodyRetainerName(name.asTermName)) - DefDef(retainer, prefss => - inlineCall( - ref(meth).appliedToArgss(prefss).withSpan(mdef.rhs.span.startPos))( - using ctx.withOwner(retainer))) - .showing(i"retainer for $meth: $result", inlining) - - /** Replace `Inlined` node by a block that contains its bindings and expansion */ - def dropInlined(inlined: Inlined)(using Context): Tree = - val topLevelClass = Some(inlined.call.symbol.topLevelClass).filter(_.exists) - val inliningPosition = InliningPosition(inlined.sourcePos, topLevelClass) - - val withPos = inlined.expansion.withAttachment(InliningPosition, inliningPosition) - if inlined.bindings.isEmpty then withPos else cpy.Block(inlined)(inlined.bindings, withPos) - - /** 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. - * Note: For macros it returns a Select and for other inline methods it returns an Ident (this distinction is only temporary to be able to run YCheckPositions) - */ - def inlineCallTrace(callSym: Symbol, pos: SourcePosition)(using Context): Tree = { - assert(ctx.source == pos.source) - if (callSym.is(Macro)) ref(callSym.topLevelClass.owner).select(callSym.topLevelClass.name).withSpan(pos.span) - else Ident(callSym.topLevelClass.typeRef).withSpan(pos.span) - } - - object Intrinsics { - import dotty.tools.dotc.reporting.Diagnostic.Error - private enum ErrorKind: - case Parser, Typer - - private def compileForErrors(tree: Tree)(using Context): List[(ErrorKind, Error)] = - assert(tree.symbol == defn.CompiletimeTesting_typeChecks || tree.symbol == defn.CompiletimeTesting_typeCheckErrors) - def stripTyped(t: Tree): Tree = t match { - case Typed(t2, _) => stripTyped(t2) - case Block(Nil, t2) => stripTyped(t2) - case Inlined(_, Nil, t2) => stripTyped(t2) - case _ => t - } - - val Apply(_, codeArg :: Nil) = tree - val codeArg1 = stripTyped(codeArg.underlying) - val underlyingCodeArg = - if Inliner.isInlineable(codeArg1.symbol) then stripTyped(Inliner.inlineCall(codeArg1)) - else codeArg1 - - ConstFold(underlyingCodeArg).tpe.widenTermRefExpr match { - case ConstantType(Constant(code: String)) => - val source2 = SourceFile.virtual("tasty-reflect", code) - inContext(ctx.fresh.setNewTyperState().setTyper(new Typer).setSource(source2)) { - val tree2 = new Parser(source2).block() - if ctx.reporter.allErrors.nonEmpty then - ctx.reporter.allErrors.map((ErrorKind.Parser, _)) - else - val tree3 = ctx.typer.typed(tree2) - ctx.base.postTyperPhase match - case postTyper: PostTyper if ctx.reporter.allErrors.isEmpty => - val tree4 = atPhase(postTyper) { postTyper.newTransformer.transform(tree3) } - ctx.base.inliningPhase match - case inlining: Inlining if ctx.reporter.allErrors.isEmpty => - atPhase(inlining) { inlining.newTransformer.transform(tree4) } - case _ => - case _ => - ctx.reporter.allErrors.map((ErrorKind.Typer, _)) - } - case t => - report.error(em"argument to compileError must be a statically known String but was: $codeArg", codeArg1.srcPos) - Nil - } - - private def packError(kind: ErrorKind, error: Error)(using Context): Tree = - def lit(x: Any) = Literal(Constant(x)) - val constructor: Tree = ref(defn.CompiletimeTesting_Error_apply) - val parserErrorKind: Tree = ref(defn.CompiletimeTesting_ErrorKind_Parser) - val typerErrorKind: Tree = ref(defn.CompiletimeTesting_ErrorKind_Typer) - - constructor.appliedTo( - lit(error.message), - lit(error.pos.lineContent.reverse.dropWhile("\n ".contains).reverse), - lit(error.pos.column), - if kind == ErrorKind.Parser then parserErrorKind else typerErrorKind) - - private def packErrors(errors: List[(ErrorKind, Error)])(using Context): Tree = - val individualErrors: List[Tree] = errors.map(packError) - val errorTpt = ref(defn.CompiletimeTesting_ErrorClass) - mkList(individualErrors, errorTpt) - - /** Expand call to scala.compiletime.testing.typeChecks */ - def typeChecks(tree: Tree)(using Context): Tree = - val errors = compileForErrors(tree) - Literal(Constant(errors.isEmpty)).withSpan(tree.span) - - /** Expand call to scala.compiletime.testing.typeCheckErrors */ - def typeCheckErrors(tree: Tree)(using Context): Tree = - val errors = compileForErrors(tree) - packErrors(errors) - - /** Expand call to scala.compiletime.codeOf */ - def codeOf(arg: Tree, pos: SrcPos)(using Context): Tree = - Literal(Constant(arg.show)).withSpan(pos.span) - } - - extension (tp: Type) { - - /** same as widenTermRefExpr, but preserves modules and singleton enum values */ - private final def widenInlineScrutinee(using Context): Type = tp.stripTypeVar match { - case tp: TermRef => - val sym = tp.termSymbol - if sym.isAllOf(EnumCase, butNot=JavaDefined) || sym.is(Module) then tp - else if !tp.isOverloaded then tp.underlying.widenExpr.widenInlineScrutinee - else tp - case _ => tp - } - - } - -} - -/** Produces an inlined version of `call` via its `inlined` method. - * - * @param call the original call to an inlineable method - * @param rhsToInline the body of the inlineable method that replaces the call. - */ -class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) { - import tpd._ - import Inliner._ - - private val methPart = funPart(call) - private val callTypeArgs = typeArgss(call).flatten - private val callValueArgss = termArgss(call) - private val inlinedMethod = methPart.symbol - private val inlineCallPrefix = - qualifier(methPart).orElse(This(inlinedMethod.enclosingClass.asClass)) - - inlining.println(i"-----------------------\nInlining $call\nWith RHS $rhsToInline") - - // Make sure all type arguments to the call are fully determined, - // but continue if that's not achievable (or else i7459.scala would crash). - for arg <- callTypeArgs do - isFullyDefined(arg.tpe, ForceDegree.flipBottom) - - /** A map from parameter names of the inlineable method to references of the actual arguments. - * For a type argument this is the full argument type. - * For a value argument, it is a reference to either the argument value - * (if the argument is a pure expression of singleton type), or to `val` or `def` acting - * as a proxy (if the argument is something else). - */ - private val paramBinding = new mutable.HashMap[Name, Type] - - /** A map from parameter names of the inlineable method to spans of the actual arguments */ - private val paramSpan = new mutable.HashMap[Name, Span] - - /** A map from references to (type and value) parameters of the inlineable method - * to their corresponding argument or proxy references, as given by `paramBinding`. - */ - private val paramProxy = new mutable.HashMap[Type, Type] - - /** A map from the classes of (direct and outer) this references in `rhsToInline` - * to references of their proxies. - * Note that we can't index by the ThisType itself since there are several - * possible forms to express what is logicaly the same ThisType. E.g. - * - * ThisType(TypeRef(ThisType(p), cls)) - * - * vs - * - * ThisType(TypeRef(TermRef(ThisType(), p), cls)) - * - * These are different (wrt ==) types but represent logically the same key - */ - private val thisProxy = new mutable.HashMap[ClassSymbol, TermRef] - - /** A buffer for bindings that define proxies for actual arguments */ - private val bindingsBuf = new mutable.ListBuffer[ValOrDefDef] - - private def newSym(name: Name, flags: FlagSet, info: Type)(using Context): Symbol = - newSymbol(ctx.owner, name, flags, info, coord = call.span) - - /** A binding for the parameter of an inline method. This is a `val` def for - * by-value parameters and a `def` def for by-name parameters. `val` defs inherit - * inline annotations from their parameters. The generated `def` is appended - * to `bindingsBuf`. - * @param name the name of the parameter - * @param formal the type of the parameter - * @param arg the argument corresponding to the parameter - * @param bindingsBuf the buffer to which the definition should be appended - */ - private def paramBindingDef(name: Name, formal: Type, arg0: Tree, - bindingsBuf: mutable.ListBuffer[ValOrDefDef])(using Context): ValOrDefDef = { - val isByName = formal.dealias.isInstanceOf[ExprType] - val arg = arg0 match { - case Typed(arg1, tpt) if tpt.tpe.isRepeatedParam && arg1.tpe.derivesFrom(defn.ArrayClass) => - wrapArray(arg1, arg0.tpe.elemType) - case _ => arg0 - } - val argtpe = arg.tpe.dealiasKeepAnnots.translateFromRepeated(toArray = false) - val argIsBottom = argtpe.isBottomTypeAfterErasure - val bindingType = - if argIsBottom then formal - else if isByName then ExprType(argtpe.widen) - else argtpe.widen - var bindingFlags: FlagSet = InlineProxy - if formal.widenExpr.hasAnnotation(defn.InlineParamAnnot) then - bindingFlags |= Inline - if isByName then - bindingFlags |= Method - val boundSym = newSym(InlineBinderName.fresh(name.asTermName), bindingFlags, bindingType).asTerm - val binding = { - var newArg = arg.changeOwner(ctx.owner, boundSym) - if bindingFlags.is(Inline) && argIsBottom then - newArg = Typed(newArg, TypeTree(formal)) // type ascribe RHS to avoid type errors in expansion. See i8612.scala - if isByName then DefDef(boundSym, newArg) - else ValDef(boundSym, newArg) - }.withSpan(boundSym.span) - inlining.println(i"parameter binding: $binding, $argIsBottom") - bindingsBuf += binding - binding - } - - /** Populate `paramBinding` and `bindingsBuf` by matching parameters with - * corresponding arguments. `bindingbuf` will be further extended later by - * proxies to this-references. Issue an error if some arguments are missing. - */ - private def computeParamBindings( - tp: Type, targs: List[Tree], argss: List[List[Tree]], formalss: List[List[Type]]): Boolean = - tp match - case tp: PolyType => - tp.paramNames.lazyZip(targs).foreach { (name, arg) => - paramSpan(name) = arg.span - paramBinding(name) = arg.tpe.stripTypeVar - } - computeParamBindings(tp.resultType, targs.drop(tp.paramNames.length), argss, formalss) - case tp: MethodType => - if argss.isEmpty then - report.error(i"missing arguments for inline method $inlinedMethod", call.srcPos) - false - else - tp.paramNames.lazyZip(formalss.head).lazyZip(argss.head).foreach { (name, formal, arg) => - paramSpan(name) = arg.span - paramBinding(name) = arg.tpe.dealias match - case _: SingletonType if isIdempotentPath(arg) => - arg.tpe - case _ => - paramBindingDef(name, formal, arg, bindingsBuf).symbol.termRef - } - computeParamBindings(tp.resultType, targs, argss.tail, formalss.tail) - case _ => - assert(targs.isEmpty) - assert(argss.isEmpty) - true - - // Compute val-definitions for all this-proxies and append them to `bindingsBuf` - private def computeThisBindings() = { - // All needed this-proxies, paired-with and sorted-by nesting depth of - // the classes they represent (innermost first) - val sortedProxies = thisProxy.toList - .map((cls, proxy) => (cls.ownersIterator.length, proxy.symbol)) - .sortBy(-_._1) - - var lastSelf: Symbol = NoSymbol - var lastLevel: Int = 0 - for ((level, selfSym) <- sortedProxies) { - lazy val rhsClsSym = selfSym.info.widenDealias.classSymbol - val rhs = selfSym.info.dealias match - case info: TermRef if info.isStable => - ref(info) - case info => - val rhsClsSym = info.widenDealias.classSymbol - if rhsClsSym.is(Module) && rhsClsSym.isStatic then - ref(rhsClsSym.sourceModule) - else if lastSelf.exists then - ref(lastSelf).outerSelect(lastLevel - level, selfSym.info) - else - inlineCallPrefix - val binding = ValDef(selfSym.asTerm, QuoteUtils.changeOwnerOfTree(rhs, selfSym)).withSpan(selfSym.span) - bindingsBuf += binding - inlining.println(i"proxy at $level: $selfSym = ${bindingsBuf.last}") - lastSelf = selfSym - lastLevel = level - } - } - - private def canElideThis(tpe: ThisType): Boolean = - inlineCallPrefix.tpe == tpe && ctx.owner.isContainedIn(tpe.cls) || - tpe.cls.isContainedIn(inlinedMethod) || - tpe.cls.is(Package) - - /** Very similar to TreeInfo.isPureExpr, but with the following inliner-only exceptions: - * - synthetic case class apply methods, when the case class constructor is empty, are - * elideable but not pure. Elsewhere, accessing the apply method might cause the initialization - * of a containing object so they are merely idempotent. - */ - object isElideableExpr { - def isStatElideable(tree: Tree)(using Context): Boolean = unsplice(tree) match { - case EmptyTree - | TypeDef(_, _) - | Import(_, _) - | DefDef(_, _, _, _) => - true - case vdef @ ValDef(_, _, _) => - if (vdef.symbol.flags is Mutable) false else apply(vdef.rhs) - case _ => - false - } - - def apply(tree: Tree): Boolean = unsplice(tree) match { - case EmptyTree - | This(_) - | Super(_, _) - | Literal(_) => - true - case Ident(_) => - isPureRef(tree) || tree.symbol.isAllOf(Inline | Param) - case Select(qual, _) => - if (tree.symbol.is(Erased)) true - else isPureRef(tree) && apply(qual) - case New(_) | Closure(_, _, _) => - true - case TypeApply(fn, _) => - if (fn.symbol.is(Erased) || fn.symbol == defn.QuotedTypeModule_of) true else apply(fn) - case Apply(fn, args) => - val isCaseClassApply = { - val cls = tree.tpe.classSymbol - val meth = fn.symbol - meth.name == nme.apply && - meth.flags.is(Synthetic) && - meth.owner.linkedClass.is(Case) && - cls.isNoInitsRealClass - } - if isPureApply(tree, fn) then - apply(fn) && args.forall(apply) - else if (isCaseClassApply) - args.forall(apply) - else if (fn.symbol.is(Erased)) true - else false - case Typed(expr, _) => - apply(expr) - case Block(stats, expr) => - apply(expr) && stats.forall(isStatElideable) - case Inlined(_, bindings, expr) => - apply(expr) && bindings.forall(isStatElideable) - case NamedArg(_, expr) => - apply(expr) - case _ => - false - } - } - - /** Populate `thisProxy` and `paramProxy` as follows: - * - * 1a. If given type refers to a static this, thisProxy binds it to corresponding global reference, - * 1b. If given type refers to an instance this to a class that is not contained in the - * inline method, create a proxy symbol and bind the thistype to refer to the proxy. - * The proxy is not yet entered in `bindingsBuf`; that will come later. - * 2. If given type refers to a parameter, make `paramProxy` refer to the entry stored - * in `paramNames` under the parameter's name. This roundabout way to bind parameter - * references to proxies is done because we don't know a priori what the parameter - * references of a method are (we only know the method's type, but that contains TypeParamRefs - * and MethodParams, not TypeRefs or TermRefs. - */ - private def registerType(tpe: Type): Unit = tpe match { - case tpe: ThisType if !canElideThis(tpe) && !thisProxy.contains(tpe.cls) => - val proxyName = s"${tpe.cls.name}_this".toTermName - def adaptToPrefix(tp: Type) = tp.asSeenFrom(inlineCallPrefix.tpe, inlinedMethod.owner) - val proxyType = inlineCallPrefix.tpe.dealias.tryNormalize match { - case typeMatchResult if typeMatchResult.exists => typeMatchResult - case _ => adaptToPrefix(tpe).widenIfUnstable - } - thisProxy(tpe.cls) = newSym(proxyName, InlineProxy, proxyType).termRef - if (!tpe.cls.isStaticOwner) - registerType(inlinedMethod.owner.thisType) // make sure we have a base from which to outer-select - for (param <- tpe.cls.typeParams) - paramProxy(param.typeRef) = adaptToPrefix(param.typeRef) - case tpe: NamedType - if tpe.symbol.is(Param) && tpe.symbol.owner == inlinedMethod && !paramProxy.contains(tpe) => - paramBinding.get(tpe.name) match - case Some(bound) => paramProxy(tpe) = bound - case _ => // can happen for params bound by type-lambda trees. - case _ => - } - - /** Register type of leaf node */ - private def registerLeaf(tree: Tree): Unit = tree match { - case _: This | _: Ident | _: TypeTree => - tree.typeOpt.foreachPart(registerType, stopAtStatic = true) - case _ => - } - - /** Make `tree` part of inlined expansion. This means its owner has to be changed - * from its `originalOwner`, and, if it comes from outside the inlined method - * itself, it has to be marked as an inlined argument. - */ - def integrate(tree: Tree, originalOwner: Symbol)(using Context): Tree = - // assertAllPositioned(tree) // debug - tree.changeOwner(originalOwner, ctx.owner) - - def tryConstValue: Tree = - TypeComparer.constValue(callTypeArgs.head.tpe) match { - case Some(c) => Literal(c).withSpan(call.span) - case _ => EmptyTree - } - - /** The Inlined node representing the inlined call */ - def inlined(sourcePos: SrcPos): Tree = { - - // Special handling of `requireConst` and `codeOf` - callValueArgss match - case (arg :: Nil) :: Nil => - if inlinedMethod == defn.Compiletime_requireConst then - arg match - case ConstantValue(_) | Inlined(_, Nil, Typed(ConstantValue(_), _)) => // ok - case _ => report.error(em"expected a constant value but found: $arg", arg.srcPos) - return Literal(Constant(())).withSpan(sourcePos.span) - else if inlinedMethod == defn.Compiletime_codeOf then - return Intrinsics.codeOf(arg, call.srcPos) - case _ => - - // Special handling of `constValue[T]` and `constValueOpt[T]` - if (callTypeArgs.length == 1) - if (inlinedMethod == defn.Compiletime_constValue) { - val constVal = tryConstValue - if (!constVal.isEmpty) return constVal - report.error(em"not a constant type: ${callTypeArgs.head}; cannot take constValue", call.srcPos) - } - else if (inlinedMethod == defn.Compiletime_constValueOpt) { - val constVal = tryConstValue - return ( - if (constVal.isEmpty) ref(defn.NoneModule.termRef) - else New(defn.SomeClass.typeRef.appliedTo(constVal.tpe), constVal :: Nil) - ) - } - - def paramTypess(call: Tree, acc: List[List[Type]]): List[List[Type]] = call match - case Apply(fn, args) => - fn.tpe.widen.match - case mt: MethodType => paramTypess(fn, mt.instantiateParamInfos(args.tpes) :: acc) - case _ => Nil - case TypeApply(fn, _) => paramTypess(fn, acc) - case _ => acc - - // Compute bindings for all parameters, appending them to bindingsBuf - if !computeParamBindings(inlinedMethod.info, callTypeArgs, callValueArgss, paramTypess(call, Nil)) then - return call - - // make sure prefix is executed if it is impure - if (!isIdempotentExpr(inlineCallPrefix)) registerType(inlinedMethod.owner.thisType) - - // Register types of all leaves of inlined body so that the `paramProxy` and `thisProxy` maps are defined. - rhsToInline.foreachSubTree(registerLeaf) - - // Compute bindings for all this-proxies, appending them to bindingsBuf - computeThisBindings() - - val inlineTyper = new InlineTyper(ctx.reporter.errorCount) - - val inlineCtx = inlineContext(call).fresh.setTyper(inlineTyper).setNewScope - - def inlinedFromOutside(tree: Tree)(span: Span): Tree = - Inlined(EmptyTree, Nil, tree)(using ctx.withSource(inlinedMethod.topLevelClass.source)).withSpan(span) - - // InlinerMap is a TreeTypeMap with special treatment for inlined arguments: - // They are generally left alone (not mapped further, and if they wrap a type - // the type Inlined wrapper gets dropped - class InlinerMap( - typeMap: Type => Type, - treeMap: Tree => Tree, - oldOwners: List[Symbol], - newOwners: List[Symbol], - substFrom: List[Symbol], - substTo: List[Symbol])(using Context) - extends TreeTypeMap(typeMap, treeMap, oldOwners, newOwners, substFrom, substTo): - - override def copy( - typeMap: Type => Type, - treeMap: Tree => Tree, - oldOwners: List[Symbol], - newOwners: List[Symbol], - substFrom: List[Symbol], - substTo: List[Symbol])(using Context) = - new InlinerMap(typeMap, treeMap, oldOwners, newOwners, substFrom, substTo) - - override def transformInlined(tree: Inlined)(using Context) = - if tree.call.isEmpty then - tree.expansion match - case expansion: TypeTree => expansion - case _ => tree - else super.transformInlined(tree) - end InlinerMap - - // A tree type map to prepare the inlined body for typechecked. - // The translation maps references to `this` and parameters to - // corresponding arguments or proxies on the type and term level. It also changes - // the owner from the inlined method to the current owner. - val inliner = new InlinerMap( - typeMap = - new DeepTypeMap { - def apply(t: Type) = t match { - case t: ThisType => thisProxy.getOrElse(t.cls, t) - case t: TypeRef => paramProxy.getOrElse(t, mapOver(t)) - case t: SingletonType => - if t.termSymbol.isAllOf(Inline | Param) then mapOver(t.widenTermRefExpr) - else paramProxy.getOrElse(t, mapOver(t)) - case t => mapOver(t) - } - }, - treeMap = { - case tree: This => - tree.tpe match { - case thistpe: ThisType => - thisProxy.get(thistpe.cls) match { - case Some(t) => - val thisRef = ref(t).withSpan(call.span) - inlinedFromOutside(thisRef)(tree.span) - case None => tree - } - case _ => tree - } - case tree: Ident => - /* Span of the argument. Used when the argument is inlined directly without a binding */ - def argSpan = - if (tree.name == nme.WILDCARD) tree.span // From type match - else if (tree.symbol.isTypeParam && tree.symbol.owner.isClass) tree.span // TODO is this the correct span? - else paramSpan(tree.name) - val inlinedCtx = ctx.withSource(inlinedMethod.topLevelClass.source) - paramProxy.get(tree.tpe) match { - case Some(t) if tree.isTerm && t.isSingleton => - val inlinedSingleton = singleton(t).withSpan(argSpan) - inlinedFromOutside(inlinedSingleton)(tree.span) - case Some(t) if tree.isType => - inlinedFromOutside(TypeTree(t).withSpan(argSpan))(tree.span) - case _ => tree - } - case tree => tree - }, - oldOwners = inlinedMethod :: Nil, - newOwners = ctx.owner :: Nil, - substFrom = Nil, - substTo = Nil - )(using inlineCtx) - - // Apply inliner to `rhsToInline`, split off any implicit bindings from result, and - // make them part of `bindingsBuf`. The expansion is then the tree that remains. - val expansion = inliner.transform(rhsToInline) - - def issueError() = callValueArgss match { - case (msgArg :: Nil) :: Nil => - val message = msgArg.tpe match { - case ConstantType(Constant(msg: String)) => msg - case _ => s"A literal string is expected as an argument to `compiletime.error`. Got ${msgArg.show}" - } - // Usually `error` is called from within a rewrite method. In this - // case we need to report the error at the point of the outermost enclosing inline - // call. This way, a defensively written rewrite methid can always - // report bad inputs at the point of call instead of revealing its internals. - val callToReport = if (enclosingInlineds.nonEmpty) enclosingInlineds.last else call - val ctxToReport = ctx.outersIterator.dropWhile(enclosingInlineds(using _).nonEmpty).next - inContext(ctxToReport) { - report.error(message, callToReport.srcPos) - } - case _ => - } - - /** The number of nodes in this tree, excluding code in nested inline - * calls and annotations of definitions. - */ - def treeSize(x: Any): Int = - var siz = 0 - x match - case x: Trees.Inlined[_] => - case x: Positioned => - var i = 0 - while i < x.productArity do - siz += treeSize(x.productElement(i)) - i += 1 - case x: List[_] => - var xs = x - while xs.nonEmpty do - siz += treeSize(xs.head) - xs = xs.tail - case _ => - siz - - trace(i"inlining $call", inlining, show = true) { - - // The normalized bindings collected in `bindingsBuf` - bindingsBuf.mapInPlace { binding => - // Set trees to symbols allow macros to see the definition tree. - // This is used by `underlyingArgument`. - val binding1 = reducer.normalizeBinding(binding)(using inlineCtx).setDefTree - binding1.foreachSubTree { - case tree: MemberDef => tree.setDefTree - case _ => - } - binding1 - } - - // Run a typing pass over the inlined tree. See InlineTyper for details. - val expansion1 = inlineTyper.typed(expansion)(using inlineCtx) - - if (ctx.settings.verbose.value) { - inlining.println(i"to inline = $rhsToInline") - inlining.println(i"original bindings = ${bindingsBuf.toList}%\n%") - inlining.println(i"original expansion = $expansion1") - } - - // Drop unused bindings - val (finalBindings, finalExpansion) = dropUnusedDefs(bindingsBuf.toList, expansion1) - - if (inlinedMethod == defn.Compiletime_error) issueError() - - addInlinedTrees(treeSize(finalExpansion)) - - // Take care that only argument bindings go into `bindings`, since positions are - // different for bindings from arguments and bindings from body. - tpd.Inlined(call, finalBindings, finalExpansion) - } - } - - /** A utility object offering methods for rewriting inlined code */ - object reducer { - - /** An extractor for terms equivalent to `new C(args)`, returning the class `C`, - * a list of bindings, and the arguments `args`. Can see inside blocks and Inlined nodes and can - * follow a reference to an inline value binding to its right hand side. - * - * @return optionally, a triple consisting of - * - the class `C` - * - the arguments `args` - * - any bindings that wrap the instance creation - * - whether the instance creation is precomputed or by-name - */ - private object NewInstance { - def unapply(tree: Tree)(using Context): Option[(Symbol, List[Tree], List[Tree], Boolean)] = { - def unapplyLet(bindings: List[Tree], expr: Tree) = - unapply(expr) map { - case (cls, reduced, prefix, precomputed) => (cls, reduced, bindings ::: prefix, precomputed) - } - tree match { - case Apply(fn, args) => - fn match { - case Select(New(tpt), nme.CONSTRUCTOR) => - Some((tpt.tpe.classSymbol, args, Nil, false)) - case TypeApply(Select(New(tpt), nme.CONSTRUCTOR), _) => - Some((tpt.tpe.classSymbol, args, Nil, false)) - case _ => - val meth = fn.symbol - if (meth.name == nme.apply && - meth.flags.is(Synthetic) && - meth.owner.linkedClass.is(Case)) - Some(meth.owner.linkedClass, args, Nil, false) - else None - } - case Typed(inner, _) => - // drop the ascribed tpt. We only need it if we can't find a NewInstance - unapply(inner) - case Ident(_) => - val binding = tree.symbol.defTree - for ((cls, reduced, prefix, precomputed) <- unapply(binding)) - yield (cls, reduced, prefix, precomputed || binding.isInstanceOf[ValDef]) - case Inlined(_, bindings, expansion) => - unapplyLet(bindings, expansion) - case Block(stats, expr) if isElideableExpr(tree) => - unapplyLet(stats, expr) - case _ => - None - } - } - } - - /** If `tree` is equivalent to `new C(args).x` where class `C` does not have - * initialization code and `x` is a parameter corresponding to one of the - * arguments `args`, the corresponding argument, otherwise `tree` itself. - * Side effects of original arguments need to be preserved. - */ - def reduceProjection(tree: Tree)(using Context): Tree = { - if (ctx.debug) inlining.println(i"try reduce projection $tree") - tree match { - case Select(NewInstance(cls, args, prefix, precomputed), field) if cls.isNoInitsRealClass => - def matches(param: Symbol, selection: Symbol): Boolean = - param == selection || { - selection.name match { - case InlineAccessorName(underlying) => - param.name == underlying && selection.info.isInstanceOf[ExprType] - case _ => - false - } - } - val idx = cls.asClass.paramAccessors.indexWhere(matches(_, tree.symbol)) - if (idx >= 0 && idx < args.length) { - def finish(arg: Tree) = - new TreeTypeMap().transform(arg) // make sure local bindings in argument have fresh symbols - .showing(i"projecting $tree -> $result", inlining) - val arg = args(idx) - if (precomputed) - if (isElideableExpr(arg)) finish(arg) - else tree // nothing we can do here, projection would duplicate side effect - else { - // newInstance is evaluated in place, need to reflect side effects of - // arguments in the order they were written originally - def collectImpure(from: Int, end: Int) = - (from until end).filterNot(i => isElideableExpr(args(i))).toList.map(args) - val leading = collectImpure(0, idx) - val trailing = collectImpure(idx + 1, args.length) - val argInPlace = - if (trailing.isEmpty) arg - else - def argsSpan = trailing.map(_.span).foldLeft(arg.span)(_.union(_)) - letBindUnless(TreeInfo.Pure, arg)(Block(trailing, _).withSpan(argsSpan)) - val blockSpan = (prefix ::: leading).map(_.span).foldLeft(argInPlace.span)(_.union(_)) - finish(seq(prefix, seq(leading, argInPlace)).withSpan(blockSpan)) - } - } - else tree - case Block(stats, expr) if stats.forall(isPureBinding) => - cpy.Block(tree)(stats, reduceProjection(expr)) - case _ => tree - } - } - - /** If this is a value binding: - * - reduce its rhs if it is a projection and adjust its type accordingly, - * - record symbol -> rhs in the InlineBindings context propery. - */ - def normalizeBinding(binding: ValOrDefDef)(using Context) = { - val binding1 = binding match { - case binding: ValDef => - val rhs1 = reduceProjection(binding.rhs) - binding.symbol.defTree = rhs1 - if (rhs1 `eq` binding.rhs) binding - else { - binding.symbol.info = rhs1.tpe - cpy.ValDef(binding)(tpt = TypeTree(rhs1.tpe), rhs = rhs1) - } - case _ => - binding - } - binding1.withSpan(call.span) - } - - /** An extractor for references to inlineable arguments. These are : - * - by-value arguments marked with `inline` - * - all by-name arguments - */ - private object InlineableArg { - lazy val paramProxies = paramProxy.values.toSet - def unapply(tree: Trees.Ident[?])(using Context): Option[Tree] = { - def search(buf: mutable.ListBuffer[ValOrDefDef]) = buf.find(_.name == tree.name) - if (paramProxies.contains(tree.typeOpt)) - search(bindingsBuf) match { - case Some(bind: ValOrDefDef) if bind.symbol.is(Inline) => - Some(integrate(bind.rhs, bind.symbol)) - case _ => None - } - else None - } - } - - def tryInlineArg(tree: Tree)(using Context): Tree = tree match { - case InlineableArg(rhs) => - inlining.println(i"inline arg $tree -> $rhs") - rhs - case _ => - EmptyTree - } - - /** Rewrite an application - * - * ((x1, ..., xn) => b)(e1, ..., en) - * - * to - * - * val/def x1 = e1; ...; val/def xn = en; b - * - * where `def` is used for call-by-name parameters. However, we shortcut any NoPrefix - * refs among the ei's directly without creating an intermediate binding. - */ - def betaReduce(tree: Tree)(using Context): Tree = tree match { - case Apply(Select(cl @ closureDef(ddef), nme.apply), args) if defn.isFunctionType(cl.tpe) => - // closureDef also returns a result for closures wrapped in Inlined nodes. - // These need to be preserved. - def recur(cl: Tree): Tree = cl match - case Inlined(call, bindings, expr) => - cpy.Inlined(cl)(call, bindings, recur(expr)) - case _ => ddef.tpe.widen match - case mt: MethodType if ddef.paramss.head.length == args.length => - val bindingsBuf = new mutable.ListBuffer[ValOrDefDef] - val argSyms = mt.paramNames.lazyZip(mt.paramInfos).lazyZip(args).map { (name, paramtp, arg) => - arg.tpe.dealias match { - case ref @ TermRef(NoPrefix, _) => ref.symbol - case _ => - paramBindingDef(name, paramtp, arg, bindingsBuf)( - using ctx.withSource(cl.source) - ).symbol - } - } - val expander = new TreeTypeMap( - oldOwners = ddef.symbol :: Nil, - newOwners = ctx.owner :: Nil, - substFrom = ddef.paramss.head.map(_.symbol), - substTo = argSyms) - Block(bindingsBuf.toList, expander.transform(ddef.rhs)).withSpan(tree.span) - case _ => tree - recur(cl) - case _ => tree - } - - /** The result type of reducing a match. It consists optionally of a list of bindings - * for the pattern-bound variables and the RHS of the selected case. - * Returns `None` if no case was selected. - */ - type MatchRedux = Option[(List[MemberDef], Tree)] - - /** Reduce an inline match - * @param mtch the match tree - * @param scrutinee the scrutinee expression, assumed to be pure, or - * EmptyTree for a summonFrom - * @param scrutType its fully defined type, or - * ImplicitScrutineeTypeRef for a summonFrom - * @param typer The current inline typer - * @return optionally, if match can be reduced to a matching case: A pair of - * bindings for all pattern-bound variables and the RHS of the case. - */ - def reduceInlineMatch(scrutinee: Tree, scrutType: Type, cases: List[CaseDef], typer: Typer)(using Context): MatchRedux = { - - val isImplicit = scrutinee.isEmpty - - /** Try to match pattern `pat` against scrutinee reference `scrut`. If successful add - * bindings for variables bound in this pattern to `caseBindingMap`. - */ - def reducePattern( - caseBindingMap: mutable.ListBuffer[(Symbol, MemberDef)], - scrut: TermRef, - pat: Tree - )(using Context): Boolean = { - - /** Create a binding of a pattern bound variable with matching part of - * scrutinee as RHS and type that corresponds to RHS. - */ - def newTermBinding(sym: TermSymbol, rhs: Tree): Unit = { - val copied = sym.copy(info = rhs.tpe.widenInlineScrutinee, coord = sym.coord, flags = sym.flags &~ Case).asTerm - caseBindingMap += ((sym, ValDef(copied, constToLiteral(rhs)).withSpan(sym.span))) - } - - def newTypeBinding(sym: TypeSymbol, alias: Type): Unit = { - val copied = sym.copy(info = TypeAlias(alias), coord = sym.coord).asType - caseBindingMap += ((sym, TypeDef(copied))) - } - - def searchImplicit(sym: TermSymbol, tpt: Tree) = { - val evTyper = new Typer - val evCtx = ctx.fresh.setTyper(evTyper) - val evidence = evTyper.inferImplicitArg(tpt.tpe, tpt.span)(using evCtx) - evidence.tpe match { - case fail: Implicits.AmbiguousImplicits => - report.error(evTyper.missingArgMsg(evidence, tpt.tpe, ""), tpt.srcPos) - true // hard error: return true to stop implicit search here - case fail: Implicits.SearchFailureType => - false - case _ => - //inliner.println(i"inferred implicit $sym: ${sym.info} with $evidence: ${evidence.tpe.widen}, ${evCtx.gadt.constraint}, ${evCtx.typerState.constraint}") - newTermBinding(sym, evidence) - true - } - } - - type TypeBindsMap = SimpleIdentityMap[TypeSymbol, java.lang.Boolean] - - def getTypeBindsMap(pat: Tree, tpt: Tree): TypeBindsMap = { - val getBinds = new TreeAccumulator[Set[TypeSymbol]] { - def apply(syms: Set[TypeSymbol], t: Tree)(using Context): Set[TypeSymbol] = { - val syms1 = t match { - case t: Bind if t.symbol.isType => - syms + t.symbol.asType - case _ => syms - } - foldOver(syms1, t) - } - } - - // Extractors contain Bind nodes in type parameter lists, the tree looks like this: - // UnApply[t @ t](pats)(implicits): T[t] - // Test case is pos/inline-caseclass.scala. - val binds: Set[TypeSymbol] = pat match { - case UnApply(TypeApply(_, tpts), _, _) => getBinds(Set.empty[TypeSymbol], tpts) - case _ => getBinds(Set.empty[TypeSymbol], tpt) - } - - val extractBindVariance = new TypeAccumulator[TypeBindsMap] { - def apply(syms: TypeBindsMap, t: Type) = { - val syms1 = t match { - // `binds` is used to check if the symbol was actually bound by the pattern we're processing - case tr: TypeRef if tr.symbol.is(Case) && binds.contains(tr.symbol.asType) => - val trSym = tr.symbol.asType - // Exact same logic as in IsFullyDefinedAccumulator: - // the binding is to be maximized iff it only occurs contravariantly in the type - val wasToBeMinimized: Boolean = { - val v = syms(trSym) - if (v ne null) v else false - } - syms.updated(trSym, wasToBeMinimized || variance >= 0 : java.lang.Boolean) - case _ => - syms - } - foldOver(syms1, t) - } - } - - extractBindVariance(SimpleIdentityMap.empty, tpt.tpe) - } - - def addTypeBindings(typeBinds: TypeBindsMap)(using Context): Unit = - typeBinds.foreachBinding { case (sym, shouldBeMinimized) => - newTypeBinding(sym, ctx.gadt.approximation(sym, fromBelow = shouldBeMinimized)) - } - - def registerAsGadtSyms(typeBinds: TypeBindsMap)(using Context): Unit = - if (typeBinds.size > 0) ctx.gadt.addToConstraint(typeBinds.keys) - - pat match { - case Typed(pat1, tpt) => - val typeBinds = getTypeBindsMap(pat1, tpt) - registerAsGadtSyms(typeBinds) - scrut <:< tpt.tpe && { - addTypeBindings(typeBinds) - reducePattern(caseBindingMap, scrut, pat1) - } - case pat @ Bind(name: TermName, Typed(_, tpt)) if isImplicit => - val typeBinds = getTypeBindsMap(tpt, tpt) - registerAsGadtSyms(typeBinds) - searchImplicit(pat.symbol.asTerm, tpt) && { - addTypeBindings(typeBinds) - true - } - case pat @ Bind(name: TermName, body) => - reducePattern(caseBindingMap, scrut, body) && { - if (name != nme.WILDCARD) newTermBinding(pat.symbol.asTerm, ref(scrut)) - true - } - case Ident(nme.WILDCARD) => - true - case pat: Literal => - scrut.widenTermRefExpr =:= pat.tpe - case pat: RefTree => - scrut =:= pat.tpe || - scrut.classSymbol.is(Module) && scrut.widen =:= pat.tpe.widen && { - scrut.prefix match { - case _: SingletonType | NoPrefix => true - case _ => false - } - } - case UnApply(unapp, _, pats) => - unapp.tpe.widen match { - case mt: MethodType if mt.paramInfos.length == 1 => - - def reduceSubPatterns(pats: List[Tree], selectors: List[Tree]): Boolean = (pats, selectors) match { - case (Nil, Nil) => true - case (pat :: pats1, selector :: selectors1) => - val elem = newSym(InlineBinderName.fresh(), Synthetic, selector.tpe.widenInlineScrutinee).asTerm - val rhs = constToLiteral(selector) - elem.defTree = rhs - caseBindingMap += ((NoSymbol, ValDef(elem, rhs).withSpan(elem.span))) - reducePattern(caseBindingMap, elem.termRef, pat) && - reduceSubPatterns(pats1, selectors1) - case _ => false - } - - val paramType = mt.paramInfos.head - val paramCls = paramType.classSymbol - if (paramCls.is(Case) && unapp.symbol.is(Synthetic) && scrut <:< paramType) { - val caseAccessors = - if (paramCls.is(Scala2x)) paramCls.caseAccessors.filter(_.is(Method)) - else paramCls.asClass.paramAccessors - val selectors = - for (accessor <- caseAccessors) - yield constToLiteral(reduceProjection(ref(scrut).select(accessor).ensureApplied)) - caseAccessors.length == pats.length && reduceSubPatterns(pats, selectors) - } - else false - case _ => - false - } - case Inlined(EmptyTree, Nil, ipat) => - reducePattern(caseBindingMap, scrut, ipat) - case _ => false - } - } - - /** The initial scrutinee binding: `val $scrutineeN = ` */ - val scrutineeSym = newSym(InlineScrutineeName.fresh(), Synthetic, scrutType).asTerm - val scrutineeBinding = normalizeBinding(ValDef(scrutineeSym, scrutinee)) - - def reduceCase(cdef: CaseDef): MatchRedux = { - val caseBindingMap = new mutable.ListBuffer[(Symbol, MemberDef)]() - - def substBindings( - bindings: List[(Symbol, MemberDef)], - bbuf: mutable.ListBuffer[MemberDef], - from: List[Symbol], to: List[Symbol]): (List[MemberDef], List[Symbol], List[Symbol]) = - bindings match { - case (sym, binding) :: rest => - bbuf += binding.subst(from, to).asInstanceOf[MemberDef] - if (sym.exists) substBindings(rest, bbuf, sym :: from, binding.symbol :: to) - else substBindings(rest, bbuf, from, to) - case Nil => (bbuf.toList, from, to) - } - - if (!isImplicit) caseBindingMap += ((NoSymbol, scrutineeBinding)) - val gadtCtx = ctx.fresh.setFreshGADTBounds.addMode(Mode.GadtConstraintInference) - if (reducePattern(caseBindingMap, scrutineeSym.termRef, cdef.pat)(using gadtCtx)) { - val (caseBindings, from, to) = substBindings(caseBindingMap.toList, mutable.ListBuffer(), Nil, Nil) - val guardOK = cdef.guard.isEmpty || { - typer.typed(cdef.guard.subst(from, to), defn.BooleanType) match { - case ConstantValue(true) => true - case _ => false - } - } - if (guardOK) Some((caseBindings.map(_.subst(from, to)), cdef.body.subst(from, to))) - else None - } - else None - } - - def recur(cases: List[CaseDef]): MatchRedux = cases match { - case Nil => None - case cdef :: cases1 => reduceCase(cdef) `orElse` recur(cases1) - } - - recur(cases) - } - } - - /** A typer for inlined bodies. Beyond standard typing, an inline typer performs - * the following functions: - * - * 1. Implement constant folding over inlined code - * 2. Selectively expand ifs with constant conditions - * 3. Inline arguments that are by-name closures - * 4. Make sure inlined code is type-correct. - * 5. Make sure that the tree's typing is idempotent (so that future -Ycheck passes succeed) - */ - class InlineTyper(initialErrorCount: Int) extends ReTyper { - import reducer._ - - override def ensureAccessible(tpe: Type, superAccess: Boolean, pos: SrcPos)(using Context): Type = { - tpe match { - case tpe: NamedType if tpe.symbol.exists && !tpe.symbol.isAccessibleFrom(tpe.prefix, superAccess) => - tpe.info match { - case TypeAlias(alias) => return ensureAccessible(alias, superAccess, pos) - case info: ConstantType if tpe.symbol.isStableMember => return info - case _ => - } - case _ => - } - super.ensureAccessible(tpe, superAccess, pos) - } - - override def typedIdent(tree: untpd.Ident, pt: Type)(using Context): Tree = - inlineIfNeeded(tryInlineArg(tree.asInstanceOf[tpd.Tree]) `orElse` super.typedIdent(tree, pt)) - - override def typedSelect(tree: untpd.Select, pt: Type)(using Context): Tree = { - assert(tree.hasType, tree) - val qual1 = typed(tree.qualifier, shallowSelectionProto(tree.name, pt, this)) - val resNoReduce = untpd.cpy.Select(tree)(qual1, tree.name).withType(tree.typeOpt) - val resMaybeReduced = constToLiteral(reducer.reduceProjection(resNoReduce)) - if (resNoReduce ne resMaybeReduced) - typed(resMaybeReduced, pt) // redo typecheck if reduction changed something - else - val res = resMaybeReduced - ensureAccessible(res.tpe, tree.qualifier.isInstanceOf[untpd.Super], tree.srcPos) - inlineIfNeeded(res) - } - - override def typedIf(tree: untpd.If, pt: Type)(using Context): Tree = - val condCtx = if tree.isInline then ctx.addMode(Mode.ForceInline) else ctx - typed(tree.cond, defn.BooleanType)(using condCtx) match { - case cond1 @ ConstantValue(b: Boolean) => - val selected0 = if (b) tree.thenp else tree.elsep - val selected = if (selected0.isEmpty) tpd.Literal(Constant(())) else typed(selected0, pt) - if (isIdempotentExpr(cond1)) selected - else Block(cond1 :: Nil, selected) - case cond1 => - if (tree.isInline) - errorTree(tree, - em"Cannot reduce `inline if` because its condition is not a constant value: $cond1") - else - cond1.computeNullableDeeply() - val if1 = untpd.cpy.If(tree)(cond = untpd.TypedSplice(cond1)) - super.typedIf(if1, pt) - } - - override def typedValDef(vdef: untpd.ValDef, sym: Symbol)(using Context): Tree = - val vdef1 = - if sym.is(Inline) then - val rhs = typed(vdef.rhs) - sym.info = rhs.tpe - untpd.cpy.ValDef(vdef)(vdef.name, untpd.TypeTree(rhs.tpe), untpd.TypedSplice(rhs)) - else vdef - super.typedValDef(vdef1, sym) - - override def typedApply(tree: untpd.Apply, pt: Type)(using Context): Tree = - val res = constToLiteral(betaReduce(super.typedApply(tree, pt))) match { - case res: Apply if res.symbol == defn.QuotedRuntime_exprSplice - && level == 0 - && !hasInliningErrors => - val expanded = expandMacro(res.args.head, tree.srcPos) - typedExpr(expanded) // Inline calls and constant fold code generated by the macro - case res => - inlineIfNeeded(res) - } - if res.symbol == defn.QuotedRuntime_exprQuote then - ctx.compilationUnit.needsQuotePickling = true - res - - override def typedTypeApply(tree: untpd.TypeApply, pt: Type)(using Context): Tree = - inlineIfNeeded(constToLiteral(betaReduce(super.typedTypeApply(tree, pt)))) - - override def typedMatch(tree: untpd.Match, pt: Type)(using Context): Tree = - val tree1 = - if tree.isInline then - // TODO this might not be useful if we do not support #11291 - val sel1 = typedExpr(tree.selector)(using ctx.addMode(Mode.ForceInline)) - untpd.cpy.Match(tree)(sel1, tree.cases) - else tree - super.typedMatch(tree1, pt) - - override def typedMatchFinish(tree: untpd.Match, sel: Tree, wideSelType: Type, cases: List[untpd.CaseDef], pt: Type)(using Context) = - if (!tree.isInline || ctx.owner.isInlineMethod) // don't reduce match of nested inline method yet - super.typedMatchFinish(tree, sel, wideSelType, cases, pt) - else { - def selTyped(sel: Tree): Type = sel match { - case Typed(sel2, _) => selTyped(sel2) - case Block(Nil, sel2) => selTyped(sel2) - case Inlined(_, Nil, sel2) => selTyped(sel2) - case _ => sel.tpe - } - val selType = if (sel.isEmpty) wideSelType else selTyped(sel) - reduceInlineMatch(sel, selType, cases.asInstanceOf[List[CaseDef]], this) match { - case Some((caseBindings, rhs0)) => - // drop type ascriptions/casts hiding pattern-bound types (which are now aliases after reducing the match) - // note that any actually necessary casts will be reinserted by the typing pass below - val rhs1 = rhs0 match { - case Block(stats, t) if t.span.isSynthetic => - t match { - case Typed(expr, _) => - Block(stats, expr) - case TypeApply(sel@Select(expr, _), _) if sel.symbol.isTypeCast => - Block(stats, expr) - case _ => - rhs0 - } - case _ => rhs0 - } - val (usedBindings, rhs2) = dropUnusedDefs(caseBindings, rhs1) - val rhs = seq(usedBindings, rhs2) - inlining.println(i"""--- reduce: - |$tree - |--- to: - |$rhs""") - typedExpr(rhs, pt) - case None => - def guardStr(guard: untpd.Tree) = if (guard.isEmpty) "" else i" if $guard" - def patStr(cdef: untpd.CaseDef) = i"case ${cdef.pat}${guardStr(cdef.guard)}" - val msg = - if (tree.selector.isEmpty) - em"""cannot reduce summonFrom with - | patterns : ${tree.cases.map(patStr).mkString("\n ")}""" - else - em"""cannot reduce inline match with - | scrutinee: $sel : ${selType} - | patterns : ${tree.cases.map(patStr).mkString("\n ")}""" - errorTree(tree, msg) - } - } - - override def newLikeThis: Typer = new InlineTyper(initialErrorCount) - - /** True if this inline typer has already issued errors */ - override def hasInliningErrors(using Context) = ctx.reporter.errorCount > initialErrorCount - - private def inlineIfNeeded(tree: Tree)(using Context): Tree = - if Inliner.needsInlining(tree) then Inliner.inlineCall(tree) - else tree - - override def typedUnadapted(tree: untpd.Tree, pt: Type, locked: TypeVars)(using Context): Tree = - super.typedUnadapted(tree, pt, locked) match - case member: MemberDef => member.setDefTree - case tree => tree - } - - /** Drop any side-effect-free bindings that are unused in expansion or other reachable bindings. - * Inline def bindings that are used only once. - */ - def dropUnusedDefs(bindings: List[MemberDef], tree: Tree)(using Context): (List[MemberDef], Tree) = { - // inlining.println(i"drop unused $bindings%, % in $tree") - val (termBindings, typeBindings) = bindings.partition(_.symbol.isTerm) - if (typeBindings.nonEmpty) { - val typeBindingsSet = typeBindings.foldLeft[SimpleIdentitySet[Symbol]](SimpleIdentitySet.empty)(_ + _.symbol) - val inlineTypeBindings = new TreeTypeMap( - typeMap = new TypeMap() { - override def apply(tp: Type): Type = tp match { - case tr: TypeRef if tr.prefix.eq(NoPrefix) && typeBindingsSet.contains(tr.symbol) => - val TypeAlias(res) = tr.info - res - case tp => mapOver(tp) - } - }, - treeMap = { - case ident: Ident if ident.isType && typeBindingsSet.contains(ident.symbol) => - val TypeAlias(r) = ident.symbol.info - TypeTree(r).withSpan(ident.span) - case tree => tree - } - ) - val Block(termBindings1, tree1) = inlineTypeBindings(Block(termBindings, tree)) - dropUnusedDefs(termBindings1.asInstanceOf[List[ValOrDefDef]], tree1) - } - else { - val refCount = MutableSymbolMap[Int]() - val bindingOfSym = MutableSymbolMap[MemberDef]() - - def isInlineable(binding: MemberDef) = binding match { - case ddef @ DefDef(_, Nil, _, _) => isElideableExpr(ddef.rhs) - case vdef @ ValDef(_, _, _) => isElideableExpr(vdef.rhs) - case _ => false - } - for (binding <- bindings if isInlineable(binding)) { - refCount(binding.symbol) = 0 - bindingOfSym(binding.symbol) = binding - } - - val countRefs = new TreeTraverser { - override def traverse(t: Tree)(using Context) = { - def updateRefCount(sym: Symbol, inc: Int) = - for (x <- refCount.get(sym)) refCount(sym) = x + inc - def updateTermRefCounts(t: Tree) = - t.typeOpt.foreachPart { - case ref: TermRef => updateRefCount(ref.symbol, 2) // can't be inlined, so make sure refCount is at least 2 - case _ => - } - - t match { - case t: RefTree => - updateRefCount(t.symbol, 1) - updateTermRefCounts(t) - case _: New | _: TypeTree => - updateTermRefCounts(t) - case _ => - } - traverseChildren(t) - } - } - countRefs.traverse(tree) - for (binding <- bindings) countRefs.traverse(binding) - - def retain(boundSym: Symbol) = { - refCount.get(boundSym) match { - case Some(x) => x > 1 || x == 1 && !boundSym.is(Method) - case none => true - } - } && !boundSym.is(Inline) - - val inlineBindings = new TreeMap { - override def transform(t: Tree)(using Context) = t match { - case t: RefTree => - val sym = t.symbol - val t1 = refCount.get(sym) match { - case Some(1) => - bindingOfSym(sym) match { - case binding: ValOrDefDef => integrate(binding.rhs, sym) - } - case none => t - } - super.transform(t1) - case t: Apply => - val t1 = super.transform(t) - if (t1 `eq` t) t else reducer.betaReduce(t1) - case Block(Nil, expr) => - super.transform(expr) - case _ => - super.transform(t) - } - } - - val retained = bindings.filterConserve(binding => retain(binding.symbol)) - if (retained `eq` bindings) - (bindings, tree) - else { - val expanded = inlineBindings.transform(tree) - dropUnusedDefs(retained, expanded) - } - } - } - - private def expandMacro(body: Tree, splicePos: SrcPos)(using Context) = { - assert(level == 0) - val inlinedFrom = enclosingInlineds.last - val dependencies = macroDependencies(body) - val suspendable = ctx.compilationUnit.isSuspendable - if dependencies.nonEmpty && !ctx.reporter.errorsReported then - for sym <- dependencies do - if ctx.compilationUnit.source.file == sym.associatedFile then - report.error(em"Cannot call macro $sym defined in the same source file", call.srcPos) - if (suspendable && ctx.settings.XprintSuspension.value) - report.echo(i"suspension triggered by macro call to ${sym.showLocated} in ${sym.associatedFile}", call.srcPos) - if suspendable then - ctx.compilationUnit.suspend() // this throws a SuspendException - - val evaluatedSplice = inContext(quoted.MacroExpansion.context(inlinedFrom)) { - Splicer.splice(body, splicePos, inlinedFrom.srcPos, MacroClassLoader.fromContext) - } - val inlinedNormailizer = new TreeMap { - override def transform(tree: tpd.Tree)(using Context): tpd.Tree = tree match { - case Inlined(EmptyTree, Nil, expr) if enclosingInlineds.isEmpty => transform(expr) - case _ => super.transform(tree) - } - } - val normalizedSplice = inlinedNormailizer.transform(evaluatedSplice) - if (normalizedSplice.isEmpty) normalizedSplice - else normalizedSplice.withSpan(splicePos.span) - } - - /** Return the set of symbols that are referred at level -1 by the tree and defined in the current run. - * This corresponds to the symbols that will need to be interpreted. - */ - private def macroDependencies(tree: Tree)(using Context) = - new TreeAccumulator[List[Symbol]] { - private var level = -1 - override def apply(syms: List[Symbol], tree: tpd.Tree)(using Context): List[Symbol] = - if (level != -1) foldOver(syms, tree) - else tree match { - case tree: RefTree if level == -1 && tree.symbol.isDefinedInCurrentRun && !tree.symbol.isLocal => - foldOver(tree.symbol :: syms, tree) - case Quoted(body) => - level += 1 - try apply(syms, body) - finally level -= 1 - case Spliced(body) => - level -= 1 - try apply(syms, body) - finally level += 1 - case SplicedType(body) => - level -= 1 - try apply(syms, body) - finally level += 1 - case _ => - foldOver(syms, tree) - } - }.apply(Nil, tree) - - object ConstantValue { - def unapply(tree: Tree)(using Context): Option[Any] = - tree match - case Typed(expr, _) => unapply(expr) - case Inlined(_, Nil, expr) => unapply(expr) - case Block(Nil, expr) => unapply(expr) - case _ => - tree.tpe.widenTermRefExpr.normalized match - case ConstantType(Constant(x)) => Some(x) - case _ => None - } - - -} From 9c1e6260feb0cf75d8803540872bec4e42072f26 Mon Sep 17 00:00:00 2001 From: Anatolii Kmetiuk Date: Fri, 15 Jul 2022 11:54:46 +0200 Subject: [PATCH 15/44] Comment out warning about inline positions recording Such warnings are produced when compiling the bootstrapped compiler. --- compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala index 4bd47ab1a0e3..d33bb5f811ea 100644 --- a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala +++ b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala @@ -157,7 +157,7 @@ object InlinedSourceMaps: val offset = sourcePos.startLine - request.origPos.startLine Some(request.firstFakeLine + offset + 1) case None => - report.warning(s"${sourcePos.show} was inlined in ${cunit.source} but its inlining position was not recorded.") + // report.warning(s"${sourcePos.show} was inlined in ${cunit.source} but its inlining position was not recorded.") None From 119449512a04defe556f4b8e647249a904c64356 Mon Sep 17 00:00:00 2001 From: Anatolii Kmetiuk Date: Fri, 12 Aug 2022 14:09:11 +0200 Subject: [PATCH 16/44] Don't generate mappings for the quotes compiled at runtime by the staging compiler --- .../tools/backend/jvm/InlinedSourceMaps.scala | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala index d33bb5f811ea..211c79b5eefd 100644 --- a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala +++ b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala @@ -107,15 +107,18 @@ object InlinedSourceMaps: else traverseChildren(tree) end RequestCollector - var lastLine = cunit.tpdTree.sourcePos.endLine - def allocate(origPos: SourcePosition): Int = - val line = lastLine + 1 - lastLine += origPos.lines.length - line - - RequestCollector(cunit.source).traverse(cunit.tpdTree) - val allocated = requests.sortBy(_._1.start).map(r => Request(r._1, r._2, allocate(r._2))) - InlinedSourceMap(cunit, allocated.toList, internalNames) + // 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 + def allocate(origPos: SourcePosition): Int = + val line = lastLine + 1 + lastLine += origPos.lines.length + line + + RequestCollector(cunit.source).traverse(cunit.tpdTree) + val allocated = requests.sortBy(_._1.start).map(r => Request(r._1, r._2, allocate(r._2))) + InlinedSourceMap(cunit, allocated.toList, internalNames) end sourceMapFor class InlinedSourceMap private[InlinedSourceMaps] ( From 5f57a5ee42eeac37f3c25a2a5e7bd8ec96b1e4ed Mon Sep 17 00:00:00 2001 From: Anatolii Kmetiuk Date: Fri, 12 Aug 2022 15:02:46 +0200 Subject: [PATCH 17/44] Inlined code gets virtual line numbers in stack traces We follow the Kotlin solution to allow inlined code to point to virtual numbers. We rely on the tooling for proper navigation. See also: https://youtrack.jetbrains.com/issue/KT-8628 https://bugs.openjdk.org/browse/JDK-4972961 --- tests/run-macros/i4947e.check | 6 ++-- tests/run-macros/i4947f.check | 6 ++-- tests/run-macros/splice-position.check | 4 +-- tests/run/i4947b.check | 44 +++++++++++++------------- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/tests/run-macros/i4947e.check b/tests/run-macros/i4947e.check index 1e67df692f1e..26435e45140a 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:16) true -assertImpl: Test$.main(Test_2.scala:8) +assertImpl: Test$.main(Test_2.scala:16) false -assertImpl: Test$.main(Test_2.scala:9) +assertImpl: Test$.main(Test_2.scala:18) 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..26435e45140a 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:16) true -assertImpl: Test$.main(Test_2.scala:8) +assertImpl: Test$.main(Test_2.scala:16) false -assertImpl: Test$.main(Test_2.scala:9) +assertImpl: Test$.main(Test_2.scala:18) 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..9e1489ab38f4 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:7) +Test$.main(Test.scala:8) diff --git a/tests/run/i4947b.check b/tests/run/i4947b.check index 3950d4e0b7a1..e603f0d9ca79 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:25) +track: Test$.main(Test_2.scala:26) +track: Test$.main(Test_2.scala:25) +track: Test$.main(Test_2.scala:26) 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:35) +track: Test$.main(Test_2.scala:36) +fact: Test$.main(Test_2.scala:43) 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:35) +track: Test$.main(Test_2.scala:36) +fact: Test$.main(Test_2.scala:43) 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:49) +track (i = 1): Test$.main(Test_2.scala:49) +track: Test$.main(Test_2.scala:35) +track: Test$.main(Test_2.scala:36) +fact: Test$.main(Test_2.scala:43) 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:49) +track (i = 0): Test$.main(Test_2.scala:49) +track: Test$.main(Test_2.scala:35) +track: Test$.main(Test_2.scala:36) +fact: Test$.main(Test_2.scala:43) main1 (i = -1): Test$.main(Test_2.scala:17) main2 (i = -1): Test$.main(Test_2.scala:18) From 3bb9b4233633c423f0ec1191ff3c09949973beed Mon Sep 17 00:00:00 2001 From: Anatolii Kmetiuk Date: Fri, 12 Aug 2022 17:10:23 +0200 Subject: [PATCH 18/44] Fix virtual line numbers in assert-stack When emitting the bytecode, line numbers are emitted for trees. These line numbers are used in call stack frames. Trees inlined from other files get virtual numbers. It appears that, when inlining `call(x)`, the line number for `call` is emitted before that for `x`. So, if `x` is coming from the current file, its line number will be real and will override that of `call`. --- tests/run/assert-stack.check | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) From e713b07341084629f716844b72821e62141fad72 Mon Sep 17 00:00:00 2001 From: Anatolii Kmetiuk Date: Fri, 26 Aug 2022 11:10:15 +0200 Subject: [PATCH 19/44] Don't do ExpandPrivate checks on synthetic methods. --- compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala b/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala index 545fe70488a2..6aae841b7d92 100644 --- a/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala +++ b/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala @@ -66,7 +66,7 @@ class ExpandPrivate extends MiniPhase with IdentityDenotTransformer { thisPhase private def ensurePrivateAccessible(d: SymDenotation)(using Context) = if (isVCPrivateParamAccessor(d)) d.ensureNotPrivate.installAfter(thisPhase) - else if (d.is(PrivateTerm) && !d.owner.is(Package) && d.owner != ctx.owner.lexicallyEnclosingClass && !d.is(InlineProxy)) { + else if (d.is(PrivateTerm) && !d.owner.is(Package) && d.owner != ctx.owner.lexicallyEnclosingClass && !d.is(InlineProxy) && !d.is(Synthetic)) { // Paths `p1` and `p2` are similar if they have a common suffix that follows // possibly different directory paths. That is, their common suffix extends // in both cases either to the start of the path or to a file separator character. From fd458f06041c60cee1b3468b36cc7227db3fdd98 Mon Sep 17 00:00:00 2001 From: Tetchki Date: Mon, 27 Feb 2023 18:16:27 +0100 Subject: [PATCH 20/44] Comments and some useful prints added --- .../tools/backend/jvm/BCodeSkelBuilder.scala | 2 ++ .../tools/backend/jvm/InlinedSourceMaps.scala | 23 +++++++++++++++++-- .../dotty/tools/dotc/inlines/Inlines.scala | 6 +++++ .../tools/dotc/transform/ExpandPrivate.scala | 2 +- 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala index 0453c7d9f39c..1ad0fe7c5ec4 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala @@ -281,6 +281,8 @@ trait BCodeSkelBuilder extends BCodeHelpers { if (emitSource) { sourceMap = sourceMapFor(cunit)(s => classBTypeFromSymbol(s).internalName) + // debugExtension is the ScalaDebug Stratum + // println(s"sourceMap for ${claszSymbol.fullName} = ${sourceMap.debugExtension}") cnode.visitSource(cunit.source.file.name, sourceMap.debugExtension.orNull) } diff --git a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala index 211c79b5eefd..31c837a10cf8 100644 --- a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala +++ b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala @@ -85,6 +85,8 @@ object InlinedSourceMaps: val requests = mutable.ListBuffer.empty[(SourcePosition, SourcePosition)] var internalNames = Map.empty[SourceFile, String] + // Collect all inlined calls in the compilation unit + class RequestCollector(enclosingFile: SourceFile) extends TreeTraverser: override def traverse(tree: Tree)(using Context): Unit = if tree.source != enclosingFile && tree.source != cunit.source then @@ -95,6 +97,7 @@ object InlinedSourceMaps: cls match case Some(symbol) if !internalNames.isDefinedAt(tree.source) => internalNames += (tree.source -> internalNameProvider(symbol)) + //println(s"Internal name for ${tree.source} is ${internalNames(tree.source)}") // 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 @@ -107,17 +110,30 @@ object InlinedSourceMaps: else traverseChildren(tree) end RequestCollector + // Create the mapping from the real source files to the real and virtual lines + // 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 + var lastLine = cunit.tpdTree.sourcePos.endLine // Last line of the source file + //println(s"lastLine = $lastLine") def allocate(origPos: SourcePosition): Int = - val line = lastLine + 1 + val line = lastLine + 1 // Add an empty line. Why ? lastLine += origPos.lines.length + //println(s"LastLine = $lastLine, line = $line") line RequestCollector(cunit.source).traverse(cunit.tpdTree) val allocated = requests.sortBy(_._1.start).map(r => Request(r._1, r._2, allocate(r._2))) + // targetPos.startLine is the line in the source file where the inlined call is located + // origPos.startLine is the line in the source file where the inlined is defined + // firstFakeLine is the first virtual line of the inline + /*allocated.foreach { case Request(targetPos, origPos, firstFakeLine) => + println(s"targetPos = ${targetPos.startLine}, origPos = ${origPos.startLine}, firstFakeLine = $firstFakeLine") + }*/ + // internalNames is a map from source file to the internal name of the class that contains the inlined code + // Map(local/Macro_1.scala -> Macro_1$package$) + //println(s"internalNames = $internalNames") InlinedSourceMap(cunit, allocated.toList, internalNames) end sourceMapFor @@ -126,10 +142,13 @@ object InlinedSourceMaps: requests: List[Request], internalNames: Map[SourceFile, String])(using Context): + // Generate the Scala Stratum and the ScalaDebug Stratum from the map created in sourceMapFor + def debugExtension: Option[String] = Option.when(requests.nonEmpty) { val scalaStratum = val files = cunit.source :: requests.map(_.origPos.source).distinct.filter(_ != cunit.source) val mappings = requests.map { case Request(_, origPos, firstFakeLine) => + //println(s" origPos = ${origPos.startLine}, files.indexOf(origPos.source) + 1 = ${files.indexOf(origPos.source) + 1}, origPos.lines.length = ${origPos.lines.length}, firstFakeLine = $firstFakeLine") Mapping(origPos.startLine, files.indexOf(origPos.source) + 1, origPos.lines.length, firstFakeLine, 1) } Stratum("Scala", diff --git a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala index 38b4047a9643..85c760d07e3d 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala @@ -244,9 +244,15 @@ object Inlines: /** Replace `Inlined` node by a block that contains its bindings and expansion */ def dropInlined(inlined: Inlined)(using Context): Tree = + //println(i"inlined : ${inlined.show}") + //println(i"inlined is empty : ${inlined.call.isEmpty}") val topLevelClass = Option.when(!inlined.call.isEmpty)(inlined.call.symbol.topLevelClass) + //println(i"topLevelClass : ${topLevelClass}") val inliningPosition = InliningPosition(inlined.sourcePos, topLevelClass) + //println(i"inliningPosition : ${inliningPosition}") val withPos = inlined.expansion.withAttachment(InliningPosition, inliningPosition) + //println(i"withPos : ${withPos}") + //println(i"inlined.bindings : ${inlined.bindings}") if inlined.bindings.isEmpty then withPos else cpy.Block(inlined)(inlined.bindings, withPos) /** Leave only a call trace consisting of diff --git a/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala b/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala index 6aae841b7d92..545fe70488a2 100644 --- a/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala +++ b/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala @@ -66,7 +66,7 @@ class ExpandPrivate extends MiniPhase with IdentityDenotTransformer { thisPhase private def ensurePrivateAccessible(d: SymDenotation)(using Context) = if (isVCPrivateParamAccessor(d)) d.ensureNotPrivate.installAfter(thisPhase) - else if (d.is(PrivateTerm) && !d.owner.is(Package) && d.owner != ctx.owner.lexicallyEnclosingClass && !d.is(InlineProxy) && !d.is(Synthetic)) { + else if (d.is(PrivateTerm) && !d.owner.is(Package) && d.owner != ctx.owner.lexicallyEnclosingClass && !d.is(InlineProxy)) { // Paths `p1` and `p2` are similar if they have a common suffix that follows // possibly different directory paths. That is, their common suffix extends // in both cases either to the start of the path or to a file separator character. From 2d46e51263acb97078e032c3baf7d08b8bcb4af8 Mon Sep 17 00:00:00 2001 From: Tetchki Date: Mon, 27 Feb 2023 23:47:25 +0100 Subject: [PATCH 21/44] Revert "Comments and some useful prints added" This reverts commit c07bbbd829c289576efa30824ce2e0a62b04a368. --- .../tools/backend/jvm/BCodeSkelBuilder.scala | 2 -- .../tools/backend/jvm/InlinedSourceMaps.scala | 23 ++----------------- .../dotty/tools/dotc/inlines/Inlines.scala | 6 ----- .../tools/dotc/transform/ExpandPrivate.scala | 2 +- 4 files changed, 3 insertions(+), 30 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala index 1ad0fe7c5ec4..0453c7d9f39c 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala @@ -281,8 +281,6 @@ trait BCodeSkelBuilder extends BCodeHelpers { if (emitSource) { sourceMap = sourceMapFor(cunit)(s => classBTypeFromSymbol(s).internalName) - // debugExtension is the ScalaDebug Stratum - // println(s"sourceMap for ${claszSymbol.fullName} = ${sourceMap.debugExtension}") cnode.visitSource(cunit.source.file.name, sourceMap.debugExtension.orNull) } diff --git a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala index 31c837a10cf8..211c79b5eefd 100644 --- a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala +++ b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala @@ -85,8 +85,6 @@ object InlinedSourceMaps: val requests = mutable.ListBuffer.empty[(SourcePosition, SourcePosition)] var internalNames = Map.empty[SourceFile, String] - // Collect all inlined calls in the compilation unit - class RequestCollector(enclosingFile: SourceFile) extends TreeTraverser: override def traverse(tree: Tree)(using Context): Unit = if tree.source != enclosingFile && tree.source != cunit.source then @@ -97,7 +95,6 @@ object InlinedSourceMaps: cls match case Some(symbol) if !internalNames.isDefinedAt(tree.source) => internalNames += (tree.source -> internalNameProvider(symbol)) - //println(s"Internal name for ${tree.source} is ${internalNames(tree.source)}") // 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 @@ -110,30 +107,17 @@ object InlinedSourceMaps: else traverseChildren(tree) end RequestCollector - // Create the mapping from the real source files to the real and virtual lines - // 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 // Last line of the source file - //println(s"lastLine = $lastLine") + var lastLine = cunit.tpdTree.sourcePos.endLine def allocate(origPos: SourcePosition): Int = - val line = lastLine + 1 // Add an empty line. Why ? + val line = lastLine + 1 lastLine += origPos.lines.length - //println(s"LastLine = $lastLine, line = $line") line RequestCollector(cunit.source).traverse(cunit.tpdTree) val allocated = requests.sortBy(_._1.start).map(r => Request(r._1, r._2, allocate(r._2))) - // targetPos.startLine is the line in the source file where the inlined call is located - // origPos.startLine is the line in the source file where the inlined is defined - // firstFakeLine is the first virtual line of the inline - /*allocated.foreach { case Request(targetPos, origPos, firstFakeLine) => - println(s"targetPos = ${targetPos.startLine}, origPos = ${origPos.startLine}, firstFakeLine = $firstFakeLine") - }*/ - // internalNames is a map from source file to the internal name of the class that contains the inlined code - // Map(local/Macro_1.scala -> Macro_1$package$) - //println(s"internalNames = $internalNames") InlinedSourceMap(cunit, allocated.toList, internalNames) end sourceMapFor @@ -142,13 +126,10 @@ object InlinedSourceMaps: requests: List[Request], internalNames: Map[SourceFile, String])(using Context): - // Generate the Scala Stratum and the ScalaDebug Stratum from the map created in sourceMapFor - def debugExtension: Option[String] = Option.when(requests.nonEmpty) { val scalaStratum = val files = cunit.source :: requests.map(_.origPos.source).distinct.filter(_ != cunit.source) val mappings = requests.map { case Request(_, origPos, firstFakeLine) => - //println(s" origPos = ${origPos.startLine}, files.indexOf(origPos.source) + 1 = ${files.indexOf(origPos.source) + 1}, origPos.lines.length = ${origPos.lines.length}, firstFakeLine = $firstFakeLine") Mapping(origPos.startLine, files.indexOf(origPos.source) + 1, origPos.lines.length, firstFakeLine, 1) } Stratum("Scala", diff --git a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala index 85c760d07e3d..38b4047a9643 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala @@ -244,15 +244,9 @@ object Inlines: /** Replace `Inlined` node by a block that contains its bindings and expansion */ def dropInlined(inlined: Inlined)(using Context): Tree = - //println(i"inlined : ${inlined.show}") - //println(i"inlined is empty : ${inlined.call.isEmpty}") val topLevelClass = Option.when(!inlined.call.isEmpty)(inlined.call.symbol.topLevelClass) - //println(i"topLevelClass : ${topLevelClass}") val inliningPosition = InliningPosition(inlined.sourcePos, topLevelClass) - //println(i"inliningPosition : ${inliningPosition}") val withPos = inlined.expansion.withAttachment(InliningPosition, inliningPosition) - //println(i"withPos : ${withPos}") - //println(i"inlined.bindings : ${inlined.bindings}") if inlined.bindings.isEmpty then withPos else cpy.Block(inlined)(inlined.bindings, withPos) /** Leave only a call trace consisting of diff --git a/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala b/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala index 545fe70488a2..6aae841b7d92 100644 --- a/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala +++ b/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala @@ -66,7 +66,7 @@ class ExpandPrivate extends MiniPhase with IdentityDenotTransformer { thisPhase private def ensurePrivateAccessible(d: SymDenotation)(using Context) = if (isVCPrivateParamAccessor(d)) d.ensureNotPrivate.installAfter(thisPhase) - else if (d.is(PrivateTerm) && !d.owner.is(Package) && d.owner != ctx.owner.lexicallyEnclosingClass && !d.is(InlineProxy)) { + else if (d.is(PrivateTerm) && !d.owner.is(Package) && d.owner != ctx.owner.lexicallyEnclosingClass && !d.is(InlineProxy) && !d.is(Synthetic)) { // Paths `p1` and `p2` are similar if they have a common suffix that follows // possibly different directory paths. That is, their common suffix extends // in both cases either to the start of the path or to a file separator character. From 9b95e3e3d59ce63bcffb8a936c793b272815e738 Mon Sep 17 00:00:00 2001 From: Tetchki Date: Mon, 27 Feb 2023 23:48:51 +0100 Subject: [PATCH 22/44] Removed synthetic ExpandPrivate check --- compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala b/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala index 6aae841b7d92..545fe70488a2 100644 --- a/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala +++ b/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala @@ -66,7 +66,7 @@ class ExpandPrivate extends MiniPhase with IdentityDenotTransformer { thisPhase private def ensurePrivateAccessible(d: SymDenotation)(using Context) = if (isVCPrivateParamAccessor(d)) d.ensureNotPrivate.installAfter(thisPhase) - else if (d.is(PrivateTerm) && !d.owner.is(Package) && d.owner != ctx.owner.lexicallyEnclosingClass && !d.is(InlineProxy) && !d.is(Synthetic)) { + else if (d.is(PrivateTerm) && !d.owner.is(Package) && d.owner != ctx.owner.lexicallyEnclosingClass && !d.is(InlineProxy)) { // Paths `p1` and `p2` are similar if they have a common suffix that follows // possibly different directory paths. That is, their common suffix extends // in both cases either to the start of the path or to a file separator character. From ba320285b9a2ee5ba4450d43034ce99bd050d628 Mon Sep 17 00:00:00 2001 From: Tetchki Date: Tue, 28 Feb 2023 00:02:40 +0100 Subject: [PATCH 23/44] CI Test --- compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala index 211c79b5eefd..3940566d0cad 100644 --- a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala +++ b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala @@ -143,7 +143,6 @@ object InlinedSourceMaps: } Stratum("ScalaDebug", File(1, cunit.source.name, None) :: Nil, mappings) - val b = new StringBuilder b ++= "SMAP\n" b ++= cunit.source.name From 91e9f92de7a3d6afae10fe7dd11b2df3da091e51 Mon Sep 17 00:00:00 2001 From: Tetchki Date: Mon, 6 Mar 2023 16:26:34 +0100 Subject: [PATCH 24/44] Reverted InlineProxy and removed source location assert --- .../src/dotty/tools/dotc/transform/ExpandPrivate.scala | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala b/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala index 545fe70488a2..79d1aea9bbc5 100644 --- a/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala +++ b/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala @@ -66,7 +66,7 @@ class ExpandPrivate extends MiniPhase with IdentityDenotTransformer { thisPhase private def ensurePrivateAccessible(d: SymDenotation)(using Context) = if (isVCPrivateParamAccessor(d)) d.ensureNotPrivate.installAfter(thisPhase) - else if (d.is(PrivateTerm) && !d.owner.is(Package) && d.owner != ctx.owner.lexicallyEnclosingClass && !d.is(InlineProxy)) { + else if (d.is(PrivateTerm) && !d.owner.is(Package) && d.owner != ctx.owner.lexicallyEnclosingClass) { // Paths `p1` and `p2` are similar if they have a common suffix that follows // possibly different directory paths. That is, their common suffix extends // in both cases either to the start of the path or to a file separator character. @@ -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) } From 45b7099a2a866b6949d77cff5f4cfa571bcacb3c Mon Sep 17 00:00:00 2001 From: Tetchki Date: Mon, 8 May 2023 14:28:53 +0200 Subject: [PATCH 25/44] Fixed inlines in scoverage not being repositioned --- .../dotc/transform/InstrumentCoverage.scala | 61 ++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala b/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala index 29572a4ae30d..67d1b4a08d4a 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,64 @@ 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. + * + * 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 + + + val dropped = dropInlinedInstrumentaton(tree) // drop and reposition transform(dropped) // transform the content of the Inlined // For everything else just recurse and transform From 32b1208fa4dbc1bb3abc15f7e06185c3d36e655f Mon Sep 17 00:00:00 2001 From: Tetchki Date: Mon, 8 May 2023 14:31:03 +0200 Subject: [PATCH 26/44] Fixed 2 identical consecutive inlines generates wrong line number --- .../dotty/tools/dotc/inlines/Inlines.scala | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala index 38b4047a9643..66bfd8d7c213 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, Property} +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 @@ -30,7 +37,7 @@ object Inlines: private[dotc] class MissingInlineInfo extends Exception object InliningPosition extends Property.StickyKey[InliningPosition] - case class InliningPosition(sourcePos: SourcePosition, topLevelSymbol: Option[Symbol]) + case class InliningPosition(targetPos: List[(SourcePosition, Option[Symbol])]) /** `sym` is an inline method with a known body to inline. */ @@ -245,9 +252,20 @@ object Inlines: /** Replace `Inlined` node by a block that contains its bindings and expansion */ def dropInlined(inlined: Inlined)(using Context): Tree = val topLevelClass = Option.when(!inlined.call.isEmpty)(inlined.call.symbol.topLevelClass) - val inliningPosition = InliningPosition(inlined.sourcePos, topLevelClass) - val withPos = inlined.expansion.withAttachment(InliningPosition, inliningPosition) - if inlined.bindings.isEmpty then withPos else cpy.Block(inlined)(inlined.bindings, withPos) + val position = (inlined.sourcePos, topLevelClass) + val withPos = + if inlined.expansion.hasAttachment(InliningPosition) then + val att = InliningPosition(position :: inlined.expansion.getAttachment(InliningPosition).get.targetPos) + inlined.expansion.withAttachment(InliningPosition, att) + else + inlined.expansion.withAttachment(InliningPosition, InliningPosition(List(position))) + + if inlined.bindings.isEmpty then + withPos + else + cpy.Block(inlined)(inlined.bindings, withPos) + + /** Leave only a call trace consisting of * - a reference to the top-level class from which the call was inlined, From b0e7c72214c8a0d5cc7fd40f68ca1d53df4fd317 Mon Sep 17 00:00:00 2001 From: Tetchki Date: Mon, 8 May 2023 14:32:26 +0200 Subject: [PATCH 27/44] Attempted fix for nested and parameter inlines generate wrong line number --- .../tools/backend/jvm/BCodeSkelBuilder.scala | 14 +++- .../tools/backend/jvm/InlinedSourceMaps.scala | 67 +++++++++++++------ 2 files changed, 56 insertions(+), 25 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala index 0453c7d9f39c..868465859f83 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 } @@ -25,6 +24,7 @@ import dotty.tools.dotc.report import dotty.tools.dotc.transform.SymUtils._ import InlinedSourceMaps._ +import dotty.tools.dotc.inlines.Inlines.InliningPosition /* * @@ -585,8 +585,16 @@ trait BCodeSkelBuilder extends BCodeHelpers { if (!emitLines || !tree.span.exists) return; if tree.source != cunit.source then - sourceMap.lineFor(tree.sourcePos, lastRealLineNr) match - case Some(nr) => emitNr(nr) + tree.getAttachment(InliningPosition) match + case Some(pos) => + val sourcePosition = pos.targetPos.find(p => p._1.source == cunit.source) + if sourcePosition.nonEmpty then + val offset = sourcePosition.get._1.span.point + lastRealLineNr = ctx.source.offsetToLine(offset) + 1 + case None => () + sourceMap.lineFor(tree.sourcePos, lastRealLineNr, tree.getAttachment(InliningPosition)) match + case Some(nr) => + emitNr(nr) case None => () else val nr = ctx.source.offsetToLine(tree.span.point) + 1 diff --git a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala index 3940566d0cad..82f85e906052 100644 --- a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala +++ b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala @@ -81,43 +81,53 @@ object InlinedSourceMaps: b ++= "*E\n" end Stratum + //targetPos is the position of the inlined call inlined(5) + //origPos is the position of the inlined code inline def inlined(arg) { ... } def sourceMapFor(cunit: CompilationUnit)(internalNameProvider: Symbol => String)(using Context): InlinedSourceMap = val requests = mutable.ListBuffer.empty[(SourcePosition, SourcePosition)] var internalNames = Map.empty[SourceFile, String] class RequestCollector(enclosingFile: SourceFile) extends TreeTraverser: override def traverse(tree: Tree)(using Context): Unit = - if tree.source != enclosingFile && tree.source != cunit.source then + if tree.hasAttachment(InliningPosition) then tree.getAttachment(InliningPosition) match - case Some(InliningPosition(targetPos, cls)) => - requests += (targetPos -> tree.sourcePos) - - cls 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 _ => () - RequestCollector(tree.source).traverseChildren(tree) + case Some(InliningPosition(l)) => + l.foreach { + (targetPos, cls) => + if tree.source != enclosingFile && tree.source != cunit.source then + requests += (targetPos -> tree.sourcePos) + + cls 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 None => // Not exactly sure in which cases it is happening. Should we report warning? - RequestCollector(tree.source).traverseChildren(tree) - else traverseChildren(tree) + traverseChildren(tree) + else + 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.sortBy(_._1.start).map(r => Request(r._1, r._2, allocate(r._2))) + + val allocated = requests.map(r => Request(r._1, r._2, allocate(r._2))) + InlinedSourceMap(cunit, allocated.toList, internalNames) end sourceMapFor @@ -153,13 +163,26 @@ object InlinedSourceMaps: b.toString } - def lineFor(sourcePos: SourcePosition, lastRealNr: Int): Option[Int] = - requests.find(r => r.origPos.contains(sourcePos) && r.targetPos.endLine + 1 >= lastRealNr) match + var lastNestedInlineLine = -1 + + def lineFor(sourcePos: SourcePosition, lastRealNr: Int, attachement: Option[InliningPosition]): Option[Int] = + + if attachement.isDefined then + attachement.get.targetPos.find(p => p._1.source == sourcePos.source) match + case Some((pos, _)) => + lastNestedInlineLine = pos.startLine + case None => () + + requests.find(r => + r.origPos.contains(sourcePos) && + (if r.targetPos.source == cunit.source then r.targetPos.endLine + 1 >= lastRealNr + else r.targetPos.startLine >= lastNestedInlineLine + ) + ) match case Some(request) => val offset = sourcePos.startLine - request.origPos.startLine - Some(request.firstFakeLine + offset + 1) + val virtualLine = request.firstFakeLine + offset + 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 - - + None \ No newline at end of file From 86c4379ea3501d1d099e9dde2c7d838729d1aafa Mon Sep 17 00:00:00 2001 From: Tetchki Date: Tue, 16 May 2023 18:19:14 +0200 Subject: [PATCH 28/44] Keep Inlined nodes until BCode builder --- .../tools/backend/jvm/BCodeBodyBuilder.scala | 18 ++++++++++++++++++ .../dotty/tools/dotc/transform/Erasure.scala | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala index e7b5a0dad1bf..66c79a85147f 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala @@ -479,6 +479,13 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { case t: TypeApply => // dotty specific generatedType = genTypeApply(t) + case inlined @ Inlined(call, bindings, expansion) => + if(bindings.isEmpty) + genLoadTo(expansion, expectedType, dest) + else + genInlinedTo(inlined, expectedType, dest) + generatedDest = dest + case _ => abort(s"Unexpected tree in genLoad: $tree/${tree.getClass} at: ${tree.span}") } @@ -1090,6 +1097,17 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { varsInScope = savedScope } + def genInlinedTo(tree: Inlined, expectedType: BType, dest: LoadDestination): Unit = tree match { + case Inlined(call, bindings, expansion) => + + val savedScope = varsInScope + varsInScope = Nil + bindings foreach genStat + genLoadTo(expansion, expectedType, dest) + emitLocalVarScopes() + varsInScope = savedScope + } + /** Add entries to the `LocalVariableTable` JVM attribute for all the vars in * `varsInScope`, ending at the current program point. */ diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 3ed024429bb6..31af682205fa 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -897,7 +897,7 @@ object Erasure { override def typedInlined(tree: untpd.Inlined, pt: Type)(using Context): Tree = super.typedInlined(tree, pt) match { - case tree: Inlined => Inlines.dropInlined(tree) + case tree: Inlined => tree //Inlines.dropInlined(tree) } override def typedValDef(vdef: untpd.ValDef, sym: Symbol)(using Context): Tree = From ee4dc3c819a72f47c0c6fdc6b3180a34125095bb Mon Sep 17 00:00:00 2001 From: Tetchki Date: Tue, 16 May 2023 18:24:50 +0200 Subject: [PATCH 29/44] Use Inlined nodes' information instead of using attachements --- .../tools/backend/jvm/BCodeSkelBuilder.scala | 17 +-- .../tools/backend/jvm/InlinedSourceMaps.scala | 121 +++++++----------- 2 files changed, 48 insertions(+), 90 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala index 868465859f83..1b618217a360 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala @@ -582,24 +582,17 @@ trait BCodeSkelBuilder extends BCodeHelpers { end emitNr def lineNumber(tree: Tree): Unit = { - if (!emitLines || !tree.span.exists) return; - if tree.source != cunit.source then - tree.getAttachment(InliningPosition) match - case Some(pos) => - val sourcePosition = pos.targetPos.find(p => p._1.source == cunit.source) - if sourcePosition.nonEmpty then - val offset = sourcePosition.get._1.span.point - lastRealLineNr = ctx.source.offsetToLine(offset) + 1 - case None => () - sourceMap.lineFor(tree.sourcePos, lastRealLineNr, tree.getAttachment(InliningPosition)) match + // Use JSR-45 mapping for inlined trees defined outside of the current compilation unit + if tree.source != cunit.source || (tree.isInstanceOf[Inlined] && tree.asInstanceOf[Inlined].expansion.source != cunit.source) then + sourceMap.lineFor(tree) match case Some(nr) => - emitNr(nr) + return emitNr(nr) case None => () else val nr = ctx.source.offsetToLine(tree.span.point) + 1 lastRealLineNr = nr - emitNr(nr) + return emitNr(nr) } // on entering a method diff --git a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala index 82f85e906052..78e7bb274bae 100644 --- a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala +++ b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala @@ -33,7 +33,8 @@ import collection.mutable * stack frames for inlined calls. **/ object InlinedSourceMaps: - private case class Request(targetPos: SourcePosition, origPos: SourcePosition, firstFakeLine: Int) + //private case class Request(targetPos: SourcePosition, origPos: SourcePosition, firstFakeLine: Int) + private case class Request(inline: Inlined, firstFakeLine: Int) private class File(id: Int, name: String, path: Option[String]): def write(b: mutable.StringBuilder): Unit = @@ -81,37 +82,29 @@ object InlinedSourceMaps: b ++= "*E\n" end Stratum - //targetPos is the position of the inlined call inlined(5) - //origPos is the position of the inlined code inline def inlined(arg) { ... } def sourceMapFor(cunit: CompilationUnit)(internalNameProvider: Symbol => String)(using Context): InlinedSourceMap = - val requests = mutable.ListBuffer.empty[(SourcePosition, SourcePosition)] + 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 = - if tree.hasAttachment(InliningPosition) then - tree.getAttachment(InliningPosition) match - case Some(InliningPosition(l)) => - l.foreach { - (targetPos, cls) => - if tree.source != enclosingFile && tree.source != cunit.source then - requests += (targetPos -> tree.sourcePos) - - cls 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 None => - // Not exactly sure in which cases it is happening. Should we report warning? - traverseChildren(tree) - else - traverseChildren(tree) + tree match + case Inlined(call, bindings, expansion) => + if expansion.source != enclosingFile && expansion.source != cunit.source then + requests += tree.asInstanceOf[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 @@ -126,7 +119,7 @@ object InlinedSourceMaps: RequestCollector(cunit.source).traverse(cunit.tpdTree) - val allocated = requests.map(r => Request(r._1, r._2, allocate(r._2))) + val allocated = requests.map(r => Request(r, allocate(r.expansion.sourcePos))) InlinedSourceMap(cunit, allocated.toList, internalNames) end sourceMapFor @@ -136,53 +129,25 @@ object InlinedSourceMaps: 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(_.origPos.source).distinct.filter(_ != cunit.source) - val mappings = requests.map { case Request(_, origPos, firstFakeLine) => - Mapping(origPos.startLine, files.indexOf(origPos.source) + 1, origPos.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(targetPos, origPos, firstFakeLine) => - Mapping(targetPos.startLine, 1, 1, firstFakeLine, origPos.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 - } - - var lastNestedInlineLine = -1 - - def lineFor(sourcePos: SourcePosition, lastRealNr: Int, attachement: Option[InliningPosition]): Option[Int] = - - if attachement.isDefined then - attachement.get.targetPos.find(p => p._1.source == sourcePos.source) match - case Some((pos, _)) => - lastNestedInlineLine = pos.startLine - case None => () - - requests.find(r => - r.origPos.contains(sourcePos) && - (if r.targetPos.source == cunit.source then r.targetPos.endLine + 1 >= lastRealNr - else r.targetPos.startLine >= lastNestedInlineLine - ) - ) match - case Some(request) => - val offset = sourcePos.startLine - request.origPos.startLine - val virtualLine = request.firstFakeLine + offset - 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 + def debugExtension: Option[String] = Some("TODO") + + private val inlines = mutable.ListBuffer.empty[Inlined] + + def lineFor(tree: Tree): Option[Int] = + + tree match + case Inlined(call, binding, expansion) => + inlines += tree.asInstanceOf[Inlined] + None + case _ => + val sourcePos = tree.sourcePos + val inline = inlines.findLast(_.expansion.contains(tree)) + 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 From a4c6fef679bc986117fa43778800f7de5a7e9da3 Mon Sep 17 00:00:00 2001 From: Tetchki Date: Wed, 17 May 2023 11:00:53 +0200 Subject: [PATCH 30/44] Added JS Inlined code gen and simplified JVM Inlined BCode gen --- .../tools/backend/jvm/BCodeBodyBuilder.scala | 17 +---------------- .../src/dotty/tools/backend/sjs/JSCodeGen.scala | 3 +++ 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala index 66c79a85147f..43d1fccd338c 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala @@ -480,11 +480,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { generatedType = genTypeApply(t) case inlined @ Inlined(call, bindings, expansion) => - if(bindings.isEmpty) - genLoadTo(expansion, expectedType, dest) - else - genInlinedTo(inlined, expectedType, dest) - generatedDest = dest + genLoadTo(Block(bindings, expansion), expectedType, dest) case _ => abort(s"Unexpected tree in genLoad: $tree/${tree.getClass} at: ${tree.span}") } @@ -1097,17 +1093,6 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { varsInScope = savedScope } - def genInlinedTo(tree: Inlined, expectedType: BType, dest: LoadDestination): Unit = tree match { - case Inlined(call, bindings, expansion) => - - val savedScope = varsInScope - varsInScope = Nil - bindings foreach genStat - genLoadTo(expansion, expectedType, dest) - emitLocalVarScopes() - varsInScope = savedScope - } - /** Add entries to the `LocalVariableTable` JVM attribute for all the vars in * `varsInScope`, ending at the current program point. */ diff --git a/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala b/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala index eee791852fde..4b4d5582f1f3 100644 --- a/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala +++ b/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala @@ -1930,6 +1930,9 @@ class JSCodeGen()(using genCtx: Context) { case EmptyTree => js.Skip() + case Inlined(call, bindings, expansion) => + genStatOrExpr(Block(bindings, expansion), isStat) + case _ => throw new FatalError("Unexpected tree in genExpr: " + tree + "/" + tree.getClass + " at: " + (tree.span: Position)) From 2c42d1ffbf3b783a2d13f6769ad622929b8fbc24 Mon Sep 17 00:00:00 2001 From: Tetchki Date: Wed, 17 May 2023 11:01:32 +0200 Subject: [PATCH 31/44] Removed unecessary check --- compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala index 1b618217a360..18e04d9b4121 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala @@ -584,7 +584,7 @@ trait BCodeSkelBuilder extends BCodeHelpers { 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 || (tree.isInstanceOf[Inlined] && tree.asInstanceOf[Inlined].expansion.source != cunit.source) then + if tree.source != cunit.source then sourceMap.lineFor(tree) match case Some(nr) => return emitNr(nr) From 95f897ef759d476cd16b6d7740cadefec0a83e5f Mon Sep 17 00:00:00 2001 From: Tetchki Date: Wed, 17 May 2023 11:05:31 +0200 Subject: [PATCH 32/44] Removed unused code --- .../tools/backend/jvm/InlinedSourceMaps.scala | 32 +++++++------------ 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala index 78e7bb274bae..6aa5d93d2203 100644 --- a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala +++ b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala @@ -89,9 +89,9 @@ object InlinedSourceMaps: class RequestCollector(enclosingFile: SourceFile) extends TreeTraverser: override def traverse(tree: Tree)(using Context): Unit = tree match - case Inlined(call, bindings, expansion) => + case inlined @ Inlined(call, bindings, expansion) => if expansion.source != enclosingFile && expansion.source != cunit.source then - requests += tree.asInstanceOf[Inlined] + requests += inlined val topLevelClass = Option.when(!call.isEmpty)(call.symbol.topLevelClass) topLevelClass match @@ -131,23 +131,15 @@ object InlinedSourceMaps: def debugExtension: Option[String] = Some("TODO") - private val inlines = mutable.ListBuffer.empty[Inlined] - def lineFor(tree: Tree): Option[Int] = - tree match - case Inlined(call, binding, expansion) => - inlines += tree.asInstanceOf[Inlined] - None - case _ => - val sourcePos = tree.sourcePos - val inline = inlines.findLast(_.expansion.contains(tree)) - 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 + 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 From dfd779bef22a366b41544f2f22edd8403a3aa732 Mon Sep 17 00:00:00 2001 From: Tetchki Date: Wed, 24 May 2023 10:51:53 +0200 Subject: [PATCH 33/44] Added missing generated destination and used dropInlined to correctly transform an inlined node --- compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala index 43d1fccd338c..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,8 +480,9 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { case t: TypeApply => // dotty specific generatedType = genTypeApply(t) - case inlined @ Inlined(call, bindings, expansion) => - genLoadTo(Block(bindings, expansion), expectedType, dest) + case inlined @ Inlined(_, _, _) => + genLoadTo(Inlines.dropInlined(inlined) , expectedType, dest) + generatedDest = dest case _ => abort(s"Unexpected tree in genLoad: $tree/${tree.getClass} at: ${tree.span}") } From 86e03ff2ce1e5ca5dabdebfad592b34dc162d7e9 Mon Sep 17 00:00:00 2001 From: Tetchki Date: Wed, 24 May 2023 10:53:24 +0200 Subject: [PATCH 34/44] Cleanup --- .../src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala index 18e04d9b4121..606c9fece7b2 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala @@ -377,8 +377,6 @@ trait BCodeSkelBuilder extends BCodeHelpers { var stackHeight = 0 // line numbers var lastEmittedLineNr = -1 - // by real line number we mean line number that is not pointing to virtual lines added by inlined calls - var lastRealLineNr = -1 object bc extends JCodeMethodN { override def jmethod = PlainSkelBuilder.this.mnode @@ -587,13 +585,12 @@ trait BCodeSkelBuilder extends BCodeHelpers { if tree.source != cunit.source then sourceMap.lineFor(tree) match case Some(nr) => - return emitNr(nr) + emitNr(nr) case None => () else val nr = ctx.source.offsetToLine(tree.span.point) + 1 - lastRealLineNr = nr - return emitNr(nr) - } + emitNr(nr) + } // on entering a method def resetMethodBookkeeping(dd: DefDef) = { From 156bf7b5ecc048b217f5a88e9e1170144dc8039f Mon Sep 17 00:00:00 2001 From: Tetchki Date: Wed, 24 May 2023 10:54:05 +0200 Subject: [PATCH 35/44] Use dropInlined to correctly drop an inlined node --- .../src/dotty/tools/backend/sjs/JSCodeGen.scala | 5 +++-- compiler/src/dotty/tools/dotc/inlines/Inlines.scala | 13 ++----------- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala b/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala index 4b4d5582f1f3..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,8 +1931,8 @@ class JSCodeGen()(using genCtx: Context) { case EmptyTree => js.Skip() - case Inlined(call, bindings, expansion) => - genStatOrExpr(Block(bindings, expansion), isStat) + case inlined @ Inlined(_, _, _) => + genStatOrExpr(Inlines.dropInlined(inlined), isStat) case _ => throw new FatalError("Unexpected tree in genExpr: " + diff --git a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala index 66bfd8d7c213..c8ba0ae142b3 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala @@ -251,19 +251,10 @@ object Inlines: /** Replace `Inlined` node by a block that contains its bindings and expansion */ def dropInlined(inlined: Inlined)(using Context): Tree = - val topLevelClass = Option.when(!inlined.call.isEmpty)(inlined.call.symbol.topLevelClass) - val position = (inlined.sourcePos, topLevelClass) - val withPos = - if inlined.expansion.hasAttachment(InliningPosition) then - val att = InliningPosition(position :: inlined.expansion.getAttachment(InliningPosition).get.targetPos) - inlined.expansion.withAttachment(InliningPosition, att) - else - inlined.expansion.withAttachment(InliningPosition, InliningPosition(List(position))) - if inlined.bindings.isEmpty then - withPos + inlined.expansion else - cpy.Block(inlined)(inlined.bindings, withPos) + cpy.Block(inlined)(inlined.bindings, inlined.expansion) From 27ac3067920e32fa2862104ee5058ac1105c160a Mon Sep 17 00:00:00 2001 From: Tetchki Date: Wed, 24 May 2023 10:55:27 +0200 Subject: [PATCH 36/44] Added case to manage inlined nodes and correctly drop them --- tests/pos-with-compiler-cc/dotc/transform/Mixin.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/pos-with-compiler-cc/dotc/transform/Mixin.scala b/tests/pos-with-compiler-cc/dotc/transform/Mixin.scala index 9a220d9c4f8c..708d90860c07 100644 --- a/tests/pos-with-compiler-cc/dotc/transform/Mixin.scala +++ b/tests/pos-with-compiler-cc/dotc/transform/Mixin.scala @@ -221,6 +221,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) From 37b71f390e48c340df490e12bc6e44c02781f833 Mon Sep 17 00:00:00 2001 From: Tetchki Date: Wed, 24 May 2023 11:28:41 +0200 Subject: [PATCH 37/44] Edited wrong file --- compiler/src/dotty/tools/dotc/transform/Mixin.scala | 2 ++ tests/pos-with-compiler-cc/dotc/transform/Mixin.scala | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) 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/tests/pos-with-compiler-cc/dotc/transform/Mixin.scala b/tests/pos-with-compiler-cc/dotc/transform/Mixin.scala index 708d90860c07..9a220d9c4f8c 100644 --- a/tests/pos-with-compiler-cc/dotc/transform/Mixin.scala +++ b/tests/pos-with-compiler-cc/dotc/transform/Mixin.scala @@ -221,7 +221,6 @@ 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) From 07e4f0507177fca1840a4ea1e4eec6da6a5faeed Mon Sep 17 00:00:00 2001 From: Tetchki Date: Wed, 24 May 2023 13:08:28 +0200 Subject: [PATCH 38/44] Cleanup and adapted description --- compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala | 1 - .../src/dotty/tools/dotc/transform/InstrumentCoverage.scala | 2 -- 2 files changed, 3 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala index 6aa5d93d2203..03e2378e79eb 100644 --- a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala +++ b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala @@ -33,7 +33,6 @@ import collection.mutable * stack frames for inlined calls. **/ object InlinedSourceMaps: - //private case class Request(targetPos: SourcePosition, origPos: SourcePosition, firstFakeLine: Int) private case class Request(inline: Inlined, firstFakeLine: Int) private class File(id: Int, name: String, path: Option[String]): diff --git a/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala b/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala index 67d1b4a08d4a..e1cfdf6a4e10 100644 --- a/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala +++ b/compiler/src/dotty/tools/dotc/transform/InstrumentCoverage.scala @@ -328,8 +328,6 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer: * 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) { From 353bc21061e5df38f508a26f90bba057091b1fd4 Mon Sep 17 00:00:00 2001 From: Julien Richard-Foy Date: Wed, 24 May 2023 17:20:18 +0200 Subject: [PATCH 39/44] Fix ArrayApply tests --- .../tools/dotc/transform/ArrayApply.scala | 43 +++++++++++++------ 1 file changed, 31 insertions(+), 12 deletions(-) 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" From 0b34347bc0cbafcec443e577cc8ef9fb230b93d6 Mon Sep 17 00:00:00 2001 From: Tetchki Date: Mon, 5 Jun 2023 17:27:27 +0200 Subject: [PATCH 40/44] Updated check files --- tests/run-macros/i4947e.check | 6 ++-- tests/run-macros/i4947f.check | 6 ++-- tests/run-macros/splice-position.check | 2 +- tests/run/i4947b.check | 40 +++++++++++++------------- 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/tests/run-macros/i4947e.check b/tests/run-macros/i4947e.check index 26435e45140a..2c68aad8cb16 100644 --- a/tests/run-macros/i4947e.check +++ b/tests/run-macros/i4947e.check @@ -1,8 +1,8 @@ -assertImpl: Test$.main(Test_2.scala:16) +assertImpl: Test$.main(Test_2.scala:18) true -assertImpl: Test$.main(Test_2.scala:16) +assertImpl: Test$.main(Test_2.scala:23) false -assertImpl: Test$.main(Test_2.scala:18) +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 26435e45140a..df10159e803a 100644 --- a/tests/run-macros/i4947f.check +++ b/tests/run-macros/i4947f.check @@ -1,8 +1,8 @@ -assertImpl: Test$.main(Test_2.scala:16) +assertImpl: Test$.main(Test_2.scala:17) true -assertImpl: Test$.main(Test_2.scala:16) +assertImpl: Test$.main(Test_2.scala:21) false -assertImpl: Test$.main(Test_2.scala:18) +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 9e1489ab38f4..27d1ddb12840 100644 --- a/tests/run-macros/splice-position.check +++ b/tests/run-macros/splice-position.check @@ -1,2 +1,2 @@ -Test$.main(Test.scala:7) Test$.main(Test.scala:8) +Test$.main(Test.scala:10) diff --git a/tests/run/i4947b.check b/tests/run/i4947b.check index e603f0d9ca79..a721d04c920a 100644 --- a/tests/run/i4947b.check +++ b/tests/run/i4947b.check @@ -2,35 +2,35 @@ 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:25) -track: Test$.main(Test_2.scala:26) -track: Test$.main(Test_2.scala:25) -track: Test$.main(Test_2.scala:26) +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:35) -track: Test$.main(Test_2.scala:36) -fact: Test$.main(Test_2.scala:43) +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:35) -track: Test$.main(Test_2.scala:36) -fact: Test$.main(Test_2.scala:43) +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:49) -track (i = 1): Test$.main(Test_2.scala:49) -track: Test$.main(Test_2.scala:35) -track: Test$.main(Test_2.scala:36) -fact: Test$.main(Test_2.scala:43) +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:49) -track (i = 0): Test$.main(Test_2.scala:49) -track: Test$.main(Test_2.scala:35) -track: Test$.main(Test_2.scala:36) -fact: Test$.main(Test_2.scala:43) +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) From f94cfe9d566e499fc580a3e6512d689fc48d6c20 Mon Sep 17 00:00:00 2001 From: Julien Richard-Foy Date: Mon, 5 Jun 2023 16:22:06 +0200 Subject: [PATCH 41/44] Set missing span of classOf tree --- compiler/src/dotty/tools/dotc/transform/Erasure.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 31af682205fa..492969872d5e 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -637,7 +637,7 @@ object Erasure { if (tree.typeOpt.isRef(defn.UnitClass)) tree.withType(tree.typeOpt) else if (tree.const.tag == Constants.ClazzTag) - checkNotErasedClass(clsOf(tree.const.typeValue)) + checkNotErasedClass(clsOf(tree.const.typeValue).withSpan(tree.span)) else super.typedLiteral(tree) From 0cca3fad949bb27d4aa4ae486379f482ac22e24a Mon Sep 17 00:00:00 2001 From: Julien Richard-Foy Date: Mon, 5 Jun 2023 17:24:36 +0200 Subject: [PATCH 42/44] =?UTF-8?q?Fix=20position=20issues=20at=20the=20?= =?UTF-8?q?=E2=80=9Ccopy=E2=80=9D=20level?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- compiler/src/dotty/tools/dotc/ast/Trees.scala | 17 ++++++++++++----- compiler/src/dotty/tools/dotc/ast/tpd.scala | 2 +- .../src/dotty/tools/dotc/inlines/Inlines.scala | 2 +- .../dotty/tools/dotc/quoted/PickledQuotes.scala | 2 +- .../dotty/tools/dotc/transform/BetaReduce.scala | 2 +- .../dotty/tools/dotc/transform/Erasure.scala | 7 +------ .../dotty/tools/dotc/transform/PostTyper.scala | 4 ++-- .../scala/quoted/runtime/impl/QuotesImpl.scala | 2 +- 8 files changed, 20 insertions(+), 18 deletions(-) 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 c8ba0ae142b3..5e3964189a30 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inlines.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inlines.scala @@ -127,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(_))) 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/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 492969872d5e..49a60a24def4 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -637,7 +637,7 @@ object Erasure { if (tree.typeOpt.isRef(defn.UnitClass)) tree.withType(tree.typeOpt) else if (tree.const.tag == Constants.ClazzTag) - checkNotErasedClass(clsOf(tree.const.typeValue).withSpan(tree.span)) + checkNotErasedClass(clsOf(tree.const.typeValue)) else super.typedLiteral(tree) @@ -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 => tree //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/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 From f742d28c83148dff09eb4b69e864f31a7fbca9c4 Mon Sep 17 00:00:00 2001 From: Tetchki Date: Mon, 5 Jun 2023 18:18:09 +0200 Subject: [PATCH 43/44] Add missing span to inline expansion --- compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From f08fea498e0e892890da513ddad2291c90155967 Mon Sep 17 00:00:00 2001 From: Tetchki Date: Thu, 29 Jun 2023 10:15:04 +0200 Subject: [PATCH 44/44] Added SMAP string builder --- .../tools/backend/jvm/InlinedSourceMaps.scala | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala index 03e2378e79eb..f60b2b3c1086 100644 --- a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala +++ b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala @@ -110,7 +110,7 @@ object InlinedSourceMaps: 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) + // returns the first fake line (starting from 0) def allocate(origPos: SourcePosition): Int = val line = lastLine + 1 lastLine += origPos.lines.length @@ -128,7 +128,35 @@ object InlinedSourceMaps: requests: List[Request], internalNames: Map[SourceFile, String])(using Context): - def debugExtension: Option[String] = Some("TODO") + 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] =