Skip to content

Commit e93b78f

Browse files
authored
Merge pull request #2111 from dotty-staging/fix/better-lub
Better type inference in harmonizeUnion
2 parents 215c134 + 00bbb97 commit e93b78f

File tree

4 files changed

+60
-25
lines changed

4 files changed

+60
-25
lines changed

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

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,6 @@ trait ConstraintHandling {
3535
/** If the constraint is frozen we cannot add new bounds to the constraint. */
3636
protected var frozenConstraint = false
3737

38-
protected var alwaysFluid = false
39-
40-
/** Perform `op` in a mode where all attempts to set `frozen` to true are ignored */
41-
def fluidly[T](op: => T): T = {
42-
val saved = alwaysFluid
43-
alwaysFluid = true
44-
try op finally alwaysFluid = saved
45-
}
46-
4738
/** If set, align arguments `S1`, `S2`when taking the glb
4839
* `T1 { X = S1 } & T2 { X = S2 }` of a constraint upper bound for some type parameter.
4940
* Aligning means computing `S1 =:= S2` which may change the current constraint.
@@ -156,16 +147,24 @@ trait ConstraintHandling {
156147
up.forall(addOneBound(_, lo, isUpper = false))
157148
}
158149

150+
151+
protected def isSubType(tp1: Type, tp2: Type, whenFrozen: Boolean): Boolean = {
152+
if (whenFrozen)
153+
isSubTypeWhenFrozen(tp1, tp2)
154+
else
155+
isSubType(tp1, tp2)
156+
}
157+
159158
final def isSubTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = {
160159
val saved = frozenConstraint
161-
frozenConstraint = !alwaysFluid
160+
frozenConstraint = true
162161
try isSubType(tp1, tp2)
163162
finally frozenConstraint = saved
164163
}
165164

166165
final def isSameTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = {
167166
val saved = frozenConstraint
168-
frozenConstraint = !alwaysFluid
167+
frozenConstraint = true
169168
try isSameType(tp1, tp2)
170169
finally frozenConstraint = saved
171170
}

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

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
384384
// So if the constraint is not yet frozen, we do the same comparison again
385385
// with a frozen constraint, which means that we get a chance to do the
386386
// widening in `fourthTry` before adding to the constraint.
387-
if (frozenConstraint || alwaysFluid) isSubType(tp1, bounds(tp2).lo)
387+
if (frozenConstraint) isSubType(tp1, bounds(tp2).lo)
388388
else isSubTypeWhenFrozen(tp1, tp2)
389389
alwaysTrue || {
390390
if (canConstrain(tp2)) addConstraint(tp2, tp1.widenExpr, fromBelow = true)
@@ -1143,19 +1143,20 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
11431143
((defn.AnyType: Type) /: tps)(glb)
11441144

11451145
/** The least upper bound of two types
1146+
* @param canConstrain If true, new constraints might be added to simplify the lub.
11461147
* @note We do not admit singleton types in or-types as lubs.
11471148
*/
1148-
def lub(tp1: Type, tp2: Type): Type = /*>|>*/ ctx.traceIndented(s"lub(${tp1.show}, ${tp2.show})", subtyping, show = true) /*<|<*/ {
1149+
def lub(tp1: Type, tp2: Type, canConstrain: Boolean = false): Type = /*>|>*/ ctx.traceIndented(s"lub(${tp1.show}, ${tp2.show}, canConstrain=$canConstrain)", subtyping, show = true) /*<|<*/ {
11491150
if (tp1 eq tp2) tp1
11501151
else if (!tp1.exists) tp1
11511152
else if (!tp2.exists) tp2
11521153
else if ((tp1 isRef AnyClass) || (tp2 isRef NothingClass)) tp1
11531154
else if ((tp2 isRef AnyClass) || (tp1 isRef NothingClass)) tp2
11541155
else {
1155-
val t1 = mergeIfSuper(tp1, tp2)
1156+
val t1 = mergeIfSuper(tp1, tp2, canConstrain)
11561157
if (t1.exists) t1
11571158
else {
1158-
val t2 = mergeIfSuper(tp2, tp1)
1159+
val t2 = mergeIfSuper(tp2, tp1, canConstrain)
11591160
if (t2.exists) t2
11601161
else {
11611162
val tp1w = tp1.widen
@@ -1169,7 +1170,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
11691170

11701171
/** The least upper bound of a list of types */
11711172
final def lub(tps: List[Type]): Type =
1172-
((defn.NothingType: Type) /: tps)(lub)
1173+
((defn.NothingType: Type) /: tps)(lub(_,_, canConstrain = false))
11731174

11741175
/** Merge `t1` into `tp2` if t1 is a subtype of some &-summand of tp2.
11751176
*/
@@ -1192,17 +1193,18 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
11921193
}
11931194

11941195
/** Merge `tp1` into `tp2` if tp1 is a supertype of some |-summand of tp2.
1196+
* @param canConstrain If true, new constraints might be added to make the merge possible.
11951197
*/
1196-
private def mergeIfSuper(tp1: Type, tp2: Type): Type =
1197-
if (isSubTypeWhenFrozen(tp2, tp1))
1198-
if (isSubTypeWhenFrozen(tp1, tp2)) tp2 else tp1 // keep existing type if possible
1198+
private def mergeIfSuper(tp1: Type, tp2: Type, canConstrain: Boolean): Type =
1199+
if (isSubType(tp2, tp1, whenFrozen = !canConstrain))
1200+
if (isSubType(tp1, tp2, whenFrozen = !canConstrain)) tp2 else tp1 // keep existing type if possible
11991201
else tp2 match {
12001202
case tp2 @ OrType(tp21, tp22) =>
1201-
val higher1 = mergeIfSuper(tp1, tp21)
1203+
val higher1 = mergeIfSuper(tp1, tp21, canConstrain)
12021204
if (higher1 eq tp21) tp2
12031205
else if (higher1.exists) higher1 | tp22
12041206
else {
1205-
val higher2 = mergeIfSuper(tp1, tp22)
1207+
val higher2 = mergeIfSuper(tp1, tp22, canConstrain)
12061208
if (higher2 eq tp22) tp2
12071209
else if (higher2.exists) tp21 | higher2
12081210
else NoType
@@ -1491,9 +1493,9 @@ class ExplainingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
14911493
super.hasMatchingMember(name, tp1, tp2)
14921494
}
14931495

1494-
override def lub(tp1: Type, tp2: Type) =
1495-
traceIndented(s"lub(${show(tp1)}, ${show(tp2)})") {
1496-
super.lub(tp1, tp2)
1496+
override def lub(tp1: Type, tp2: Type, canConstrain: Boolean = false) =
1497+
traceIndented(s"lub(${show(tp1)}, ${show(tp2)}, canConstrain=$canConstrain)") {
1498+
super.lub(tp1, tp2, canConstrain)
14971499
}
14981500

14991501
override def glb(tp1: Type, tp2: Type) =

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
282282
*/
283283
def harmonizeUnion(tp: Type): Type = tp match {
284284
case tp: OrType =>
285-
joinIfScala2(typeComparer.fluidly(tp.tp1 | tp.tp2))
285+
joinIfScala2(ctx.typeComparer.lub(harmonizeUnion(tp.tp1), harmonizeUnion(tp.tp2), canConstrain = true))
286286
case tp @ AndType(tp1, tp2) =>
287287
tp derived_& (harmonizeUnion(tp1), harmonizeUnion(tp2))
288288
case tp: RefinedType =>

tests/pos/constraining-lub.scala

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
class Inv[A](x: A)
2+
object Inv {
3+
def empty[A]: Inv[A] = new Inv(???)
4+
}
5+
6+
class Inv2[A](x: A)
7+
object Inv2 {
8+
def empty[A]: Inv2[A] = new Inv2(???)
9+
}
10+
11+
object Test {
12+
def inv(cond: Boolean) =
13+
if (cond)
14+
new Inv(1)
15+
else
16+
Inv.empty
17+
18+
val x: Inv[Int] = inv(true)
19+
20+
def inv2(cond: Boolean) =
21+
if (cond) {
22+
if (cond)
23+
new Inv(1)
24+
else
25+
Inv.empty
26+
} else {
27+
if (cond)
28+
new Inv2(1)
29+
else
30+
Inv2.empty
31+
}
32+
33+
val y: Inv[Int] | Inv2[Int] = inv2(true)
34+
}

0 commit comments

Comments
 (0)