Skip to content

Commit

Permalink
Bypass eligible caches for implicit search under GADT constraints
Browse files Browse the repository at this point in the history
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
  • Loading branch information
odersky committed Dec 8, 2021
1 parent d2a6201 commit 0249fc6
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 12 deletions.
33 changes: 23 additions & 10 deletions compiler/src/dotty/tools/dotc/typer/Implicits.scala
Expand Up @@ -330,11 +330,25 @@ object Implicits:
(this eq finalImplicits) || (outerImplicits eq finalImplicits)
}

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

def uncachedEligible(tp: Type)(using Context): List[Candidate] =
Stats.record("uncached eligible")
if monitored then record(s"check uncached eligible refs in irefCtx", refs.length)
val ownEligible = filterMatching(tp)
if isOuterMost then ownEligible
else combineEligibles(ownEligible, outerImplicits.uncachedEligible(tp))

/** The implicit references that are eligible for type `tp`. */
def eligible(tp: Type): List[Candidate] =
if (tp.hash == NotCached)
Stats.record(i"compute eligible not cached ${tp.getClass}")
Stats.record(i"compute eligible not cached")
Stats.record("compute eligible not cached")
computeEligible(tp)
else {
val eligibles = eligibleCache.lookup(tp)
Expand All @@ -354,14 +368,8 @@ object Implicits:
private def computeEligible(tp: Type): List[Candidate] = /*>|>*/ trace(i"computeEligible $tp in $refs%, %", implicitsDetailed) /*<|<*/ {
if (monitored) record(s"check eligible refs in irefCtx", refs.length)
val ownEligible = filterMatching(tp)
if (isOuterMost) ownEligible
else if ownEligible.isEmpty then outerImplicits.eligible(tp)
else
val outerEligible = outerImplicits.eligible(tp)
if outerEligible.isEmpty then ownEligible
else
val shadowed = ownEligible.map(_.ref.implicitName).toSet
ownEligible ::: outerEligible.filterConserve(cand => !shadowed.contains(cand.ref.implicitName))
if isOuterMost then ownEligible
else combineEligibles(ownEligible, outerImplicits.eligible(tp))
}

override def isAccessible(ref: TermRef)(using Context): Boolean =
Expand Down Expand Up @@ -1444,7 +1452,12 @@ trait Implicits:
NoMatchingImplicitsFailure
else
val eligible =
if contextual then ctx.implicits.eligible(wildProto)
if contextual then
if ctx.gadt.isNarrowing then
withoutMode(Mode.ImplicitsEnabled) {
ctx.implicits.uncachedEligible(wildProto)
}
else ctx.implicits.eligible(wildProto)
else implicitScope(wildProto).eligible
searchImplicit(eligible, contextual) match
case result: SearchSuccess =>
Expand Down
3 changes: 3 additions & 0 deletions compiler/test/dotc/pos-test-pickling.blacklist
Expand Up @@ -75,4 +75,7 @@ i13842.scala
# GADT cast applied to singleton type difference
i4176-gadt.scala

# GADT difference
i13974a.scala

java-inherited-type1
4 changes: 2 additions & 2 deletions tests/neg/gadt-approximation-interaction.scala
Expand Up @@ -47,7 +47,7 @@ object ImplicitConversion {

def foo[T](t: T, ev: T SUB Int) =
ev match { case SUB.Refl() =>
t ** 2 // error // implementation limitation
t ** 2
}

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

def foo[T](t: T, ev: T SUB Int) =
ev match { case SUB.Refl() =>
t ** 2 // error (implementation limitation)
t ** 2
}

def bar[T](t: T, ev: T SUB Int) =
Expand Down
12 changes: 12 additions & 0 deletions tests/pos/13974a.scala
@@ -0,0 +1,12 @@

object Test2:
class Foo[+X]
enum SUB[-S, +T]:
case Refl[U]() extends SUB[U, U]
def f[A, B, C](sub : A SUB (B,C)) =
given Foo[A] = ???
val x = summon[Foo[A]]
sub match
case SUB.Refl() =>
val c: Foo[(B, C)] = summon[Foo[A]]
summon[Foo[(B, C)]]
13 changes: 13 additions & 0 deletions tests/pos/i13974.scala
@@ -0,0 +1,13 @@
object Test {
class C
class Use[A]
case class UseC() extends Use[C]
class ConversionTarget
implicit def convert(c: C): ConversionTarget = ???
def go[X](u: Use[X], x: X) =
u match {
case UseC() =>
//val y: C = x
x: ConversionTarget
}
}

0 comments on commit 0249fc6

Please sign in to comment.