Skip to content

Commit 6dba668

Browse files
committed
Make atoms computation talk about empty sets correctly
Previously we merged the atoms info of a type with an empty atoms set and a type with no atoms, since we identified the empty set with "does not have atoms". But that cannot type the inequality 0 & 1 <: 0 The new scheme distinguishes the two states by using an Option[Set[Type]] in the computations.
1 parent 5058dc8 commit 6dba668

File tree

3 files changed

+56
-45
lines changed

3 files changed

+56
-45
lines changed

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

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -440,18 +440,20 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w
440440
(tp1.widenSingletons ne tp1) &&
441441
recur(tp1.widenSingletons, tp2)
442442

443-
if (tp2.atoms().nonEmpty && canCompare(tp2.atoms()))
444-
val atoms1 = tp1.atoms(widenOK = true)
445-
atoms1.nonEmpty && atoms1.subsetOf(tp2.atoms())
446-
else
447-
widenOK
448-
|| joinOK
449-
|| recur(tp11, tp2) && recur(tp12, tp2)
450-
|| containsAnd(tp1) && recur(tp1.join, tp2)
451-
// An & on the left side loses information. Compensate by also trying the join.
452-
// This is less ad-hoc than it looks since we produce joins in type inference,
453-
// and then need to check that they are indeed supertypes of the original types
454-
// under -Ycheck. Test case is i7965.scala.
443+
tp2.atoms() match
444+
case Some(ts2) if canCompare(ts2) =>
445+
tp1.atoms(widenOK = true) match
446+
case Some(ts1) => ts1.subsetOf(ts2)
447+
case none => false
448+
case _ =>
449+
widenOK
450+
|| joinOK
451+
|| recur(tp11, tp2) && recur(tp12, tp2)
452+
|| containsAnd(tp1) && recur(tp1.join, tp2)
453+
// An & on the left side loses information. Compensate by also trying the join.
454+
// This is less ad-hoc than it looks since we produce joins in type inference,
455+
// and then need to check that they are indeed supertypes of the original types
456+
// under -Ycheck. Test case is i7965.scala.
455457

456458
case tp1: MatchType =>
457459
val reduced = tp1.reduced
@@ -613,9 +615,13 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w
613615
}
614616
compareTypeLambda
615617
case OrType(tp21, tp22) =>
616-
if (tp2.atoms().nonEmpty && canCompare(tp2.atoms()))
617-
val atoms1 = tp1.atoms(widenOK = true)
618-
return atoms1.nonEmpty && atoms1.subsetOf(tp2.atoms()) || isSubType(tp1, NothingType)
618+
tp2.atoms() match
619+
case Some(ts2) if canCompare(ts2) =>
620+
val atomsFit = tp1.atoms(widenOK = true) match
621+
case Some(ts1) => ts1.subsetOf(ts2)
622+
case none => false
623+
return atomsFit || isSubType(tp1, NothingType)
624+
case none =>
619625

620626
// The next clause handles a situation like the one encountered in i2745.scala.
621627
// We have:
@@ -1783,15 +1789,15 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w
17831789
else if tp2.isAny && !tp1.isLambdaSub || tp2.isAnyKind || tp1.isRef(NothingClass) then tp2
17841790
else
17851791
def mergedLub(tp1: Type, tp2: Type): Type = {
1786-
val atoms1 = tp1.atoms(widenOK = true)
1787-
if (atoms1.nonEmpty && !widenInUnions) {
1788-
val atoms2 = tp2.atoms(widenOK = true)
1789-
if (atoms2.nonEmpty) {
1790-
if (atoms1.subsetOf(atoms2)) return tp2
1791-
if (atoms2.subsetOf(atoms1)) return tp1
1792-
if ((atoms1 & atoms2).isEmpty) return orType(tp1, tp2)
1793-
}
1794-
}
1792+
tp1.atoms(widenOK = true) match
1793+
case Some(ts1) if !widenInUnions =>
1794+
tp2.atoms(widenOK = true) match
1795+
case Some(ts2) =>
1796+
if ts1.subsetOf(ts2) then return tp2
1797+
if ts2.subsetOf(ts1) then return tp1
1798+
if (ts1 & ts2).isEmpty then return orType(tp1, tp2)
1799+
case none =>
1800+
case none =>
17951801
val t1 = mergeIfSuper(tp1, tp2, canConstrain)
17961802
if (t1.exists) return t1
17971803

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

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1173,7 +1173,8 @@ object Types {
11731173
* @param widenOK If type proxies that are upperbounded by types with atoms
11741174
* have the same atoms.
11751175
*/
1176-
def atoms(widenOK: Boolean = false)(implicit ctx: Context): Set[Type] = dealias match {
1176+
def atoms(widenOK: Boolean = false)(implicit ctx: Context): Option[Set[Type]] =
1177+
dealias match
11771178
case tp: SingletonType =>
11781179
def normalize(tp: Type): Type = tp match {
11791180
case tp: SingletonType =>
@@ -1187,16 +1188,17 @@ object Types {
11871188
}
11881189
case _ => tp
11891190
}
1190-
val underlyingAtoms = tp.underlying.atoms(widenOK)
1191-
if underlyingAtoms.isEmpty && tp.isStable then Set.empty + normalize(tp)
1192-
else if underlyingAtoms.size == 1 || widenOK then underlyingAtoms
1193-
else Set.empty
1191+
tp.underlying.atoms(widenOK) match
1192+
case None if tp.isStable => Some(Set.empty + normalize(tp))
1193+
case s @ Some(ts) if ts.size == 1 || widenOK => s
1194+
case _ => None
11941195
case tp: ExprType => tp.resType.atoms(widenOK)
11951196
case tp: OrType => tp.atoms(widenOK) // `atoms` overridden in OrType
1196-
case tp: AndType => tp.tp1.atoms(widenOK) & tp.tp2.atoms(widenOK)
1197+
case tp: AndType =>
1198+
for ts1 <- tp.tp1.atoms(widenOK); ts2 <- tp.tp2.atoms(widenOK)
1199+
yield ts1 & ts2
11971200
case tp: TypeProxy if widenOK => tp.underlying.atoms(widenOK)
1198-
case _ => Set.empty
1199-
}
1201+
case _ => None
12001202

12011203
private def dealias1(keep: AnnotatedType => Context => Boolean)(implicit ctx: Context): Type = this match {
12021204
case tp: TypeRef =>
@@ -2920,28 +2922,26 @@ object Types {
29202922
}
29212923

29222924
private var atomsRunId: RunId = NoRunId
2923-
private var myAtoms: Set[Type] = _
2924-
private var myWidenedAtoms: Set[Type] = _
2925+
private var myAtoms: Option[Set[Type]] = _
2926+
private var myWidenedAtoms: Option[Set[Type]] = _
29252927
private var myWidened: Type = _
29262928

29272929
private def ensureAtomsComputed()(implicit ctx: Context): Unit =
2928-
if (atomsRunId != ctx.runId) {
2929-
val atoms1 = tp1.atoms()
2930-
val atoms2 = tp2.atoms()
2931-
myAtoms = if (atoms1.nonEmpty && atoms2.nonEmpty) atoms1 | atoms2 else Set.empty
2932-
val widenedAtoms1 = tp1.atoms(widenOK = true)
2933-
val widenedAtoms2 = tp2.atoms(widenOK = true)
2934-
myWidenedAtoms = if (widenedAtoms1.nonEmpty && widenedAtoms2.nonEmpty) widenedAtoms1 | widenedAtoms2 else Set.empty
2930+
if atomsRunId != ctx.runId then
2931+
myAtoms =
2932+
for ts1 <- tp1.atoms(); ts2 <- tp2.atoms()
2933+
yield ts1 | ts2
2934+
myWidenedAtoms =
2935+
for ts1 <- tp1.atoms(widenOK = true); ts2 <- tp2.atoms(widenOK = true)
2936+
yield ts1 | ts2
29352937
val tp1w = tp1.widenSingletons
29362938
val tp2w = tp2.widenSingletons
29372939
myWidened = if ((tp1 eq tp1w) && (tp2 eq tp2w)) this else tp1w | tp2w
29382940
atomsRunId = ctx.runId
2939-
}
29402941

2941-
override def atoms(widenOK: Boolean)(implicit ctx: Context): Set[Type] = {
2942+
override def atoms(widenOK: Boolean)(implicit ctx: Context): Option[Set[Type]] =
29422943
ensureAtomsComputed()
29432944
if widenOK then myAtoms else myWidenedAtoms
2944-
}
29452945

29462946
override def widenSingletons(implicit ctx: Context): Type = {
29472947
ensureAtomsComputed()

tests/pos/i8128.scala

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
object Test {
2-
def id: (x: 1 | 0) => x.type = ???
2+
def id: (x: 1 | 0) => x.type = ??? // x => x does not work yet
33
id(0): 0 // ok
44

55
def id2: Function1[1 | 0, 1 | 0] {
@@ -11,4 +11,9 @@ object Test {
1111
def apply(x: 1 | 0): x.type
1212
} = ???
1313
id3(0): 0 // ok
14+
15+
var x: 0 = 0
16+
var y: (1 & 0) | 0 = 0
17+
x = y
18+
y = x
1419
}

0 commit comments

Comments
 (0)