diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index ac6cc701be87..b0f476444754 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -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} @@ -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") diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 09df6614d496..4ddeb6f66b85 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -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 */ @@ -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 diff --git a/tests/pos/i18316.orig.scala b/tests/pos/i18316.orig.scala new file mode 100644 index 000000000000..1d7d6ce8043b --- /dev/null +++ b/tests/pos/i18316.orig.scala @@ -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 +} diff --git a/tests/pos/i18316.scala b/tests/pos/i18316.scala new file mode 100644 index 000000000000..82f56a33468a --- /dev/null +++ b/tests/pos/i18316.scala @@ -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)