Skip to content

Commit

Permalink
Fix #20897: Make Nothing ⋔ Nothing, as per spec.
Browse files Browse the repository at this point in the history
`derivesFrom`, used in `provablyDisjointClasses`, normally returns
`false` when the receiver is `Nothing`. However, it returns `true`
if the right-hand-side happens to be exactly `Nothing` as well.
For the purpose of computing `provablyDisjoint`, that is not what
we want.

The root issue was that we let the previous algorithm handle
`Nothing` like a class type, which it *is* in dotc but not in the
spec. That led to this mistake.

`AnyKind` suffers a similar issue, but already had special-cases in
various places to mitigate it.

Instead of adding a new special-case for `Nothing` inside
`provablyDisjointClasses`, we address the root issue. Now we deal
with `Nothing` and `AnyKind` early, before trying any of the code
paths that handle (real) class types.

[Cherry-picked b7846c4][modified]
  • Loading branch information
WojciechMazur committed Dec 2, 2024
1 parent 13848de commit 55861e9
Show file tree
Hide file tree
Showing 3 changed files with 23 additions and 1 deletion.
12 changes: 12 additions & 0 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2881,6 +2881,12 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
true
case (_, _: HKLambda) =>
true
/* Nothing is not a class type in the spec but dotc represents it as if it were one.
* Get it out of the way early to avoid mistakes (see for example #20897).
* Nothing ⋔ T and T ⋔ Nothing for all T.
*/
case (tp1, tp2) if tp1.isExactlyNothing || tp2.isExactlyNothing =>
true
case (tp1: OrType, _) =>
provablyDisjoint(tp1.tp1, tp2) && provablyDisjoint(tp1.tp2, tp2)
case (_, tp2: OrType) =>
Expand All @@ -2891,6 +2897,12 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
case (_, tp2: AndType) =>
!(tp2 <:< tp1)
&& (provablyDisjoint(tp1, tp2.tp2) || provablyDisjoint(tp1, tp2.tp1))
/* Handle AnyKind now for the same reason as Nothing above: it is not a real class type.
* Other than the rules with Nothing, unions and intersections, there is structurally
* no rule such that AnyKind ⋔ T or T ⋔ AnyKind for any T.
*/
case (tp1, tp2) if tp1.isDirectRef(AnyKindClass) || tp2.isDirectRef(AnyKindClass) =>
false
case (tp1: NamedType, _) if gadtBounds(tp1.symbol) != null =>
provablyDisjoint(gadtBounds(tp1.symbol).uncheckedNN.hi, tp2)
|| provablyDisjoint(tp1.superTypeNormalized, tp2)
Expand Down
2 changes: 1 addition & 1 deletion compiler/test/dotc/pos-test-pickling.blacklist
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ i15158.scala
i15155.scala
i15827.scala
i18211.scala
i20897.scala

# Opaque type
i5720.scala
Expand Down Expand Up @@ -112,4 +113,3 @@ i15525.scala
i19955a.scala
i19955b.scala
i20053b.scala

10 changes: 10 additions & 0 deletions tests/pos/i20897.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
object Test:
type Disj[A, B] =
A match
case B => true
case _ => false

def f(a: Disj[1 | Nothing, 2 | Nothing]): Unit = ()

val t = f(false)
end Test

0 comments on commit 55861e9

Please sign in to comment.