Skip to content

Commit 3fce32d

Browse files
committed
Bypass eligible caches for implicit search under GADT constraints
Under a non-empty GADT constraint, bypass caches when computing eligible implicits. The GADT constraint might influence what implicits are available locally, so cached results would be unreliable. Fixes #13974
1 parent d2a6201 commit 3fce32d

File tree

5 files changed

+53
-12
lines changed

5 files changed

+53
-12
lines changed

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

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -330,11 +330,25 @@ object Implicits:
330330
(this eq finalImplicits) || (outerImplicits eq finalImplicits)
331331
}
332332

333+
private def combineEligibles(ownEligible: List[Candidate], outerEligible: List[Candidate]): List[Candidate] =
334+
if ownEligible.isEmpty then outerEligible
335+
else if outerEligible.isEmpty then ownEligible
336+
else
337+
val shadowed = ownEligible.map(_.ref.implicitName).toSet
338+
ownEligible ::: outerEligible.filterConserve(cand => !shadowed.contains(cand.ref.implicitName))
339+
340+
def uncachedEligible(tp: Type)(using Context): List[Candidate] =
341+
Stats.record("uncached eligible")
342+
if monitored then record(s"check uncached eligible refs in irefCtx", refs.length)
343+
val ownEligible = filterMatching(tp)
344+
if isOuterMost then ownEligible
345+
else combineEligibles(ownEligible, outerImplicits.uncachedEligible(tp))
346+
333347
/** The implicit references that are eligible for type `tp`. */
334348
def eligible(tp: Type): List[Candidate] =
335349
if (tp.hash == NotCached)
336350
Stats.record(i"compute eligible not cached ${tp.getClass}")
337-
Stats.record(i"compute eligible not cached")
351+
Stats.record("compute eligible not cached")
338352
computeEligible(tp)
339353
else {
340354
val eligibles = eligibleCache.lookup(tp)
@@ -354,14 +368,8 @@ object Implicits:
354368
private def computeEligible(tp: Type): List[Candidate] = /*>|>*/ trace(i"computeEligible $tp in $refs%, %", implicitsDetailed) /*<|<*/ {
355369
if (monitored) record(s"check eligible refs in irefCtx", refs.length)
356370
val ownEligible = filterMatching(tp)
357-
if (isOuterMost) ownEligible
358-
else if ownEligible.isEmpty then outerImplicits.eligible(tp)
359-
else
360-
val outerEligible = outerImplicits.eligible(tp)
361-
if outerEligible.isEmpty then ownEligible
362-
else
363-
val shadowed = ownEligible.map(_.ref.implicitName).toSet
364-
ownEligible ::: outerEligible.filterConserve(cand => !shadowed.contains(cand.ref.implicitName))
371+
if isOuterMost then ownEligible
372+
else combineEligibles(ownEligible, outerImplicits.eligible(tp))
365373
}
366374

367375
override def isAccessible(ref: TermRef)(using Context): Boolean =
@@ -1444,7 +1452,12 @@ trait Implicits:
14441452
NoMatchingImplicitsFailure
14451453
else
14461454
val eligible =
1447-
if contextual then ctx.implicits.eligible(wildProto)
1455+
if contextual then
1456+
if ctx.gadt.isNarrowing then
1457+
withoutMode(Mode.ImplicitsEnabled) {
1458+
ctx.implicits.uncachedEligible(wildProto)
1459+
}
1460+
else ctx.implicits.eligible(wildProto)
14481461
else implicitScope(wildProto).eligible
14491462
searchImplicit(eligible, contextual) match
14501463
case result: SearchSuccess =>

compiler/test/dotc/pos-test-pickling.blacklist

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,7 @@ i13842.scala
7575
# GADT cast applied to singleton type difference
7676
i4176-gadt.scala
7777

78+
# GADT difference
79+
i13974a.scala
80+
7881
java-inherited-type1

tests/neg/gadt-approximation-interaction.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ object ImplicitConversion {
4747

4848
def foo[T](t: T, ev: T SUB Int) =
4949
ev match { case SUB.Refl() =>
50-
t ** 2 // error // implementation limitation
50+
t ** 2
5151
}
5252

5353
def bar[T](t: T, ev: T SUB Int) =
@@ -67,7 +67,7 @@ object GivenConversion {
6767

6868
def foo[T](t: T, ev: T SUB Int) =
6969
ev match { case SUB.Refl() =>
70-
t ** 2 // error (implementation limitation)
70+
t ** 2
7171
}
7272

7373
def bar[T](t: T, ev: T SUB Int) =

tests/pos/i13974.scala

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
object Test {
2+
class C
3+
class Use[A]
4+
case class UseC() extends Use[C]
5+
class ConversionTarget
6+
implicit def convert(c: C): ConversionTarget = ???
7+
def go[X](u: Use[X], x: X) =
8+
u match {
9+
case UseC() =>
10+
//val y: C = x
11+
x: ConversionTarget
12+
}
13+
}

tests/pos/i13974a.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
object Test2:
3+
class Foo[+X]
4+
enum SUB[-S, +T]:
5+
case Refl[U]() extends SUB[U, U]
6+
def f[A, B, C](sub : A SUB (B,C)) =
7+
given Foo[A] = ???
8+
val x = summon[Foo[A]]
9+
sub match
10+
case SUB.Refl() =>
11+
val c: Foo[(B, C)] = summon[Foo[A]]
12+
summon[Foo[(B, C)]]

0 commit comments

Comments
 (0)