Skip to content

Commit 08d592f

Browse files
committed
Resolve name when named imp is behind wild imps
When a named import (such as `import bug.util.List`) is defined before two clashing wildcard imports (`import bug.util.*; import java.util.*`) the name "List" should resolve to it, rather than a resolution error being emitted. This was due to the fact that `findRefRecur` didn't return the precedence at which it found that import, `checkImportAlternatives` used the `prevPrec` to `checkNewOrShadowed`. Now we check against the entire `foundResult`, allowing an early named import to be picked over later wildcard imports.
1 parent 64411b6 commit 08d592f

File tree

7 files changed

+82
-39
lines changed

7 files changed

+82
-39
lines changed

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

Lines changed: 42 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -241,38 +241,40 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
241241
!owner.isEmptyPackage || ctx.owner.enclosingPackageClass.isEmptyPackage
242242
}
243243

244+
import BindingPrec.*
245+
type Result = (Type, BindingPrec)
246+
val NoResult = (NoType, NothingBound)
247+
244248
/** Find the denotation of enclosing `name` in given context `ctx`.
245-
* @param previous A denotation that was found in a more deeply nested scope,
246-
* or else `NoDenotation` if nothing was found yet.
247-
* @param prevPrec The binding precedence of the previous denotation,
248-
* or else `nothingBound` if nothing was found yet.
249+
* @param prevResult A type that was found in a more deeply nested scope,
250+
* and its precedence, or NoResult if nothing was found yet.
249251
* @param prevCtx The context of the previous denotation,
250252
* or else `NoContext` if nothing was found yet.
251253
*/
252-
def findRefRecur(previous: Type, prevPrec: BindingPrec, prevCtx: Context)(using Context): Type = {
253-
import BindingPrec.*
254+
def findRefRecur(prevResult: Result, prevCtx: Context)(using Context): Result = {
255+
val (previous, prevPrec) = prevResult
254256

255257
/** Check that any previously found result from an inner context
256258
* does properly shadow the new one from an outer context.
257-
* @param found The newly found result
258-
* @param newPrec Its precedence
259+
* @param newResult The newly found type and its precedence.
259260
* @param scala2pkg Special mode where we check members of the same package, but defined
260261
* in different compilation units under Scala2. If set, and the
261262
* previous and new contexts do not have the same scope, we select
262263
* the previous (inner) definition. This models what scalac does.
263264
*/
264-
def checkNewOrShadowed(found: Type, newPrec: BindingPrec, scala2pkg: Boolean = false)(using Context): Type =
265+
def checkNewOrShadowed(newResult: Result, scala2pkg: Boolean = false)(using Context): Result =
266+
val (found, newPrec) = newResult
265267
if !previous.exists || TypeComparer.isSameRef(previous, found) then
266-
found
268+
newResult
267269
else if (prevCtx.scope eq ctx.scope) && newPrec.beats(prevPrec) then
268270
// special cases: definitions beat imports, and named imports beat
269271
// wildcard imports, provided both are in contexts with same scope
270-
found
272+
newResult
271273
else
272274
if !scala2pkg && !previous.isError && !found.isError then
273275
fail(AmbiguousReference(name, newPrec, prevPrec, prevCtx,
274276
isExtension = previous.termSymbol.is(ExtensionMethod) && found.termSymbol.is(ExtensionMethod)))
275-
previous
277+
prevResult
276278

277279
/** Assemble and check alternatives to an imported reference. This implies:
278280
* - If we expand an extension method (i.e. altImports != null),
@@ -285,12 +287,13 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
285287
* shadowed. This order of checking is necessary since an outer package-level
286288
* definition might trump two conflicting inner imports, so no error should be
287289
* issued in that case. See i7876.scala.
288-
* @param previous the previously found reference (which is an import)
289-
* @param prevPrec the precedence of the reference (either NamedImport or WildImport)
290+
* @param prevResult the previously found reference (which is an import), and
291+
* the precedence of the reference (either NamedImport or WildImport)
290292
* @param prevCtx the context in which the reference was found
291293
* @param using_Context the outer context of `precCtx`
292294
*/
293-
def checkImportAlternatives(previous: Type, prevPrec: BindingPrec, prevCtx: Context)(using Context): Type =
295+
def checkImportAlternatives(prevResult: Result, prevCtx: Context)(using Context): Result =
296+
val (previous, prevPrec) = prevResult
294297

295298
def addAltImport(altImp: TermRef) =
296299
if !TypeComparer.isSameRef(previous, altImp)
@@ -305,20 +308,20 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
305308
if prevPrec == WildImport then
306309
// Discard all previously found references and continue with `altImp`
307310
altImports.clear()
308-
checkImportAlternatives(altImp, NamedImport, ctx)(using ctx.outer)
311+
checkImportAlternatives((altImp, NamedImport), ctx)(using ctx.outer)
309312
else
310313
addAltImport(altImp)
311-
checkImportAlternatives(previous, prevPrec, prevCtx)(using ctx.outer)
314+
checkImportAlternatives(prevResult, prevCtx)(using ctx.outer)
312315
case _ =>
313316
if prevPrec == WildImport then
314317
wildImportRef(curImport) match
315318
case altImp: TermRef => addAltImport(altImp)
316319
case _ =>
317-
checkImportAlternatives(previous, prevPrec, prevCtx)(using ctx.outer)
320+
checkImportAlternatives(prevResult, prevCtx)(using ctx.outer)
318321
else
319-
val found = findRefRecur(previous, prevPrec, prevCtx)
320-
if found eq previous then checkNewOrShadowed(found, prevPrec)(using prevCtx)
321-
else found
322+
val foundResult = findRefRecur(prevResult, prevCtx)
323+
if foundResult._1 eq previous then checkNewOrShadowed(foundResult)(using prevCtx)
324+
else foundResult
322325
end checkImportAlternatives
323326

324327
def selection(imp: ImportInfo, name: Name, checkBounds: Boolean): Type =
@@ -408,10 +411,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
408411
!noImports &&
409412
(prevPrec.ordinal < prec.ordinal || prevPrec == prec && (prevCtx.scope eq ctx.scope))
410413

411-
@tailrec def loop(lastCtx: Context)(using Context): Type =
412-
if (ctx.scope eq EmptyScope) previous
414+
@tailrec def loop(lastCtx: Context)(using Context): Result =
415+
if (ctx.scope eq EmptyScope) prevResult
413416
else {
414-
var result: Type = NoType
417+
var result: Result = NoResult
415418
val curOwner = ctx.owner
416419

417420
/** Is curOwner a package object that should be skipped?
@@ -510,37 +513,36 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
510513
effectiveOwner.thisType.select(name, defDenot).makePackageObjPrefixExplicit
511514
}
512515
if !curOwner.is(Package) || isDefinedInCurrentUnit(defDenot) then
513-
result = checkNewOrShadowed(found, Definition) // no need to go further out, we found highest prec entry
516+
result = checkNewOrShadowed((found, Definition)) // no need to go further out, we found highest prec entry
514517
found match
515518
case found: NamedType
516519
if curOwner.isClass && isInherited(found.denot) && !ctx.compilationUnit.isJava =>
517520
checkNoOuterDefs(found.denot, ctx, ctx)
518521
case _ =>
519522
else
520523
if migrateTo3 && !foundUnderScala2.exists then
521-
foundUnderScala2 = checkNewOrShadowed(found, Definition, scala2pkg = true)
524+
foundUnderScala2 = checkNewOrShadowed((found, Definition), scala2pkg = true)._1
522525
if (defDenot.symbol.is(Package))
523-
result = checkNewOrShadowed(previous orElse found, PackageClause)
526+
result = checkNewOrShadowed((previous orElse found, PackageClause))
524527
else if (prevPrec.ordinal < PackageClause.ordinal)
525-
result = findRefRecur(found, PackageClause, ctx)(using ctx.outer)
528+
result = findRefRecur((found, PackageClause), ctx)(using ctx.outer)
526529
}
527530

528-
if result.exists then result
531+
if result._1.exists then result
529532
else { // find import
530533
val outer = ctx.outer
531534
val curImport = ctx.importInfo
532-
def updateUnimported() =
533-
if (curImport.nn.unimported ne NoSymbol) unimported += curImport.nn.unimported
534535
if (curOwner.is(Package) && curImport != null && curImport.isRootImport && previous.exists)
535-
previous // no more conflicts possible in this case
536-
else if (isPossibleImport(NamedImport) && (curImport nen outer.importInfo)) {
537-
val namedImp = namedImportRef(curImport.uncheckedNN)
536+
prevResult // no more conflicts possible in this case
537+
else if (isPossibleImport(NamedImport) && curImport != null && (curImport ne outer.importInfo)) {
538+
def updateUnimported() = if curImport.unimported ne NoSymbol then unimported += curImport.unimported
539+
val namedImp = namedImportRef(curImport)
538540
if (namedImp.exists)
539-
checkImportAlternatives(namedImp, NamedImport, ctx)(using outer)
540-
else if (isPossibleImport(WildImport) && !curImport.nn.importSym.isCompleting) {
541-
val wildImp = wildImportRef(curImport.uncheckedNN)
541+
checkImportAlternatives((namedImp, NamedImport), ctx)(using outer)
542+
else if (isPossibleImport(WildImport) && !curImport.importSym.isCompleting) {
543+
val wildImp = wildImportRef(curImport)
542544
if (wildImp.exists)
543-
checkImportAlternatives(wildImp, WildImport, ctx)(using outer)
545+
checkImportAlternatives((wildImp, WildImport), ctx)(using outer)
544546
else {
545547
updateUnimported()
546548
loop(ctx)(using outer)
@@ -559,7 +561,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
559561
loop(NoContext)
560562
}
561563

562-
findRefRecur(NoType, BindingPrec.NothingBound, NoContext)
564+
val (foundRef, foundPrec) = findRefRecur(NoResult, NoContext)
565+
foundRef
563566
}
564567

565568
/** If `tree`'s type is a `TermRef` identified by flow typing to be non-null, then

tests/pos/i18529/JCode1.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package bug.code;
2+
3+
import bug.util.List;
4+
import bug.util.*;
5+
import java.util.*;
6+
7+
public class JCode1 {
8+
public void m1(List<Integer> xs) { return; }
9+
}

tests/pos/i18529/JCode2.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package bug.code;
2+
3+
import bug.util.*;
4+
import bug.util.List;
5+
import java.util.*;
6+
7+
public class JCode2 {
8+
public void m1(List<Integer> xs) { return; }
9+
}

tests/pos/i18529/List.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package bug.util;
2+
3+
public final class List<E> {}

tests/pos/i18529/SCode1.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package bug.code
2+
3+
import bug.util.List
4+
import bug.util.*
5+
import java.util.*
6+
7+
class SCode1 {
8+
def work(xs: List[Int]): Unit = {}
9+
}

tests/pos/i18529/SCode2.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package bug.code
2+
3+
import bug.util.*
4+
import bug.util.List
5+
import java.util.*
6+
7+
class SCode2 {
8+
def work(xs: List[Int]): Unit = {}
9+
}

tests/pos/i18529/Test.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
class Test

0 commit comments

Comments
 (0)