Skip to content

Commit d871d35

Browse files
authored
Merge pull request #14364 from dotty-staging/fix-14346
Keep language imports around longer
2 parents 593498d + 7608bb4 commit d871d35

File tree

7 files changed

+68
-64
lines changed

7 files changed

+68
-64
lines changed

compiler/src/dotty/tools/dotc/ast/TreeMapWithImplicits.scala

+1-40
Original file line numberDiff line numberDiff line change
@@ -15,51 +15,12 @@ import scala.annotation.tailrec
1515
*
1616
* This incudes implicits defined in scope as well as imported implicits.
1717
*/
18-
class TreeMapWithImplicits extends tpd.TreeMap {
18+
class TreeMapWithImplicits extends tpd.TreeMapWithPreciseStatContexts {
1919
import tpd._
2020

2121
def transformSelf(vd: ValDef)(using Context): ValDef =
2222
cpy.ValDef(vd)(tpt = transform(vd.tpt))
2323

24-
/** Transform statements, while maintaining import contexts and expression contexts
25-
* in the same way as Typer does. The code addresses additional concerns:
26-
* - be tail-recursive where possible
27-
* - don't re-allocate trees where nothing has changed
28-
*/
29-
override def transformStats(stats: List[Tree], exprOwner: Symbol)(using Context): List[Tree] = {
30-
31-
@tailrec def traverse(curStats: List[Tree])(using Context): List[Tree] = {
32-
33-
def recur(stats: List[Tree], changed: Tree, rest: List[Tree])(using Context): List[Tree] =
34-
if (stats eq curStats) {
35-
val rest1 = transformStats(rest, exprOwner)
36-
changed match {
37-
case Thicket(trees) => trees ::: rest1
38-
case tree => tree :: rest1
39-
}
40-
}
41-
else stats.head :: recur(stats.tail, changed, rest)
42-
43-
curStats match {
44-
case stat :: rest =>
45-
val statCtx = stat match {
46-
case stat: DefTree => ctx
47-
case _ => ctx.exprContext(stat, exprOwner)
48-
}
49-
val restCtx = stat match {
50-
case stat: Import => ctx.importContext(stat, stat.symbol)
51-
case _ => ctx
52-
}
53-
val stat1 = transform(stat)(using statCtx)
54-
if (stat1 ne stat) recur(stats, stat1, rest)(using restCtx)
55-
else traverse(rest)(using restCtx)
56-
case nil =>
57-
stats
58-
}
59-
}
60-
traverse(stats)
61-
}
62-
6324
private def nestedScopeCtx(defs: List[Tree])(using Context): Context = {
6425
val nestedCtx = ctx.fresh.setNewScope
6526
defs foreach {

compiler/src/dotty/tools/dotc/ast/tpd.scala

+46-1
Original file line numberDiff line numberDiff line change
@@ -1151,12 +1151,57 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
11511151
case _ => tree1 :: rest1
11521152
case nil => nil
11531153
recur(trees, 0)
1154+
1155+
/** Transform statements while maintaining import contexts and expression contexts
1156+
* in the same way as Typer does. The code addresses additional concerns:
1157+
* - be tail-recursive where possible
1158+
* - don't re-allocate trees where nothing has changed
1159+
*/
1160+
inline def mapStatements(exprOwner: Symbol, inline op: Tree => Context ?=> Tree)(using Context): List[Tree] =
1161+
@tailrec
1162+
def loop(mapped: mutable.ListBuffer[Tree] | Null, unchanged: List[Tree], pending: List[Tree])(using Context): List[Tree] =
1163+
pending match
1164+
case stat :: rest =>
1165+
val statCtx = stat match
1166+
case _: DefTree | _: ImportOrExport => ctx
1167+
case _ => ctx.exprContext(stat, exprOwner)
1168+
val stat1 = op(stat)(using statCtx)
1169+
val restCtx = stat match
1170+
case stat: Import => ctx.importContext(stat, stat.symbol)
1171+
case _ => ctx
1172+
if stat1 eq stat then
1173+
loop(mapped, unchanged, rest)(using restCtx)
1174+
else
1175+
val buf = if mapped == null then new mutable.ListBuffer[Tree] else mapped
1176+
var xc = unchanged
1177+
while xc ne pending do
1178+
buf += xc.head
1179+
xc = xc.tail
1180+
stat1 match
1181+
case Thicket(stats1) => buf ++= stats1
1182+
case _ => buf += stat1
1183+
loop(buf, rest, rest)(using restCtx)
1184+
case nil =>
1185+
if mapped == null then unchanged
1186+
else mapped.prependToList(unchanged)
1187+
1188+
loop(null, trees, trees)
1189+
end mapStatements
11541190
end extension
11551191

1192+
/** A treemap that generates the same contexts as the original typer for statements.
1193+
* This means:
1194+
* - statements that are not definitions get the exprOwner as owner
1195+
* - imports are reflected in the contexts of subsequent statements
1196+
*/
1197+
class TreeMapWithPreciseStatContexts(cpy: TreeCopier = tpd.cpy) extends TreeMap(cpy):
1198+
override def transformStats(trees: List[Tree], exprOwner: Symbol)(using Context): List[Tree] =
1199+
trees.mapStatements(exprOwner, transform(_))
1200+
11561201
/** Map Inlined nodes, NamedArgs, Blocks with no statements and local references to underlying arguments.
11571202
* Also drops Inline and Block with no statements.
11581203
*/
1159-
class MapToUnderlying extends TreeMap {
1204+
private class MapToUnderlying extends TreeMap {
11601205
override def transform(tree: Tree)(using Context): Tree = tree match {
11611206
case tree: Ident if isBinding(tree.symbol) && skipLocal(tree.symbol) =>
11621207
tree.symbol.defTree match {

compiler/src/dotty/tools/dotc/transform/FirstTransform.scala

+5-3
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ object FirstTransform {
2525
}
2626

2727
/** The first tree transform
28-
* - eliminates some kinds of trees: Imports, NamedArgs
28+
* - eliminates some kinds of trees: Imports other than language imports,
29+
* Exports, NamedArgs, type trees other than TypeTree
2930
* - stubs out native methods
3031
* - eliminates self tree in Template and self symbol in ClassInfo
3132
* - collapses all type trees to trees of class TypeTree
@@ -58,7 +59,7 @@ class FirstTransform extends MiniPhase with InfoTransformer { thisPhase =>
5859
tree.symbol.is(JavaStatic) && qualTpe.derivesFrom(tree.symbol.enclosingClass),
5960
i"non member selection of ${tree.symbol.showLocated} from ${qualTpe} in $tree")
6061
case _: TypeTree =>
61-
case _: Import | _: NamedArg | _: TypTree =>
62+
case _: Export | _: NamedArg | _: TypTree =>
6263
assert(false, i"illegal tree: $tree")
6364
case _ =>
6465
}
@@ -136,7 +137,8 @@ class FirstTransform extends MiniPhase with InfoTransformer { thisPhase =>
136137
}
137138

138139
override def transformOther(tree: Tree)(using Context): Tree = tree match {
139-
case tree: ImportOrExport => EmptyTree
140+
case tree: Import if untpd.languageImport(tree.expr).isEmpty => EmptyTree
141+
case tree: Export => EmptyTree
140142
case tree: NamedArg => transformAllDeep(tree.arg)
141143
case tree => if (tree.isType) toTypeTree(tree) else tree
142144
}

compiler/src/dotty/tools/dotc/transform/MacroTransform.scala

+3-11
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,11 @@ abstract class MacroTransform extends Phase {
2929
*/
3030
protected def transformPhase(using Context): Phase = this
3131

32-
class Transformer extends TreeMap(cpy = cpyBetweenPhases) {
32+
class Transformer extends TreeMapWithPreciseStatContexts(cpy = cpyBetweenPhases):
3333

34-
protected def localCtx(tree: Tree)(using Context): FreshContext =
34+
protected def localCtx(tree: Tree)(using Context): FreshContext =
3535
ctx.fresh.setTree(tree).setOwner(localOwner(tree))
3636

37-
override def transformStats(trees: List[Tree], exprOwner: Symbol)(using Context): List[Tree] = {
38-
def transformStat(stat: Tree): Tree = stat match {
39-
case _: Import | _: DefTree => transform(stat)
40-
case _ => transform(stat)(using ctx.exprContext(stat, exprOwner))
41-
}
42-
flatten(trees.mapconserve(transformStat(_)))
43-
}
44-
4537
override def transform(tree: Tree)(using Context): Tree =
4638
try
4739
tree match {
@@ -67,5 +59,5 @@ abstract class MacroTransform extends Phase {
6759

6860
def transformSelf(vd: ValDef)(using Context): ValDef =
6961
cpy.ValDef(vd)(tpt = transform(vd.tpt))
70-
}
62+
end Transformer
7163
}

compiler/src/dotty/tools/dotc/transform/MegaPhase.scala

+2-8
Original file line numberDiff line numberDiff line change
@@ -432,16 +432,10 @@ class MegaPhase(val miniPhases: Array[MiniPhase]) extends Phase {
432432
def transformSpecificTree[T <: Tree](tree: T, start: Int)(using Context): T =
433433
transformTree(tree, start).asInstanceOf[T]
434434

435-
def transformStats(trees: List[Tree], exprOwner: Symbol, start: Int)(using Context): List[Tree] = {
436-
def transformStat(stat: Tree)(using Context): Tree = stat match {
437-
case _: Import | _: DefTree => transformTree(stat, start)
438-
case Thicket(stats) => cpy.Thicket(stat)(stats.mapConserve(transformStat))
439-
case _ => transformTree(stat, start)(using ctx.exprContext(stat, exprOwner))
440-
}
435+
def transformStats(trees: List[Tree], exprOwner: Symbol, start: Int)(using Context): List[Tree] =
441436
val nestedCtx = prepStats(trees, start)
442-
val trees1 = trees.mapInline(transformStat(_)(using nestedCtx))
437+
val trees1 = trees.mapStatements(exprOwner, transformTree(_, start))(using nestedCtx)
443438
goStats(trees1, start)(using nestedCtx)
444-
}
445439

446440
def transformUnit(tree: Tree)(using Context): Tree = {
447441
val nestedCtx = prepUnit(tree, 0)

compiler/src/dotty/tools/dotc/transform/PruneErasedDefs.scala

+10
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@ import StdNames.nme
1414
import ast.tpd
1515
import SymUtils._
1616
import config.Feature
17+
import Decorators.*
1718

1819
/** This phase makes all erased term members of classes private so that they cannot
1920
* conflict with non-erased members. This is needed so that subsequent phases like
2021
* ResolveSuper that inspect class members work correctly.
2122
* The phase also replaces all expressions that appear in an erased context by
2223
* default values. This is necessary so that subsequent checking phases such
2324
* as IsInstanceOfChecker don't give false negatives.
25+
* Finally, the phase drops (language-) imports.
2426
*/
2527
class PruneErasedDefs extends MiniPhase with SymTransformer { thisTransform =>
2628
import tpd._
@@ -54,10 +56,18 @@ class PruneErasedDefs extends MiniPhase with SymTransformer { thisTransform =>
5456
checkErasedInExperimental(tree.symbol)
5557
tree
5658

59+
override def transformOther(tree: Tree)(using Context): Tree = tree match
60+
case tree: Import => EmptyTree
61+
case _ => tree
62+
5763
def checkErasedInExperimental(sym: Symbol)(using Context): Unit =
5864
// Make an exception for Scala 2 experimental macros to allow dual Scala 2/3 macros under non experimental mode
5965
if sym.is(Erased, butNot = Macro) && sym != defn.Compiletime_erasedValue && !sym.isInExperimentalScope then
6066
Feature.checkExperimentalFeature("erased", sym.sourcePos)
67+
68+
override def checkPostCondition(tree: Tree)(using Context): Unit = tree match
69+
case _: tpd.Import => assert(false, i"illegal tree: $tree")
70+
case _ =>
6171
}
6272

6373
object PruneErasedDefs {

compiler/src/dotty/tools/dotc/typer/Typer.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -2556,7 +2556,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
25562556
|The selector is not a member of an object or package.""")
25572557
else typd(imp.expr, AnySelectionProto)
25582558

2559-
def typedImport(imp: untpd.Import, sym: Symbol)(using Context): Import =
2559+
def typedImport(imp: untpd.Import, sym: Symbol)(using Context): Tree =
25602560
val expr1 = typedImportQualifier(imp, typedExpr(_, _)(using ctx.withOwner(sym)))
25612561
checkLegalImportPath(expr1)
25622562
val selectors1 = typedSelectors(imp.selectors)

0 commit comments

Comments
 (0)