Skip to content

Commit ae44a7b

Browse files
committed
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.
1 parent b47f0f2 commit ae44a7b

File tree

2 files changed

+27
-2
lines changed

2 files changed

+27
-2
lines changed

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

+19-2
Original file line numberDiff line numberDiff line change
@@ -3232,6 +3232,12 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
32323232
end provablyDisjointClasses
32333233

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

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

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

tests/pos/i21295.scala

+8
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)