diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 6cba2f78776b..4d87d6406567 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -641,6 +641,13 @@ class PlainPrinter(_ctx: Context) extends Printer { else if (pos.source.exists) s"${pos.source.file.name}:${pos.line + 1}" else s"(no source file, offset = ${pos.span.point})" + def toText(cand: Candidate): Text = + "Cand(" + ~ toTextRef(cand.ref) + ~ (if cand.isConversion then " conv" else "") + ~ (if cand.isExtension then " ext" else "") + ~ Str(" L" + cand.level) ~ ")" + def toText(result: SearchResult): Text = result match { case result: SearchSuccess => "SearchSuccess: " ~ toText(result.ref) ~ " via " ~ toText(result.tree) diff --git a/compiler/src/dotty/tools/dotc/printing/Printer.scala b/compiler/src/dotty/tools/dotc/printing/Printer.scala index b3e48ab2d843..04cea9fb9702 100644 --- a/compiler/src/dotty/tools/dotc/printing/Printer.scala +++ b/compiler/src/dotty/tools/dotc/printing/Printer.scala @@ -7,7 +7,7 @@ import Texts._, ast.Trees._ import Types.{Type, SingletonType, LambdaParam, NamedType}, Symbols.Symbol, Scopes.Scope, Constants.Constant, Names.Name, Denotations._, Annotations.Annotation, Contexts.Context -import typer.Implicits.SearchResult +import typer.Implicits.* import util.SourcePosition import typer.ImportInfo @@ -153,6 +153,9 @@ abstract class Printer { /** Textual representation of source position */ def toText(pos: SourcePosition): Text + /** Textual representation of implicit candidates. */ + def toText(cand: Candidate): Text + /** Textual representation of implicit search result */ def toText(result: SearchResult): Text diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index e576c6363e39..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} @@ -49,17 +50,19 @@ object Implicits: } /** Both search candidates and successes are references with a specific nesting level. */ - sealed trait RefAndLevel { + sealed trait RefAndLevel extends Showable { def ref: TermRef def level: Int } /** An eligible implicit candidate, consisting of an implicit reference and a nesting level */ - case class Candidate(implicitRef: ImplicitRef, kind: Candidate.Kind, level: Int) extends RefAndLevel { + case class Candidate(implicitRef: ImplicitRef, kind: Candidate.Kind, level: Int) extends RefAndLevel with Showable { def ref: TermRef = implicitRef.underlyingRef def isExtension = (kind & Candidate.Extension) != 0 def isConversion = (kind & Candidate.Conversion) != 0 + + def toText(printer: Printer): Text = printer.toText(this) } object Candidate { type Kind = Int @@ -326,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 3aaf4fec59d6..0082805f9ba4 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)