Skip to content

Commit 3783220

Browse files
authored
Fix how implicit candidates are combined (#18321)
2 parents 4347d29 + aeb6a9f commit 3783220

File tree

4 files changed

+60
-16
lines changed

4 files changed

+60
-16
lines changed

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

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import ProtoTypes._
2323
import ErrorReporting._
2424
import Inferencing.{fullyDefinedType, isFullyDefined}
2525
import Scopes.newScope
26+
import Typer.BindingPrec, BindingPrec.*
2627
import transform.TypeUtils._
2728
import Hashable._
2829
import util.{EqHashMap, Stats}
@@ -328,25 +329,28 @@ object Implicits:
328329
(this eq finalImplicits) || (outerImplicits eqn finalImplicits)
329330
}
330331

332+
def bindingPrec: BindingPrec =
333+
if isImport then if ctx.importInfo.uncheckedNN.isWildcardImport then WildImport else NamedImport else Definition
334+
331335
private def combineEligibles(ownEligible: List[Candidate], outerEligible: List[Candidate]): List[Candidate] =
332336
if ownEligible.isEmpty then outerEligible
333337
else if outerEligible.isEmpty then ownEligible
334338
else
335-
def filter(xs: List[Candidate], remove: List[Candidate]) =
336-
val shadowed = remove.map(_.ref.implicitName).toSet
337-
xs.filterConserve(cand => !shadowed.contains(cand.ref.implicitName))
338-
339+
val ownNames = mutable.Set(ownEligible.map(_.ref.implicitName)*)
339340
val outer = outerImplicits.uncheckedNN
340-
def isWildcardImport(using Context) = ctx.importInfo.nn.isWildcardImport
341-
def preferDefinitions = isImport && !outer.isImport
342-
def preferNamedImport = isWildcardImport && !isWildcardImport(using outer.irefCtx)
343-
344-
if !migrateTo3(using irefCtx) && level == outer.level && (preferDefinitions || preferNamedImport) then
345-
// special cases: definitions beat imports, and named imports beat
346-
// wildcard imports, provided both are in contexts with same scope
347-
filter(ownEligible, outerEligible) ::: outerEligible
341+
if !migrateTo3(using irefCtx) && level == outer.level && outer.bindingPrec.beats(bindingPrec) then
342+
val keptOuters = outerEligible.filterConserve: cand =>
343+
if ownNames.contains(cand.ref.implicitName) then
344+
val keepOuter = cand.level == level
345+
if keepOuter then ownNames -= cand.ref.implicitName
346+
keepOuter
347+
else true
348+
val keptOwn = ownEligible.filterConserve: cand =>
349+
ownNames.contains(cand.ref.implicitName)
350+
keptOwn ::: keptOuters
348351
else
349-
ownEligible ::: filter(outerEligible, ownEligible)
352+
ownEligible ::: outerEligible.filterConserve: cand =>
353+
!ownNames.contains(cand.ref.implicitName)
350354

351355
def uncachedEligible(tp: Type)(using Context): List[Candidate] =
352356
Stats.record("uncached eligible")

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ object Typer {
6565
case NothingBound, PackageClause, WildImport, NamedImport, Inheritance, Definition
6666

6767
def isImportPrec = this == NamedImport || this == WildImport
68+
69+
/** special cases: definitions beat imports, and named imports beat
70+
* wildcard imports, provided both are in contexts with same scope */
71+
def beats(prevPrec: BindingPrec): Boolean =
72+
this == Definition || this == NamedImport && prevPrec == WildImport
6873
}
6974

7075
/** Assert tree has a position, unless it is empty or a typed splice */
@@ -226,9 +231,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
226231
def checkNewOrShadowed(found: Type, newPrec: BindingPrec, scala2pkg: Boolean = false)(using Context): Type =
227232
if !previous.exists || TypeComparer.isSameRef(previous, found) then
228233
found
229-
else if (prevCtx.scope eq ctx.scope)
230-
&& (newPrec == Definition || newPrec == NamedImport && prevPrec == WildImport)
231-
then
234+
else if (prevCtx.scope eq ctx.scope) && newPrec.beats(prevPrec) then
232235
// special cases: definitions beat imports, and named imports beat
233236
// wildcard imports, provided both are in contexts with same scope
234237
found

tests/pos/i18316.orig.scala

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import scala.language.implicitConversions
2+
object squerel {
3+
trait EqualityExpression
4+
object PrimitiveTypeMode:
5+
implicit def intToTE(f: Int): TypedExpression[Int] = ???
6+
7+
trait TypedExpression[A1]:
8+
def ===[A2](b: TypedExpression[A2]): EqualityExpression = ???
9+
}
10+
11+
object scalactic {
12+
trait TripleEqualsSupport:
13+
class Equalizer[L](val leftSide: L):
14+
def ===(rightSide: Any): Boolean = ???
15+
16+
trait TripleEquals extends TripleEqualsSupport:
17+
implicit def convertToEqualizer[T](left: T): Equalizer[T] = ???
18+
}
19+
20+
import squerel.PrimitiveTypeMode._ // remove to make code compile
21+
object Test extends scalactic.TripleEquals {
22+
import squerel.PrimitiveTypeMode._
23+
val fails: squerel.EqualityExpression = 1 === 1
24+
}

tests/pos/i18316.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
class R1
2+
class R2
3+
4+
class Foo { def meth(x: Int): R1 = null }
5+
class Bar { def meth(x: Int): R2 = null }
6+
7+
object Impl { implicit def mkFoo(i: Int): Foo = null }
8+
trait Trait { implicit def mkBar(i: Int): Bar = null }
9+
10+
import Impl.mkFoo // remove to make code compile
11+
object Test extends Trait:
12+
import Impl.mkFoo
13+
val fails: R1 = 1.meth(1)

0 commit comments

Comments
 (0)