Skip to content

Only track inlined positions #9966

New issue

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

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

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion compiler/src/dotty/tools/dotc/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ class Compiler {
/** Phases dealing with the frontend up to trees ready for TASTY pickling */
protected def frontendPhases: List[List[Phase]] =
List(new FrontEnd) :: // Compiler frontend: scanner, parser, namer, typer
List(new YCheckPositions) :: // YCheck positions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did we have to remove the phase? Does that mean positions are not checked anymore?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

YCheckPositions used to check that the sources we have on the trees align with the source that is in the call of the Inlined trees. Initially, this was used when we added sources to the trees to make sure those where correct and catch Inlined trees that had an empty/non-nonempty call when they should not. This was important to make sure the positions of the trees where properly unpickled and that the transformation phases did not lose information about the sources on the Inlined.calls before erasure.
This invariant does not hold after PostTyper anymore as all calls are empty and all the inlined source logic relies on the sources of the trees instead of the calls. Therefore we can't check what we were originally checking and we don't need to as we use exclusively the sources of the trees.

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
Expand Down
10 changes: 4 additions & 6 deletions compiler/src/dotty/tools/dotc/ast/Trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1242,11 +1242,9 @@ object Trees {
protected def skipTransform(tree: Tree)(using Context): Boolean = false

/** For untyped trees, this is just the identity.
* For typed trees, a context derived form `ctx` that records `call` as the
* innermost enclosing call for which the inlined version is currently
* processed.
* For typed trees, this records the position of an enclosing inlined position
*/
protected def inlineContext(call: Tree)(using Context): Context = ctx
protected def inlineContext(pos: SrcPos)(using Context): Context = ctx

abstract class TreeMap(val cpy: TreeCopier = inst.cpy) { self =>
def transform(tree: Tree)(using Context): Tree = {
Expand Down Expand Up @@ -1304,7 +1302,7 @@ object Trees {
case SeqLiteral(elems, elemtpt) =>
cpy.SeqLiteral(tree)(transform(elems), transform(elemtpt))
case Inlined(call, bindings, expansion) =>
cpy.Inlined(tree)(call, transformSub(bindings), transform(expansion)(using inlineContext(call)))
cpy.Inlined(tree)(call, transformSub(bindings), transform(expansion)(using inlineContext(tree)))
case TypeTree() =>
tree
case SingletonTypeTree(ref) =>
Expand Down Expand Up @@ -1442,7 +1440,7 @@ object Trees {
case SeqLiteral(elems, elemtpt) =>
this(this(x, elems), elemtpt)
case Inlined(call, bindings, expansion) =>
this(this(x, bindings), expansion)(using inlineContext(call))
this(this(x, bindings), expansion)(using inlineContext(tree))
case TypeTree() =>
x
case SingletonTypeTree(ref) =>
Expand Down
22 changes: 6 additions & 16 deletions compiler/src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import typer.ProtoTypes
import transform.SymUtils._
import transform.TypeUtils._
import core._
import util.Spans._, Types._, Contexts._, Constants._, Names._, Flags._, NameOps._
import util.Spans._, util.SrcPos, Types._, Contexts._, Constants._, Names._, Flags._, NameOps._
import Symbols._, StdNames._, Annotations._, Trees._, Symbols._
import Decorators._, DenotTransformers._
import collection.{immutable, mutable}
Expand Down Expand Up @@ -1232,37 +1232,27 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
}

/** A key to be used in a context property that tracks enclosing inlined calls */
private val InlinedCalls = Property.Key[List[Tree]]()
private val InlinedCalls = Property.Key[List[SrcPos]]()

/** A key to be used in a context property that tracks the number of inlined trees */
private val InlinedTrees = Property.Key[Counter]()
final class Counter {
var count: Int = 0
}

/** Record an enclosing inlined call.
* EmptyTree calls (for parameters) cancel the next-enclosing call in the list instead of being added to it.
* We assume parameters are never nested inside parameters.
*/
override def inlineContext(call: Tree)(using Context): Context = {
/** Record an enclosing inlined positions. */
override def inlineContext(pos: SrcPos)(using Context): Context = {
// We assume enclosingInlineds is already normalized, and only process the new call with the head.
val oldIC = enclosingInlineds

val newIC =
if call.isEmpty then
oldIC match
case t1 :: ts2 => ts2
case _ => oldIC
else
call :: oldIC
val newIC = pos :: oldIC

val ctx1 = ctx.fresh.setProperty(InlinedCalls, newIC)
if oldIC.isEmpty then ctx1.setProperty(InlinedTrees, new Counter) else ctx1
}

/** All enclosing calls that are currently inlined, from innermost to outermost.
*/
def enclosingInlineds(using Context): List[Tree] =
def enclosingInlineds(using Context): List[SrcPos] =
ctx.property(InlinedCalls).getOrElse(Nil)

/** Record inlined trees */
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/quoted/MacroExpansion.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ object MacroExpansion {
def position(using Context): Option[SourcePosition] =
ctx.property(MacroExpansionPosition)

def context(inlinedFrom: tpd.Tree)(using Context): Context =
ctx.fresh.setProperty(MacroExpansionPosition, SourcePosition(inlinedFrom.source, inlinedFrom.span)).setTypeAssigner(new Typer).withSource(inlinedFrom.source)
def context(inlinedFrom: SourcePosition)(using Context): Context =
ctx.fresh.setProperty(MacroExpansionPosition, inlinedFrom).setTypeAssigner(new Typer).withSource(inlinedFrom.source)
}

11 changes: 5 additions & 6 deletions compiler/src/dotty/tools/dotc/quoted/PickledQuotes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ object PickledQuotes {
def pickleQuote(tree: Tree)(using Context): PickledQuote =
if (ctx.reporter.hasErrors) Nil
else {
assert(!tree.isInstanceOf[Hole]) // Should not be pickled as it represents `'{$x}` which should be optimized to `x`
// FIXME handle new optimization opportunities. Previousliy we got some `Inlined(EmptyTree, Nil, Hole(...))` that did not get optimized
// assert(!tree.isInstanceOf[Hole]) // Should not be pickled as it represents `'{$x}` which should be optimized to `x`
val pickled = pickle(tree)
TastyString.pickle(pickled)
}
Expand All @@ -56,11 +57,9 @@ object PickledQuotes {
val tastyBytes = TastyString.unpickle(tasty)
val unpickled = withMode(Mode.ReadPositions)(
unpickle(tastyBytes, splices, isType = false))
val Inlined(call, Nil, expnasion) = unpickled
val inlineCtx = inlineContext(call)
val expansion1 = spliceTypes(expnasion, splices)(using inlineCtx)
val expansion2 = spliceTerms(expansion1, splices)(using inlineCtx)
cpy.Inlined(unpickled)(call, Nil, expansion2)
val expansion1 = spliceTypes(unpickled, splices)
val expansion2 = spliceTerms(expansion1, splices)
cpy.Inlined(unpickled)(EmptyTree, Nil, expansion2)
}

/** Unpickle the tree contained in the TastyType */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ object QuoteContextImpl {
new QuoteContextImpl(ctx)

def showTree(tree: tpd.Tree)(using Context): String = {
val qctx = QuoteContextImpl()(using MacroExpansion.context(tree))
val qctx = QuoteContextImpl()(using MacroExpansion.context(tree.sourcePos))
val syntaxHighlight =
if (ctx.settings.color.value == "always") SyntaxHighlight.ANSI
else SyntaxHighlight.plain
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/report.scala
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,8 @@ object report:
if (ctx.settings.Ydebug.value) warning(msg, pos)

private def addInlineds(pos: SrcPos)(using Context): SourcePosition =
def recur(pos: SourcePosition, inlineds: List[Trees.Tree[?]]): SourcePosition = inlineds match
case inlined :: inlineds1 => pos.withOuter(recur(inlined.sourcePos, inlineds1))
def recur(pos: SourcePosition, inlineds: List[SrcPos]): SourcePosition = inlineds match
case inlined :: inlineds1 => pos.sourcePos.withOuter(recur(inlined.sourcePos, inlineds1))
case Nil => pos
recur(pos.sourcePos, tpd.enclosingInlineds)

Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/transform/MegaPhase.scala
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ class MegaPhase(val miniPhases: Array[MiniPhase]) extends Phase {
case tree: Inlined =>
inContext(prepInlined(tree, start)(using outerCtx)) {
val bindings = transformSpecificTrees(tree.bindings, start)
val expansion = transformTree(tree.expansion, start)(using inlineContext(tree.call))
val expansion = transformTree(tree.expansion, start)(using inlineContext(tree))
goInlined(cpy.Inlined(tree)(tree.call, bindings, expansion), start)
}
case tree: Return =>
Expand Down
4 changes: 1 addition & 3 deletions compiler/src/dotty/tools/dotc/transform/PostTyper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -289,9 +289,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
super.transform(tree1)
}
case Inlined(call, bindings, expansion) if !call.isEmpty =>
val pos = call.sourcePos
val callTrace = Inliner.inlineCallTrace(call.symbol, pos)(using ctx.withSource(pos.source))
cpy.Inlined(tree)(callTrace, transformSub(bindings), transform(expansion)(using inlineContext(call)))
cpy.Inlined(tree)(EmptyTree, transformSub(bindings), transform(expansion)(using inlineContext(tree)))
case templ: Template =>
withNoCheckNews(templ.parents.flatMap(newPart)) {
forwardParamAccessors(templ)
Expand Down
10 changes: 2 additions & 8 deletions compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -132,14 +132,8 @@ class ReifyQuotes extends MacroTransform {
}
else {
val (body1, splices) = nested(isQuote = true).splitQuote(body)(using quoteContext)
if (level == 0) {
val body2 =
if (body1.isType) body1
else Inlined(Inliner.inlineCallTrace(ctx.owner, quote.sourcePos), Nil, body1)
pickledQuote(body2, splices, body.tpe, isType).withSpan(quote.span)
}
else
body
if level == 0 then pickledQuote(body1, splices, body.tpe, isType).withSpan(quote.span)
else body
}
}

Expand Down
62 changes: 0 additions & 62 deletions compiler/src/dotty/tools/dotc/transform/YCheckPositions.scala

This file was deleted.

21 changes: 4 additions & 17 deletions compiler/src/dotty/tools/dotc/typer/Inliner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ object Inliner {
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
(tree :: enclosingInlineds).last
)
tree2
}
Expand Down Expand Up @@ -258,19 +258,6 @@ object Inliner {
(new Reposition).transform(tree)
}

/** 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:
Expand Down Expand Up @@ -724,7 +711,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) {
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)
report.error(message, callToReport)
}
case _ =>
}
Expand Down Expand Up @@ -1431,8 +1418,8 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(using Context) {
if suspendable then
ctx.compilationUnit.suspend() // this throws a SuspendException

val evaluatedSplice = inContext(quoted.MacroExpansion.context(inlinedFrom)) {
Splicer.splice(body, inlinedFrom.srcPos, MacroClassLoader.fromContext)
val evaluatedSplice = inContext(quoted.MacroExpansion.context(inlinedFrom.sourcePos)) {
Splicer.splice(body, inlinedFrom, MacroClassLoader.fromContext)
}
val inlinedNormailizer = new TreeMap {
override def transform(tree: tpd.Tree)(using Context): tpd.Tree = tree match {
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1627,7 +1627,7 @@ class Typer extends Namer

def typedInlined(tree: untpd.Inlined, pt: Type)(using Context): Tree = {
val (bindings1, exprCtx) = typedBlockStats(tree.bindings)
val expansion1 = typed(tree.expansion, pt)(using inlineContext(tree.call)(using exprCtx))
val expansion1 = typed(tree.expansion, pt)(using inlineContext(tree)(using exprCtx))
assignType(cpy.Inlined(tree)(tree.call, bindings1.asInstanceOf[List[MemberDef]], expansion1),
bindings1, expansion1)
}
Expand Down
30 changes: 15 additions & 15 deletions tests/run/i4947b.check
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ main1: Test$.main(Test_2.scala:4)
main2: Test$.main(Test_2.scala:5)
track: Test$.main(Test_2.scala:7)
track: Test$.main(Test_2.scala:7)
track: Test$.main(Test_2.scala:8)
track: Test$.main(Test_2.scala:8)
track: Test$.main(Test_2.scala:7)
track: Test$.main(Test_2.scala:7)
main3: Test$.main(Test_2.scala:9)
main4: Test$.main(Test_2.scala:10)
track (i = 0): Test$.main(Test_2.scala:13)
Expand All @@ -15,22 +15,22 @@ track: Test$.main(Test_2.scala:13)
fact: Test$.main(Test_2.scala:13)
track (i = 2): Test$.main(Test_2.scala:14)
track (i = 2): Test$.main(Test_2.scala:14)
track: Test$.main(Test_2.scala:14)
track: Test$.main(Test_2.scala:14)
fact: Test$.main(Test_2.scala:14)
track: Test$.main(Test_2.scala:13)
track: Test$.main(Test_2.scala:13)
fact: Test$.main(Test_2.scala:13)
main1 (i = -1): Test$.main(Test_2.scala:15)
main2 (i = -1): Test$.main(Test_2.scala:16)
track (i = 1): Test$.main(Test_2.scala:14)
track (i = 1): Test$.main(Test_2.scala:14)
track: Test$.main(Test_2.scala:14)
track: Test$.main(Test_2.scala:14)
fact: Test$.main(Test_2.scala:14)
track (i = 1): Test$.main(Test_2.scala:13)
track (i = 1): Test$.main(Test_2.scala:13)
track: Test$.main(Test_2.scala:13)
track: Test$.main(Test_2.scala:13)
fact: Test$.main(Test_2.scala:13)
main1 (i = -1): Test$.main(Test_2.scala:15)
main2 (i = -1): Test$.main(Test_2.scala:16)
track (i = 0): Test$.main(Test_2.scala:14)
track (i = 0): Test$.main(Test_2.scala:14)
track: Test$.main(Test_2.scala:14)
track: Test$.main(Test_2.scala:14)
fact: Test$.main(Test_2.scala:14)
track (i = 0): Test$.main(Test_2.scala:13)
track (i = 0): Test$.main(Test_2.scala:13)
track: Test$.main(Test_2.scala:13)
track: Test$.main(Test_2.scala:13)
fact: Test$.main(Test_2.scala:13)
main1 (i = -1): Test$.main(Test_2.scala:15)
main2 (i = -1): Test$.main(Test_2.scala:16)