Skip to content

Commit 4d327bd

Browse files
committed
fix import shadowing
1 parent e35b6ff commit 4d327bd

File tree

5 files changed

+104
-29
lines changed

5 files changed

+104
-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, 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

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

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)