Skip to content

Commit 531f29b

Browse files
sjrdWojciechMazur
authored andcommitted
Fix #21295: Restrict provablyDisjoint with Nothings in invariant type params.
If `Foo[T]` is invariant in `T`, we previously concluded that `Foo[A] ⋔ Foo[B]` from `A ⋔ B`. That is however wrong if both `A` and `B` can be (instantiated to) `Nothing`. We now rule out these occurrences in two ways: * either we show that `T` corresponds to a field, like we do in the covariant case, or * we show that `A` or `B` cannot possibly be `Nothing`. The second condition is shaky at best. I would have preferred not to include it. However, introducing the former without the fallback on the latter breaks too many existing test cases. [Cherry-picked 0dceb7f]
1 parent c4572ba commit 531f29b

File tree

2 files changed

+27
-2
lines changed

2 files changed

+27
-2
lines changed

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

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3231,6 +3231,12 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
32313231
end provablyDisjointClasses
32323232

32333233
private def provablyDisjointTypeArgs(cls: ClassSymbol, args1: List[Type], args2: List[Type], pending: util.HashSet[(Type, Type)])(using Context): Boolean =
3234+
// sjrd: I will not be surprised when this causes further issues in the future.
3235+
// This is a compromise to be able to fix #21295 without breaking the world.
3236+
def cannotBeNothing(tp: Type): Boolean = tp match
3237+
case tp: TypeParamRef => cannotBeNothing(tp.paramInfo)
3238+
case _ => !(tp.loBound.stripTypeVar <:< defn.NothingType)
3239+
32343240
// It is possible to conclude that two types applied are disjoint by
32353241
// looking at covariant type parameters if the said type parameters
32363242
// are disjoint and correspond to fields.
@@ -3239,9 +3245,20 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
32393245
def covariantDisjoint(tp1: Type, tp2: Type, tparam: TypeParamInfo): Boolean =
32403246
provablyDisjoint(tp1, tp2, pending) && typeparamCorrespondsToField(cls.appliedRef, tparam)
32413247

3242-
// In the invariant case, direct type parameter disjointness is enough.
3248+
// In the invariant case, we have more ways to prove disjointness:
3249+
// - either the type param corresponds to a field, like in the covariant case, or
3250+
// - one of the two actual args can never be `Nothing`.
3251+
// The latter condition, as tested by `cannotBeNothing`, is ad hoc and was
3252+
// not carefully evaluated to be sound. We have it because we had to
3253+
// reintroduce the former condition to fix #21295, and alone, that broke a
3254+
// lot of existing test cases.
3255+
// Having either one of the two conditions be true is better than not requiring
3256+
// any, which was the status quo before #21295.
32433257
def invariantDisjoint(tp1: Type, tp2: Type, tparam: TypeParamInfo): Boolean =
3244-
provablyDisjoint(tp1, tp2, pending)
3258+
provablyDisjoint(tp1, tp2, pending) && {
3259+
typeparamCorrespondsToField(cls.appliedRef, tparam)
3260+
|| (cannotBeNothing(tp1) || cannotBeNothing(tp2))
3261+
}
32453262

32463263
args1.lazyZip(args2).lazyZip(cls.typeParams).exists {
32473264
(arg1, arg2, tparam) =>

tests/pos/i21295.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
sealed trait Foo[A]
2+
final class Bar extends Foo[Nothing]
3+
4+
object Test:
5+
type Extract[T] = T match
6+
case Foo[_] => Int
7+
8+
val x: Extract[Bar] = 1

0 commit comments

Comments
 (0)