Skip to content

Commit 2ea90c6

Browse files
authored
Fix #20897: Make Nothing ⋔ Nothing, as per spec. (#21241)
`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.
2 parents 4c9cf0a + b7846c4 commit 2ea90c6

File tree

3 files changed

+28
-8
lines changed

3 files changed

+28
-8
lines changed

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

+17-7
Original file line numberDiff line numberDiff line change
@@ -3064,6 +3064,13 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
30643064
case pair if pending != null && pending.contains(pair) =>
30653065
false
30663066

3067+
/* Nothing is not a class type in the spec but dotc represents it as if it were one.
3068+
* Get it out of the way early to avoid mistakes (see for example #20897).
3069+
* Nothing ⋔ T and T ⋔ Nothing for all T.
3070+
*/
3071+
case (tp1, tp2) if tp1.isExactlyNothing || tp2.isExactlyNothing =>
3072+
true
3073+
30673074
// Cases where there is an intersection or union on the right
30683075
case (tp1, tp2: OrType) =>
30693076
provablyDisjoint(tp1, tp2.tp1, pending) && provablyDisjoint(tp1, tp2.tp2, pending)
@@ -3076,14 +3083,21 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
30763083
case (tp1: AndType, tp2) =>
30773084
provablyDisjoint(tp1.tp1, tp2, pending) || provablyDisjoint(tp1.tp2, tp2, pending)
30783085

3086+
/* Handle AnyKind now for the same reason as Nothing above: it is not a real class type.
3087+
* Other than the rules with Nothing, unions and intersections, there is structurally
3088+
* no rule such that AnyKind ⋔ T or T ⋔ AnyKind for any T.
3089+
*/
3090+
case (tp1, tp2) if tp1.isDirectRef(AnyKindClass) || tp2.isDirectRef(AnyKindClass) =>
3091+
false
3092+
30793093
// Cases involving type lambdas
30803094
case (tp1: HKTypeLambda, tp2: HKTypeLambda) =>
30813095
tp1.paramNames.sizeCompare(tp2.paramNames) != 0
30823096
|| provablyDisjoint(tp1.resultType, tp2.resultType, pending)
30833097
case (tp1: HKTypeLambda, tp2) =>
3084-
!tp2.isDirectRef(defn.AnyKindClass)
3098+
true
30853099
case (tp1, tp2: HKTypeLambda) =>
3086-
!tp1.isDirectRef(defn.AnyKindClass)
3100+
true
30873101

30883102
/* Cases where both are unique values (enum cases or constant types)
30893103
*
@@ -3187,17 +3201,13 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
31873201
else child
31883202
}.filter(child => child.exists && child != cls)
31893203

3190-
// TODO? Special-case for Nothing and Null? We probably need Nothing/Null disjoint from Nothing/Null
31913204
def eitherDerivesFromOther(cls1: Symbol, cls2: Symbol): Boolean =
31923205
cls1.derivesFrom(cls2) || cls2.derivesFrom(cls1)
31933206

31943207
def smallestNonTraitBase(cls: Symbol): Symbol =
31953208
cls.asClass.baseClasses.find(!_.is(Trait)).get
31963209

3197-
if cls1 == defn.AnyKindClass || cls2 == defn.AnyKindClass then
3198-
// For some reason, A.derivesFrom(AnyKind) returns false, so we have to handle it specially
3199-
false
3200-
else if (eitherDerivesFromOther(cls1, cls2))
3210+
if (eitherDerivesFromOther(cls1, cls2))
32013211
false
32023212
else
32033213
if (cls1.is(Final) || cls2.is(Final))

compiler/test/dotc/pos-test-pickling.blacklist

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ mt-redux-norm.perspective.scala
6767
i18211.scala
6868
10867.scala
6969
named-tuples1.scala
70+
i20897.scala
7071

7172
# Opaque type
7273
i5720.scala
@@ -134,4 +135,3 @@ parsercombinators-new-syntax.scala
134135
hylolib-deferred-given
135136
hylolib-cb
136137
hylolib
137-

tests/pos/i20897.scala

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
object Test:
2+
type Disj[A, B] =
3+
A match
4+
case B => true
5+
case _ => false
6+
7+
def f(a: Disj[1 | Nothing, 2 | Nothing]): Unit = ()
8+
9+
val t = f(false)
10+
end Test

0 commit comments

Comments
 (0)