Skip to content

Commit 84e7757

Browse files
dwijnandtgodzik
authored andcommitted
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 fa2ee33 commit 84e7757

File tree

7 files changed

+80
-43
lines changed

7 files changed

+80
-43
lines changed

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

Lines changed: 40 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -205,57 +205,54 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
205205
!owner.isEmptyPackage || ctx.owner.enclosingPackageClass.isEmptyPackage
206206
}
207207

208+
import BindingPrec.*
209+
type Result = (Type, BindingPrec)
210+
val NoResult = (NoType, NothingBound)
211+
208212
/** Find the denotation of enclosing `name` in given context `ctx`.
209-
* @param previous A denotation that was found in a more deeply nested scope,
210-
* or else `NoDenotation` if nothing was found yet.
211-
* @param prevPrec The binding precedence of the previous denotation,
212-
* or else `nothingBound` if nothing was found yet.
213+
* @param prevResult A type that was found in a more deeply nested scope,
214+
* and its precedence, or NoResult if nothing was found yet.
213215
* @param prevCtx The context of the previous denotation,
214216
* or else `NoContext` if nothing was found yet.
215217
*/
216-
def findRefRecur(previous: Type, prevPrec: BindingPrec, prevCtx: Context)(using Context): Type = {
217-
import BindingPrec.*
218+
def findRefRecur(prevResult: Result, prevCtx: Context)(using Context): Result = {
219+
val (previous, prevPrec) = prevResult
218220

219221
/** Check that any previously found result from an inner context
220222
* does properly shadow the new one from an outer context.
221-
* @param found The newly found result
222-
* @param newPrec Its precedence
223+
* @param newResult The newly found type and its precedence.
223224
* @param scala2pkg Special mode where we check members of the same package, but defined
224225
* in different compilation units under Scala2. If set, and the
225226
* previous and new contexts do not have the same scope, we select
226227
* the previous (inner) definition. This models what scalac does.
227228
*/
228-
def checkNewOrShadowed(found: Type, newPrec: BindingPrec, scala2pkg: Boolean = false)(using Context): Type =
229+
def checkNewOrShadowed(newResult: Result, scala2pkg: Boolean = false)(using Context): Result =
230+
val (found, newPrec) = newResult
229231
if !previous.exists || TypeComparer.isSameRef(previous, found) then
230232
found
231233
else if (prevCtx.scope eq ctx.scope)
232234
&& (newPrec == Definition || newPrec == NamedImport && prevPrec == WildImport)
233235
then
234236
// special cases: definitions beat imports, and named imports beat
235237
// wildcard imports, provided both are in contexts with same scope
236-
found
238+
newResult
237239
else
238240
if !scala2pkg && !previous.isError && !found.isError then
239241
fail(AmbiguousReference(name, newPrec, prevPrec, prevCtx))
240242
previous
241-
242-
/** Assemble and check alternatives to an imported reference. This implies:
243-
* - If we expand an extension method (i.e. altImports != null),
244-
* search imports on the same level for other possible resolutions of `name`.
245-
* The result and altImports together then contain all possible imported
246-
* references of the highest possible precedence, where `NamedImport` beats
247243
* `WildImport`.
248244
* - Find a posssibly shadowing reference in an outer context.
249245
* If the result is the same as `previous`, check that it is new or
250246
* shadowed. This order of checking is necessary since an outer package-level
251247
* definition might trump two conflicting inner imports, so no error should be
252248
* issued in that case. See i7876.scala.
253-
* @param previous the previously found reference (which is an import)
254-
* @param prevPrec the precedence of the reference (either NamedImport or WildImport)
249+
* @param prevResult the previously found reference (which is an import), and
250+
* the precedence of the reference (either NamedImport or WildImport)
255251
* @param prevCtx the context in which the reference was found
256252
* @param using_Context the outer context of `precCtx`
257253
*/
258-
def checkImportAlternatives(previous: Type, prevPrec: BindingPrec, prevCtx: Context)(using Context): Type =
254+
def checkImportAlternatives(prevResult: Result, prevCtx: Context)(using Context): Result =
255+
val (previous, prevPrec) = prevResult
259256

260257
def addAltImport(altImp: TermRef) =
261258
if !TypeComparer.isSameRef(previous, altImp)
@@ -270,20 +267,20 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
270267
if prevPrec == WildImport then
271268
// Discard all previously found references and continue with `altImp`
272269
altImports.clear()
273-
checkImportAlternatives(altImp, NamedImport, ctx)(using ctx.outer)
270+
checkImportAlternatives((altImp, NamedImport), ctx)(using ctx.outer)
274271
else
275272
addAltImport(altImp)
276-
checkImportAlternatives(previous, prevPrec, prevCtx)(using ctx.outer)
273+
checkImportAlternatives(prevResult, prevCtx)(using ctx.outer)
277274
case _ =>
278275
if prevPrec == WildImport then
279276
wildImportRef(curImport) match
280277
case altImp: TermRef => addAltImport(altImp)
281278
case _ =>
282-
checkImportAlternatives(previous, prevPrec, prevCtx)(using ctx.outer)
279+
checkImportAlternatives(prevResult, prevCtx)(using ctx.outer)
283280
else
284-
val found = findRefRecur(previous, prevPrec, prevCtx)
285-
if found eq previous then checkNewOrShadowed(found, prevPrec)(using prevCtx)
286-
else found
281+
val foundResult = findRefRecur(prevResult, prevCtx)
282+
if foundResult._1 eq previous then checkNewOrShadowed(foundResult)(using prevCtx)
283+
else foundResult
287284
end checkImportAlternatives
288285

289286
def selection(imp: ImportInfo, name: Name, checkBounds: Boolean): Type =
@@ -370,10 +367,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
370367
!noImports &&
371368
(prevPrec.ordinal < prec.ordinal || prevPrec == prec && (prevCtx.scope eq ctx.scope))
372369

373-
@tailrec def loop(lastCtx: Context)(using Context): Type =
374-
if (ctx.scope eq EmptyScope) previous
370+
@tailrec def loop(lastCtx: Context)(using Context): Result =
371+
if (ctx.scope eq EmptyScope) prevResult
375372
else {
376-
var result: Type = NoType
373+
var result: Result = NoResult
377374
val curOwner = ctx.owner
378375

379376
/** Is curOwner a package object that should be skipped?
@@ -472,37 +469,36 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
472469
effectiveOwner.thisType.select(name, defDenot)
473470
}
474471
if !curOwner.is(Package) || isDefinedInCurrentUnit(defDenot) then
475-
result = checkNewOrShadowed(found, Definition) // no need to go further out, we found highest prec entry
472+
result = checkNewOrShadowed((found, Definition)) // no need to go further out, we found highest prec entry
476473
found match
477474
case found: NamedType
478475
if curOwner.isClass && isInherited(found.denot) && !ctx.compilationUnit.isJava =>
479476
checkNoOuterDefs(found.denot, ctx, ctx)
480477
case _ =>
481478
else
482479
if migrateTo3 && !foundUnderScala2.exists then
483-
foundUnderScala2 = checkNewOrShadowed(found, Definition, scala2pkg = true)
480+
foundUnderScala2 = checkNewOrShadowed((found, Definition), scala2pkg = true)._1
484481
if (defDenot.symbol.is(Package))
485-
result = checkNewOrShadowed(previous orElse found, PackageClause)
482+
result = checkNewOrShadowed((previous orElse found, PackageClause))
486483
else if (prevPrec.ordinal < PackageClause.ordinal)
487-
result = findRefRecur(found, PackageClause, ctx)(using ctx.outer)
484+
result = findRefRecur((found, PackageClause), ctx)(using ctx.outer)
488485
}
489486

490-
if result.exists then result
487+
if result._1.exists then result
491488
else { // find import
492489
val outer = ctx.outer
493490
val curImport = ctx.importInfo
494-
def updateUnimported() =
495-
if (curImport.nn.unimported ne NoSymbol) unimported += curImport.nn.unimported
496491
if (curOwner.is(Package) && curImport != null && curImport.isRootImport && previous.exists)
497-
previous // no more conflicts possible in this case
498-
else if (isPossibleImport(NamedImport) && (curImport nen outer.importInfo)) {
499-
val namedImp = namedImportRef(curImport.uncheckedNN)
492+
prevResult // no more conflicts possible in this case
493+
else if (isPossibleImport(NamedImport) && curImport != null && (curImport ne outer.importInfo)) {
494+
def updateUnimported() = if curImport.unimported ne NoSymbol then unimported += curImport.unimported
495+
val namedImp = namedImportRef(curImport)
500496
if (namedImp.exists)
501-
checkImportAlternatives(namedImp, NamedImport, ctx)(using outer)
502-
else if (isPossibleImport(WildImport) && !curImport.nn.importSym.isCompleting) {
503-
val wildImp = wildImportRef(curImport.uncheckedNN)
497+
checkImportAlternatives((namedImp, NamedImport), ctx)(using outer)
498+
else if (isPossibleImport(WildImport) && !curImport.importSym.isCompleting) {
499+
val wildImp = wildImportRef(curImport)
504500
if (wildImp.exists)
505-
checkImportAlternatives(wildImp, WildImport, ctx)(using outer)
501+
checkImportAlternatives((wildImp, WildImport), ctx)(using outer)
506502
else {
507503
updateUnimported()
508504
loop(ctx)(using outer)
@@ -521,7 +517,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
521517
loop(NoContext)
522518
}
523519

524-
findRefRecur(NoType, BindingPrec.NothingBound, NoContext)
520+
val (foundRef, foundPrec) = findRefRecur(NoResult, NoContext)
521+
foundRef
525522
}
526523

527524
/** 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)