Skip to content

Commit dbf2866

Browse files
committed
Improve efficiency of subtype tests involving unions of singletons
Since we do not widen singletons in unions, it is now easy to get large union types consisting of singletons. We need to specialize the code dealing with them in order not to get deep subtype recursions and drastic compilation speed slowdowns.
1 parent 4c5988f commit dbf2866

File tree

3 files changed

+76
-5
lines changed

3 files changed

+76
-5
lines changed

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,15 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
418418
case _ =>
419419
false
420420
}
421-
joinOK || recur(tp11, tp2) && recur(tp12, tp2)
421+
def widenOK =
422+
(tp2.widenSingletons eq tp2) &&
423+
(tp1.widenSingletons ne tp1) &&
424+
recur(tp1.widenSingletons, tp2)
425+
426+
if (tp2.atoms.nonEmpty)
427+
tp1.atoms.nonEmpty && tp1.atoms.subsetOf(tp2.atoms)
428+
else
429+
widenOK || joinOK || recur(tp11, tp2) && recur(tp12, tp2)
422430
case tp1: MatchType =>
423431
val reduced = tp1.reduced
424432
if (reduced.exists) recur(reduced, tp2) else thirdTry
@@ -573,6 +581,10 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
573581
}
574582
compareTypeLambda
575583
case OrType(tp21, tp22) =>
584+
if (tp2.atoms.nonEmpty)
585+
return tp1.atoms.nonEmpty && tp1.atoms.subsetOf(tp2.atoms) ||
586+
tp1.isRef(NothingClass)
587+
576588
// The next clause handles a situation like the one encountered in i2745.scala.
577589
// We have:
578590
//

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

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,6 +1062,40 @@ object Types {
10621062
tp
10631063
}
10641064

1065+
/** Widen all top-level singletons reachable by dealising
1066+
* and going to the operands of & and |.
1067+
* Overridden and cached in OrType.
1068+
*/
1069+
def widenSingletons(implicit ctx: Context): Type = dealias match {
1070+
case tp: SingletonType =>
1071+
tp.widen
1072+
case tp: OrType =>
1073+
val tp1w = tp.widenSingletons
1074+
if (tp1w eq tp) this else tp1w
1075+
case tp: AndType =>
1076+
val tp1w = tp.tp1.widenSingletons
1077+
val tp2w = tp.tp2.widenSingletons
1078+
if ((tp.tp1 eq tp1w) && (tp.tp2 eq tp2w)) this else tp1w & tp2w
1079+
case _ =>
1080+
this
1081+
}
1082+
1083+
/** If this type is an alias of a disjunction of stable singleton types,
1084+
* these types as a set, otherwise the empty set.
1085+
* Overridden and cached in OrType.
1086+
*/
1087+
def atoms(implicit ctx: Context): Set[Type] = dealias match {
1088+
case tp: SingletonType if tp.isStable =>
1089+
def normalize(tp: SingletonType): SingletonType = tp.underlying match {
1090+
case tp1: SingletonType => normalize(tp1)
1091+
case _ => tp
1092+
}
1093+
Set.empty + normalize(tp)
1094+
case tp: OrType => tp.atoms
1095+
case tp: AndType => tp.tp1.atoms & tp.tp2.atoms
1096+
case _ => Set.empty
1097+
}
1098+
10651099
private def dealias1(keep: AnnotatedType => Context => Boolean)(implicit ctx: Context): Type = this match {
10661100
case tp: TypeRef =>
10671101
if (tp.symbol.isClass) tp
@@ -2777,6 +2811,31 @@ object Types {
27772811
myJoin
27782812
}
27792813

2814+
private[this] var atomsRunId: RunId = NoRunId
2815+
private[this] var myAtoms: Set[Type] = _
2816+
private[this] var myWidened: Type = _
2817+
2818+
private def ensureAtomsComputed()(implicit ctx: Context): Unit =
2819+
if (atomsRunId != ctx.runId) {
2820+
val atoms1 = tp1.atoms
2821+
val atoms2 = tp2.atoms
2822+
myAtoms = if (atoms1.nonEmpty && atoms2.nonEmpty) atoms1 | atoms2 else Set.empty
2823+
val tp1w = tp1.widenSingletons
2824+
val tp2w = tp2.widenSingletons
2825+
myWidened = if ((tp1 eq tp1w) && (tp2 eq tp2w)) this else tp1w | tp2w
2826+
atomsRunId = ctx.runId
2827+
}
2828+
2829+
override def atoms(implicit ctx: Context): Set[Type] = {
2830+
ensureAtomsComputed()
2831+
myAtoms
2832+
}
2833+
2834+
override def widenSingletons(implicit ctx: Context): Type = {
2835+
ensureAtomsComputed()
2836+
myWidened
2837+
}
2838+
27802839
def derivedOrType(tp1: Type, tp2: Type)(implicit ctx: Context): Type =
27812840
if ((tp1 eq this.tp1) && (tp2 eq this.tp2)) this
27822841
else OrType.make(tp1, tp2)

tests/neg/singletonOrs.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
object Test {
2-
def a: 1 | 2 = 1 // error // error
3-
def b: 3 | 4 = a // error // error
4-
def c: 1 | 2 = 1 // error // error
5-
def d: 1 = a
2+
def a: 1 | 2 = 1
3+
def b: 3 | 4 = a // error
4+
def c: 1 | 2 = 1
5+
def d: 1 = a // error
66
}

0 commit comments

Comments
 (0)