Skip to content

Commit 2baefd5

Browse files
sjrdWojciechMazur
authored andcommitted
Fix #20897: Make Nothing ⋔ Nothing, as per spec.
`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]
1 parent 30cff00 commit 2baefd5

File tree

3 files changed

+28
-8
lines changed

3 files changed

+28
-8
lines changed

Diff for: compiler/src/dotty/tools/dotc/core/TypeComparer.scala

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

2932+
/* Nothing is not a class type in the spec but dotc represents it as if it were one.
2933+
* Get it out of the way early to avoid mistakes (see for example #20897).
2934+
* Nothing ⋔ T and T ⋔ Nothing for all T.
2935+
*/
2936+
case (tp1, tp2) if tp1.isExactlyNothing || tp2.isExactlyNothing =>
2937+
true
2938+
29322939
// Cases where there is an intersection or union on the right
29332940
case (tp1, tp2: OrType) =>
29342941
provablyDisjoint(tp1, tp2.tp1, pending) && provablyDisjoint(tp1, tp2.tp2, pending)
@@ -2941,14 +2948,21 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
29412948
case (tp1: AndType, tp2) =>
29422949
provablyDisjoint(tp1.tp1, tp2, pending) || provablyDisjoint(tp1.tp2, tp2, pending)
29432950

2951+
/* Handle AnyKind now for the same reason as Nothing above: it is not a real class type.
2952+
* Other than the rules with Nothing, unions and intersections, there is structurally
2953+
* no rule such that AnyKind ⋔ T or T ⋔ AnyKind for any T.
2954+
*/
2955+
case (tp1, tp2) if tp1.isDirectRef(AnyKindClass) || tp2.isDirectRef(AnyKindClass) =>
2956+
false
2957+
29442958
// Cases involving type lambdas
29452959
case (tp1: HKTypeLambda, tp2: HKTypeLambda) =>
29462960
tp1.paramNames.sizeCompare(tp2.paramNames) != 0
29472961
|| provablyDisjoint(tp1.resultType, tp2.resultType, pending)
29482962
case (tp1: HKTypeLambda, tp2) =>
2949-
!tp2.isDirectRef(defn.AnyKindClass)
2963+
true
29502964
case (tp1, tp2: HKTypeLambda) =>
2951-
!tp1.isDirectRef(defn.AnyKindClass)
2965+
true
29522966

29532967
/* Cases where both are unique values (enum cases or constant types)
29542968
*
@@ -3052,17 +3066,13 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
30523066
else child
30533067
}.filter(child => child.exists && child != cls)
30543068

3055-
// TODO? Special-case for Nothing and Null? We probably need Nothing/Null disjoint from Nothing/Null
30563069
def eitherDerivesFromOther(cls1: Symbol, cls2: Symbol): Boolean =
30573070
cls1.derivesFrom(cls2) || cls2.derivesFrom(cls1)
30583071

30593072
def smallestNonTraitBase(cls: Symbol): Symbol =
30603073
cls.asClass.baseClasses.find(!_.is(Trait)).get
30613074

3062-
if cls1 == defn.AnyKindClass || cls2 == defn.AnyKindClass then
3063-
// For some reason, A.derivesFrom(AnyKind) returns false, so we have to handle it specially
3064-
false
3065-
else if (eitherDerivesFromOther(cls1, cls2))
3075+
if (eitherDerivesFromOther(cls1, cls2))
30663076
false
30673077
else
30683078
if (cls1.is(Final) || cls2.is(Final))

Diff for: 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-

Diff for: 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)