Skip to content

Commit

Permalink
Fix how implicit candidates are combined (scala#18321)
Browse files Browse the repository at this point in the history
  • Loading branch information
dwijnand authored Aug 22, 2023
2 parents 4347d29 + aeb6a9f commit 3783220
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 16 deletions.
30 changes: 17 additions & 13 deletions compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import ProtoTypes._
import ErrorReporting._
import Inferencing.{fullyDefinedType, isFullyDefined}
import Scopes.newScope
import Typer.BindingPrec, BindingPrec.*
import transform.TypeUtils._
import Hashable._
import util.{EqHashMap, Stats}
Expand Down Expand Up @@ -328,25 +329,28 @@ object Implicits:
(this eq finalImplicits) || (outerImplicits eqn finalImplicits)
}

def bindingPrec: BindingPrec =
if isImport then if ctx.importInfo.uncheckedNN.isWildcardImport then WildImport else NamedImport else Definition

private def combineEligibles(ownEligible: List[Candidate], outerEligible: List[Candidate]): List[Candidate] =
if ownEligible.isEmpty then outerEligible
else if outerEligible.isEmpty then ownEligible
else
def filter(xs: List[Candidate], remove: List[Candidate]) =
val shadowed = remove.map(_.ref.implicitName).toSet
xs.filterConserve(cand => !shadowed.contains(cand.ref.implicitName))

val ownNames = mutable.Set(ownEligible.map(_.ref.implicitName)*)
val outer = outerImplicits.uncheckedNN
def isWildcardImport(using Context) = ctx.importInfo.nn.isWildcardImport
def preferDefinitions = isImport && !outer.isImport
def preferNamedImport = isWildcardImport && !isWildcardImport(using outer.irefCtx)

if !migrateTo3(using irefCtx) && level == outer.level && (preferDefinitions || preferNamedImport) then
// special cases: definitions beat imports, and named imports beat
// wildcard imports, provided both are in contexts with same scope
filter(ownEligible, outerEligible) ::: outerEligible
if !migrateTo3(using irefCtx) && level == outer.level && outer.bindingPrec.beats(bindingPrec) then
val keptOuters = outerEligible.filterConserve: cand =>
if ownNames.contains(cand.ref.implicitName) then
val keepOuter = cand.level == level
if keepOuter then ownNames -= cand.ref.implicitName
keepOuter
else true
val keptOwn = ownEligible.filterConserve: cand =>
ownNames.contains(cand.ref.implicitName)
keptOwn ::: keptOuters
else
ownEligible ::: filter(outerEligible, ownEligible)
ownEligible ::: outerEligible.filterConserve: cand =>
!ownNames.contains(cand.ref.implicitName)

def uncachedEligible(tp: Type)(using Context): List[Candidate] =
Stats.record("uncached eligible")
Expand Down
9 changes: 6 additions & 3 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ object Typer {
case NothingBound, PackageClause, WildImport, NamedImport, Inheritance, Definition

def isImportPrec = this == NamedImport || this == WildImport

/** special cases: definitions beat imports, and named imports beat
* wildcard imports, provided both are in contexts with same scope */
def beats(prevPrec: BindingPrec): Boolean =
this == Definition || this == NamedImport && prevPrec == WildImport
}

/** Assert tree has a position, unless it is empty or a typed splice */
Expand Down Expand Up @@ -226,9 +231,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
def checkNewOrShadowed(found: Type, newPrec: BindingPrec, scala2pkg: Boolean = false)(using Context): Type =
if !previous.exists || TypeComparer.isSameRef(previous, found) then
found
else if (prevCtx.scope eq ctx.scope)
&& (newPrec == Definition || newPrec == NamedImport && prevPrec == WildImport)
then
else if (prevCtx.scope eq ctx.scope) && newPrec.beats(prevPrec) then
// special cases: definitions beat imports, and named imports beat
// wildcard imports, provided both are in contexts with same scope
found
Expand Down
24 changes: 24 additions & 0 deletions tests/pos/i18316.orig.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import scala.language.implicitConversions
object squerel {
trait EqualityExpression
object PrimitiveTypeMode:
implicit def intToTE(f: Int): TypedExpression[Int] = ???

trait TypedExpression[A1]:
def ===[A2](b: TypedExpression[A2]): EqualityExpression = ???
}

object scalactic {
trait TripleEqualsSupport:
class Equalizer[L](val leftSide: L):
def ===(rightSide: Any): Boolean = ???

trait TripleEquals extends TripleEqualsSupport:
implicit def convertToEqualizer[T](left: T): Equalizer[T] = ???
}

import squerel.PrimitiveTypeMode._ // remove to make code compile
object Test extends scalactic.TripleEquals {
import squerel.PrimitiveTypeMode._
val fails: squerel.EqualityExpression = 1 === 1
}
13 changes: 13 additions & 0 deletions tests/pos/i18316.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class R1
class R2

class Foo { def meth(x: Int): R1 = null }
class Bar { def meth(x: Int): R2 = null }

object Impl { implicit def mkFoo(i: Int): Foo = null }
trait Trait { implicit def mkBar(i: Int): Bar = null }

import Impl.mkFoo // remove to make code compile
object Test extends Trait:
import Impl.mkFoo
val fails: R1 = 1.meth(1)

0 comments on commit 3783220

Please sign in to comment.