diff --git a/compiler/src/dotty/tools/repl/CollectTopLevelImports.scala b/compiler/src/dotty/tools/repl/CollectTopLevelImports.scala deleted file mode 100644 index d539c1986f8d..000000000000 --- a/compiler/src/dotty/tools/repl/CollectTopLevelImports.scala +++ /dev/null @@ -1,29 +0,0 @@ -package dotty.tools.repl - -import dotty.tools.dotc.ast.tpd -import dotty.tools.dotc.core.Contexts._ -import dotty.tools.dotc.core.Phases.Phase - -/** A phase that collects user defined top level imports. - * - * These imports must be collected as typed trees and therefore - * after Typer. - */ -class CollectTopLevelImports extends Phase { - import tpd._ - - def phaseName: String = "collectTopLevelImports" - - private var myImports: List[Import] = _ - def imports: List[Import] = myImports - - def run(using Context): Unit = { - def topLevelImports(tree: Tree) = { - val PackageDef(_, _ :: TypeDef(_, rhs: Template) :: _) = tree: @unchecked - rhs.body.collect { case tree: Import => tree } - } - - val tree = ctx.compilationUnit.tpdTree - myImports = topLevelImports(tree) - } -} diff --git a/compiler/src/dotty/tools/repl/ReplCompiler.scala b/compiler/src/dotty/tools/repl/ReplCompiler.scala index 764695e8479b..a874f25dc4b5 100644 --- a/compiler/src/dotty/tools/repl/ReplCompiler.scala +++ b/compiler/src/dotty/tools/repl/ReplCompiler.scala @@ -18,6 +18,7 @@ import dotty.tools.dotc.util.Spans._ import dotty.tools.dotc.util.{ParsedComment, Property, SourceFile} import dotty.tools.dotc.{CompilationUnit, Compiler, Run} import dotty.tools.repl.results._ +import dotty.tools.repl.transform._ import scala.collection.mutable import scala.util.chaining.given diff --git a/compiler/src/dotty/tools/repl/ReplDriver.scala b/compiler/src/dotty/tools/repl/ReplDriver.scala index f076333cf449..ca7616a6521e 100644 --- a/compiler/src/dotty/tools/repl/ReplDriver.scala +++ b/compiler/src/dotty/tools/repl/ReplDriver.scala @@ -31,6 +31,7 @@ import dotty.tools.dotc.util.{SourceFile, SourcePosition} import dotty.tools.dotc.{CompilationUnit, Driver} import dotty.tools.dotc.config.CompilerCommand import dotty.tools.io._ +import dotty.tools.repl.transform._ import dotty.tools.runner.ScalaClassLoader.* import org.jline.reader._ diff --git a/compiler/src/dotty/tools/repl/transform/CollectTopLevelImports.scala b/compiler/src/dotty/tools/repl/transform/CollectTopLevelImports.scala new file mode 100644 index 000000000000..eafaf4249cd4 --- /dev/null +++ b/compiler/src/dotty/tools/repl/transform/CollectTopLevelImports.scala @@ -0,0 +1,85 @@ +package dotty.tools.repl.transform + +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.ast.untpd +import dotty.tools.dotc.core.Contexts._ +import dotty.tools.dotc.core.Names.Name +import dotty.tools.dotc.core.Phases.Phase +import dotty.tools.dotc.core.StdNames.nme + + +/** This phase collects and transforms top-level Import trees to handle definition shadowing. + * + * This is used by repl to handle new run contexts and allowing + * definitions to be shadowed by imports in the same run. + * + * Import transformation is necessary for excluding its members when they are shadowed in the same run. + * This is done by finding all members defined after the Import clause calculating + * their intersection with available members from selectors including renaming. + * + * This step is necessary for proper new run initialization since we need to import the previous run + * into Context. It is accomplished in the following order: + * 1. Previous wrapper object for a given run + * 2. Previous imports for a given run + * + * This phase uses typed trees thus after the Typer phase. + */ +class CollectTopLevelImports extends Phase { + import tpd._ + + def phaseName: String = "collectTopLevelImports" + + private var gatheredImports: List[Import] = _ + + def imports: List[Import] = gatheredImports + + def run(using Context): Unit = + val PackageDef(_, _ :: TypeDef(_, rhs: Template) :: _) = ctx.compilationUnit.tpdTree: @unchecked + gatheredImports = transformTopLevelImports(rhs.body) + + /** Transforms top-level imports to exclude intersecting members declared after the Import clause. + * To properly handle imports such as: `import A.f; def f = 3` consequently making sure that original selectors are + * filtered to eliminate potential duplications that would result in compilation error. + * + * Transformed imports of which selectors were all shadowed will be ignored in the future runs. + */ + private def transformTopLevelImports(trees: List[Tree])(using Context): List[Import] = + val definitions = collectTopLevelMemberDefs(trees) + + trees.collect { + case tree @ Import(expr, selectors) => + val definitionsAfterImport = definitions.filter(_._2 > tree.endPos.end).map(_._1) + + val importedNames: List[Name] = (if selectors.exists(_.isWildcard) then + val allImportTypeMembers = expr.tpe.allMembers.map(_.name) + val nonWildcardSelectors = selectors.filter(_.isWildcard) + val renamedMembers = nonWildcardSelectors.map(_.imported.name) + nonWildcardSelectors.map(_.rename) ++ allImportTypeMembers.filterNot(renamedMembers.contains) + else + selectors.map(_.rename) + ) + + val shadowedMembers = importedNames.intersect(definitionsAfterImport) + val adjustedSelectors = shadowedMembers.map(collidingMember => { + untpd.ImportSelector(untpd.Ident(collidingMember), untpd.Ident(nme.WILDCARD)) + }) + + val remainingSelectors = selectors.filterNot(importSelector => { + shadowedMembers.contains(importSelector.rename) + }) + + if remainingSelectors.isEmpty then + None + else + Some(Import(expr, adjustedSelectors ++ remainingSelectors)) + }.flatten + + private def collectTopLevelMemberDefs(trees: List[Tree])(using Context): List[(Name, Int)] = + trees.collect { + case tree: ValDef => tree.name -> tree.endPos.end + case tree: DefDef => tree.name -> tree.endPos.end + case tree: TypeDef => tree.name -> tree.endPos.end + } + +} + diff --git a/compiler/test-resources/repl/import-shadowing b/compiler/test-resources/repl/import-shadowing index 71dc48dbe3c2..a16dd29220fb 100644 --- a/compiler/test-resources/repl/import-shadowing +++ b/compiler/test-resources/repl/import-shadowing @@ -1,4 +1,4 @@ -scala> object A { def f = 1 } +scala> object A { def f = 1; def t = 2 } // defined object A scala> object B { def f = 2 } @@ -25,3 +25,73 @@ def f: Int scala> val x3 = f val x3: Int = 1 + +scala> import A._; def f = 5 +def f: Int + +scala> val x4 = f +val x4: Int = 5 + +scala> def f = 6; println(f); import A._; println(f); // import shadowing should only work on toplevel definitions in the following runs +6 +6 +def f: Int + +scala> import A._; println(f); def f = 7; println(f) // import shadowing should only work on toplevel definitions in the following runs +7 +7 +def f: Int + +scala> def f = 8; import A.f +def f: Int + +scala> val x5 = f +val x5: Int = 1 + +scala> import A.f; def f = 9 +def f: Int + +scala> val x6 = f +val x6: Int = 9 + +scala> import A.{f => ff}; def f = 10 +def f: Int + +scala> val x7 = f +val x7: Int = 10 + +scala> val x8 = ff +val x8: Int = 1 + +scala> def f = 10; import A.{f => ff} +def f: Int + +scala> val x9 = f +val x9: Int = 10 + +scala> val x10 = ff +val x10: Int = 1 + +scala> import A.{t, _}; def f = 11 +def f: Int + +scala> val x11 = f +val x11: Int = 11 + +scala> def f = 12; import A.{t, _} +def f: Int + +scala> val x12 = f +val x12: Int = 1 + +scala> def f = 13; import A.{t => f} +def f: Int + +scala> val x13 = f +val x13: Int = 2 + +scala> import A.{t => f}; def f = 14 +def f: Int + +scala> val x14 = f +val x14: Int = 14