Skip to content

Commit 9913ee6

Browse files
authored
Merge pull request #14072 from dotty-staging/fix-13974
Bypass eligible caches for implicit search under GADT constraints
2 parents 5731a5f + 3fce32d commit 9913ee6

File tree

7 files changed

+82
-33
lines changed

7 files changed

+82
-33
lines changed

compiler/src/dotty/tools/dotc/core/GadtConstraint.scala

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ sealed abstract class GadtConstraint extends Showable {
4343
*/
4444
def contains(sym: Symbol)(using Context): Boolean
4545

46-
def isEmpty: Boolean
47-
final def nonEmpty: Boolean = !isEmpty
46+
/** GADT constraint narrows bounds of at least one variable */
47+
def isNarrowing: Boolean
4848

4949
/** See [[ConstraintHandling.approximation]] */
5050
def approximation(sym: Symbol, fromBelow: Boolean)(using Context): Type
@@ -61,13 +61,15 @@ final class ProperGadtConstraint private(
6161
private var myConstraint: Constraint,
6262
private var mapping: SimpleIdentityMap[Symbol, TypeVar],
6363
private var reverseMapping: SimpleIdentityMap[TypeParamRef, Symbol],
64+
private var wasConstrained: Boolean
6465
) extends GadtConstraint with ConstraintHandling {
6566
import dotty.tools.dotc.config.Printers.{gadts, gadtsConstr}
6667

6768
def this() = this(
6869
myConstraint = new OrderingConstraint(SimpleIdentityMap.empty, SimpleIdentityMap.empty, SimpleIdentityMap.empty),
6970
mapping = SimpleIdentityMap.empty,
70-
reverseMapping = SimpleIdentityMap.empty
71+
reverseMapping = SimpleIdentityMap.empty,
72+
wasConstrained = false
7173
)
7274

7375
/** Exposes ConstraintHandling.subsumes */
@@ -149,20 +151,24 @@ final class ProperGadtConstraint private(
149151
if (ntTvar ne null) stripInternalTypeVar(ntTvar) else bound
150152
case _ => bound
151153
}
152-
(
153-
internalizedBound match {
154-
case boundTvar: TypeVar =>
155-
if (boundTvar eq symTvar) true
156-
else if (isUpper) addLess(symTvar.origin, boundTvar.origin)
157-
else addLess(boundTvar.origin, symTvar.origin)
158-
case bound =>
159-
addBoundTransitively(symTvar.origin, bound, isUpper)
160-
}
161-
).showing({
154+
155+
val saved = constraint
156+
val result = internalizedBound match
157+
case boundTvar: TypeVar =>
158+
if (boundTvar eq symTvar) true
159+
else if (isUpper) addLess(symTvar.origin, boundTvar.origin)
160+
else addLess(boundTvar.origin, symTvar.origin)
161+
case bound =>
162+
addBoundTransitively(symTvar.origin, bound, isUpper)
163+
164+
gadts.println {
162165
val descr = if (isUpper) "upper" else "lower"
163166
val op = if (isUpper) "<:" else ">:"
164167
i"adding $descr bound $sym $op $bound = $result"
165-
}, gadts)
168+
}
169+
170+
if constraint ne saved then wasConstrained = true
171+
result
166172
}
167173

168174
override def isLess(sym1: Symbol, sym2: Symbol)(using Context): Boolean =
@@ -193,6 +199,8 @@ final class ProperGadtConstraint private(
193199

194200
override def contains(sym: Symbol)(using Context): Boolean = mapping(sym) ne null
195201

202+
def isNarrowing: Boolean = wasConstrained
203+
196204
override def approximation(sym: Symbol, fromBelow: Boolean)(using Context): Type = {
197205
val res = approximation(tvarOrError(sym).origin, fromBelow = fromBelow)
198206
gadts.println(i"approximating $sym ~> $res")
@@ -202,19 +210,19 @@ final class ProperGadtConstraint private(
202210
override def fresh: GadtConstraint = new ProperGadtConstraint(
203211
myConstraint,
204212
mapping,
205-
reverseMapping
213+
reverseMapping,
214+
wasConstrained
206215
)
207216

208217
def restore(other: GadtConstraint): Unit = other match {
209218
case other: ProperGadtConstraint =>
210219
this.myConstraint = other.myConstraint
211220
this.mapping = other.mapping
212221
this.reverseMapping = other.reverseMapping
222+
this.wasConstrained = other.wasConstrained
213223
case _ => ;
214224
}
215225

216-
override def isEmpty: Boolean = mapping.size == 0
217-
218226
// ---- Protected/internal -----------------------------------------------
219227

220228
override protected def constraint = myConstraint
@@ -293,7 +301,7 @@ final class ProperGadtConstraint private(
293301

294302
override def isLess(sym1: Symbol, sym2: Symbol)(using Context): Boolean = unsupported("EmptyGadtConstraint.isLess")
295303

296-
override def isEmpty: Boolean = true
304+
override def isNarrowing: Boolean = false
297305

298306
override def contains(sym: Symbol)(using Context) = false
299307

@@ -304,7 +312,7 @@ final class ProperGadtConstraint private(
304312

305313
override def fresh = new ProperGadtConstraint
306314
override def restore(other: GadtConstraint): Unit =
307-
if (!other.isEmpty) sys.error("cannot restore a non-empty GADTMap")
315+
assert(!other.isNarrowing, "cannot restore a non-empty GADTMap")
308316

309317
override def debugBoundsDescription(using Context): String = "EmptyGadtConstraint"
310318

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/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1662,7 +1662,7 @@ class Typer extends Namer
16621662
val pat1 = indexPattern(tree).transform(pat)
16631663
val guard1 = typedExpr(tree.guard, defn.BooleanType)
16641664
var body1 = ensureNoLocalRefs(typedExpr(tree.body, pt1), pt1, ctx.scope.toList)
1665-
if ctx.gadt.nonEmpty then
1665+
if ctx.gadt.isNarrowing then
16661666
// Store GADT constraint to later retrieve it (in PostTyper, for now).
16671667
// GADT constraints are necessary to correctly check bounds of type app,
16681668
// see tests/pos/i12226 and issue #12226. It might be possible that this
@@ -3824,7 +3824,7 @@ class Typer extends Namer
38243824

38253825
pt match
38263826
case pt: SelectionProto =>
3827-
if ctx.gadt.nonEmpty then
3827+
if ctx.gadt.isNarrowing then
38283828
// try GADT approximation if we're trying to select a member
38293829
// Member lookup cannot take GADTs into account b/c of cache, so we
38303830
// approximate types based on GADT constraints instead. For an example,

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)