Skip to content

Commit 0908c3a

Browse files
committed
Fix how implicit candidates are combined
1 parent 46fc22b commit 0908c3a

File tree

5 files changed

+64
-3
lines changed

5 files changed

+64
-3
lines changed

compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,9 @@ class PlainPrinter(_ctx: Context) extends Printer {
641641
else if (pos.source.exists) s"${pos.source.file.name}:${pos.line + 1}"
642642
else s"(no source file, offset = ${pos.span.point})"
643643

644+
def toText(cand: Candidate): Text =
645+
"Candidate(" ~ toText(cand.ref) ~ ", " ~ Str("kind=" + cand.kind) ~ ", " ~ Str("lvl=" + cand.level) ~ ")"
646+
644647
def toText(result: SearchResult): Text = result match {
645648
case result: SearchSuccess =>
646649
"SearchSuccess: " ~ toText(result.ref) ~ " via " ~ toText(result.tree)

compiler/src/dotty/tools/dotc/printing/Printer.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import Texts._, ast.Trees._
77
import Types.{Type, SingletonType, LambdaParam, NamedType},
88
Symbols.Symbol, Scopes.Scope, Constants.Constant,
99
Names.Name, Denotations._, Annotations.Annotation, Contexts.Context
10-
import typer.Implicits.SearchResult
10+
import typer.Implicits.*
1111
import util.SourcePosition
1212
import typer.ImportInfo
1313

@@ -153,6 +153,9 @@ abstract class Printer {
153153
/** Textual representation of source position */
154154
def toText(pos: SourcePosition): Text
155155

156+
/** Textual representation of implicit candidates. */
157+
def toText(cand: Candidate): Text
158+
156159
/** Textual representation of implicit search result */
157160
def toText(result: SearchResult): Text
158161

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

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,13 @@ object Implicits:
5555
}
5656

5757
/** An eligible implicit candidate, consisting of an implicit reference and a nesting level */
58-
case class Candidate(implicitRef: ImplicitRef, kind: Candidate.Kind, level: Int) extends RefAndLevel {
58+
case class Candidate(implicitRef: ImplicitRef, kind: Candidate.Kind, level: Int) extends RefAndLevel with Showable {
5959
def ref: TermRef = implicitRef.underlyingRef
6060

6161
def isExtension = (kind & Candidate.Extension) != 0
6262
def isConversion = (kind & Candidate.Conversion) != 0
63+
64+
def toText(printer: Printer): Text = printer.toText(this)
6365
}
6466
object Candidate {
6567
type Kind = Int
@@ -331,6 +333,7 @@ object Implicits:
331333
else if outerEligible.isEmpty then ownEligible
332334
else
333335
def filter(xs: List[Candidate], remove: List[Candidate]) =
336+
// Drop candidates that are shadowed by candidates in "remove"
334337
val shadowed = remove.map(_.ref.implicitName).toSet
335338
xs.filterConserve(cand => !shadowed.contains(cand.ref.implicitName))
336339

@@ -342,7 +345,22 @@ object Implicits:
342345
if !migrateTo3(using irefCtx) && level == outer.level && (preferDefinitions || preferNamedImport) then
343346
// special cases: definitions beat imports, and named imports beat
344347
// wildcard imports, provided both are in contexts with same scope
345-
filter(ownEligible, outerEligible) ::: outerEligible
348+
349+
// Using only the outer candidates at the same level as us,
350+
// remove from our own eligibles any shadowed candidate.
351+
// This removes locally imported candidates from shadowing local definitions, (foo's in i18316)
352+
// but without a remotely imported candidate removing a more locally imported candidates (mkFoo's in i18183)
353+
val ownEligible1 = filter(ownEligible, outerEligible.filter(_.level == level))
354+
355+
// Remove, from the outer eligibles, any candidate shadowed by one of our own candidates,
356+
// provided that the outer eligibles aren't at the same level (so actually shadows).
357+
// This complements the filtering of our own eligible candidates, by removing candidates in the outer candidates
358+
// that are low-level priority and shadowed by our candidates. E.g. the outer import Imp.mkFoo in i18183.
359+
val shadowed = ownEligible.map(_.ref.implicitName).toSet
360+
val outerEligible1 =
361+
outerEligible.filterConserve(cand => cand.level == level || !shadowed.contains(cand.ref.implicitName))
362+
363+
ownEligible1 ::: outerEligible1
346364
else
347365
ownEligible ::: filter(outerEligible, ownEligible)
348366

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)