Skip to content

Commit 79de255

Browse files
rochaladwijnand
authored andcommitted
fix import shadowing
1 parent 716d93d commit 79de255

File tree

5 files changed

+103
-29
lines changed

5 files changed

+103
-29
lines changed

compiler/src/dotty/tools/repl/CollectTopLevelImports.scala

Lines changed: 0 additions & 29 deletions
This file was deleted.

compiler/src/dotty/tools/repl/ReplCompiler.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import dotty.tools.dotc.util.Spans._
1818
import dotty.tools.dotc.util.{ParsedComment, Property, SourceFile}
1919
import dotty.tools.dotc.{CompilationUnit, Compiler, Run}
2020
import dotty.tools.repl.results._
21+
import dotty.tools.repl.transform._
2122

2223
import scala.collection.mutable
2324
import scala.util.chaining.given

compiler/src/dotty/tools/repl/ReplDriver.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import dotty.tools.dotc.util.{SourceFile, SourcePosition}
3131
import dotty.tools.dotc.{CompilationUnit, Driver}
3232
import dotty.tools.dotc.config.CompilerCommand
3333
import dotty.tools.io._
34+
import dotty.tools.repl.transform._
3435
import dotty.tools.runner.ScalaClassLoader.*
3536
import org.jline.reader._
3637

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package dotty.tools.repl.transform
2+
3+
import dotty.tools.dotc.ast.tpd
4+
import dotty.tools.dotc.ast.untpd
5+
import dotty.tools.dotc.core.Contexts._
6+
import dotty.tools.dotc.core.Names.Name
7+
import dotty.tools.dotc.core.Phases.Phase
8+
import dotty.tools.dotc.core.StdNames.ScalaTermNames
9+
10+
11+
/** This phase collects and transforms top-level Import trees to handle definition shadowing.
12+
*
13+
* This is used by repl to handle new run contexts and allowing
14+
* definitions to be shadowed by imports in the same run.
15+
*
16+
* Import transformation is necessary for excluding its members when they are shadowed in the same run.
17+
* This is done by finding all members defined after the Import clause calculating
18+
* their intersection with available members from selectors
19+
*
20+
* This step is necessary for proper new run initialization since we need to import the previous run
21+
* into Context. It is accomplished in the following order:
22+
* 1. Previous wrapper object for a given run
23+
* 2. Previous imports for a given run
24+
*
25+
* This phase uses typed trees thus after the Typer phase.
26+
*/
27+
class CollectTopLevelImports extends Phase {
28+
import tpd._
29+
30+
def phaseName: String = "collectTopLevelImports"
31+
32+
private var gatheredImports: List[Import] = _
33+
34+
def imports: List[Import] = gatheredImports
35+
36+
def run(using Context): Unit =
37+
val PackageDef(_, _ :: TypeDef(_, rhs: Template) :: _) = ctx.compilationUnit.tpdTree: @unchecked
38+
gatheredImports = transformTopLevelImports(rhs.body)
39+
40+
/** Transforms top-level imports to exclude intersecting members declared after the Import clause.
41+
* To properly handle imports such as: `import A.f; def f = 3` consequently making sure that original selectors are
42+
* filtered to eliminate potential duplications that would result in compilation error.
43+
*/
44+
private def transformTopLevelImports(trees: List[Tree])(using Context): List[Import] =
45+
val definitions = collectTopLevelMemberDefs(trees)
46+
47+
trees.collect {
48+
case tree @ Import(expr, selectors) =>
49+
val definitionsAfterImport = definitions.filter(_._2 > tree.endPos.end).map(_._1)
50+
val membersIntersection = expr.tpe.allMembers.map(_.name).intersect(definitionsAfterImport)
51+
52+
val transformedSelectors = membersIntersection.map(collidingMember => {
53+
untpd.ImportSelector(untpd.Ident(collidingMember), untpd.Ident(CollectTopLevelImports.nme.WILDCARD))
54+
}).toList
55+
56+
val filteredSelectors = selectors.filterNot(importSelector => membersIntersection.contains(importSelector.imported.name))
57+
58+
Import(expr, transformedSelectors.toList ::: filteredSelectors)
59+
}
60+
61+
private def collectTopLevelMemberDefs(trees: List[Tree])(using Context): List[(Name, Int)] =
62+
trees.collect {
63+
case tree: ValDef => tree.name -> tree.endPos.end
64+
case tree: DefDef => tree.name -> tree.endPos.end
65+
case tree: TypeDef => tree.name -> tree.endPos.end
66+
}
67+
68+
}
69+
70+
object CollectTopLevelImports {
71+
private lazy val nme: ScalaTermNames = new ScalaTermNames
72+
}
73+

compiler/test-resources/repl/import-shadowing

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,31 @@ def f: Int
2525

2626
scala> val x3 = f
2727
val x3: Int = 1
28+
29+
scala> import A._; def f = 5
30+
def f: Int
31+
32+
scala> val x4 = f
33+
val x4: Int = 5
34+
35+
scala> def f = 6; println(f); import A._; println(f); // import shadowing should only work on toplevel definitions in next runs
36+
6
37+
6
38+
def f: Int
39+
40+
scala> import A._; println(f); def f = 7; println(f)
41+
7
42+
7
43+
def f: Int
44+
45+
scala> def f = 8; import A.f
46+
def f: Int
47+
48+
scala> val x5 = f
49+
val x5: Int = 1
50+
51+
scala> import A.f; def f = 9
52+
def f: Int
53+
54+
scala> val x6 = f
55+
val x6: Int = 9

0 commit comments

Comments
 (0)