aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala21
-rw-r--r--compiler/src/dotty/tools/dotc/core/TypeComparer.scala28
-rw-r--r--compiler/src/dotty/tools/dotc/core/TypeOps.scala2
-rw-r--r--tests/pos/constraining-lub.scala34
4 files changed, 60 insertions, 25 deletions
diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala
index 2a1f4ee6e..a12936c58 100644
--- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala
+++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala
@@ -35,15 +35,6 @@ trait ConstraintHandling {
/** If the constraint is frozen we cannot add new bounds to the constraint. */
protected var frozenConstraint = false
- protected var alwaysFluid = false
-
- /** Perform `op` in a mode where all attempts to set `frozen` to true are ignored */
- def fluidly[T](op: => T): T = {
- val saved = alwaysFluid
- alwaysFluid = true
- try op finally alwaysFluid = saved
- }
-
/** If set, align arguments `S1`, `S2`when taking the glb
* `T1 { X = S1 } & T2 { X = S2 }` of a constraint upper bound for some type parameter.
* Aligning means computing `S1 =:= S2` which may change the current constraint.
@@ -156,16 +147,24 @@ trait ConstraintHandling {
up.forall(addOneBound(_, lo, isUpper = false))
}
+
+ protected def isSubType(tp1: Type, tp2: Type, whenFrozen: Boolean): Boolean = {
+ if (whenFrozen)
+ isSubTypeWhenFrozen(tp1, tp2)
+ else
+ isSubType(tp1, tp2)
+ }
+
final def isSubTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = {
val saved = frozenConstraint
- frozenConstraint = !alwaysFluid
+ frozenConstraint = true
try isSubType(tp1, tp2)
finally frozenConstraint = saved
}
final def isSameTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = {
val saved = frozenConstraint
- frozenConstraint = !alwaysFluid
+ frozenConstraint = true
try isSameType(tp1, tp2)
finally frozenConstraint = saved
}
diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala
index 21a12dbb7..57dde3288 100644
--- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala
+++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala
@@ -384,7 +384,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
// So if the constraint is not yet frozen, we do the same comparison again
// with a frozen constraint, which means that we get a chance to do the
// widening in `fourthTry` before adding to the constraint.
- if (frozenConstraint || alwaysFluid) isSubType(tp1, bounds(tp2).lo)
+ if (frozenConstraint) isSubType(tp1, bounds(tp2).lo)
else isSubTypeWhenFrozen(tp1, tp2)
alwaysTrue || {
if (canConstrain(tp2)) addConstraint(tp2, tp1.widenExpr, fromBelow = true)
@@ -1143,19 +1143,20 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
((defn.AnyType: Type) /: tps)(glb)
/** The least upper bound of two types
+ * @param canConstrain If true, new constraints might be added to simplify the lub.
* @note We do not admit singleton types in or-types as lubs.
*/
- def lub(tp1: Type, tp2: Type): Type = /*>|>*/ ctx.traceIndented(s"lub(${tp1.show}, ${tp2.show})", subtyping, show = true) /*<|<*/ {
+ def lub(tp1: Type, tp2: Type, canConstrain: Boolean = false): Type = /*>|>*/ ctx.traceIndented(s"lub(${tp1.show}, ${tp2.show}, canConstrain=$canConstrain)", subtyping, show = true) /*<|<*/ {
if (tp1 eq tp2) tp1
else if (!tp1.exists) tp1
else if (!tp2.exists) tp2
else if ((tp1 isRef AnyClass) || (tp2 isRef NothingClass)) tp1
else if ((tp2 isRef AnyClass) || (tp1 isRef NothingClass)) tp2
else {
- val t1 = mergeIfSuper(tp1, tp2)
+ val t1 = mergeIfSuper(tp1, tp2, canConstrain)
if (t1.exists) t1
else {
- val t2 = mergeIfSuper(tp2, tp1)
+ val t2 = mergeIfSuper(tp2, tp1, canConstrain)
if (t2.exists) t2
else {
val tp1w = tp1.widen
@@ -1169,7 +1170,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
/** The least upper bound of a list of types */
final def lub(tps: List[Type]): Type =
- ((defn.NothingType: Type) /: tps)(lub)
+ ((defn.NothingType: Type) /: tps)(lub(_,_, canConstrain = false))
/** Merge `t1` into `tp2` if t1 is a subtype of some &-summand of tp2.
*/
@@ -1192,17 +1193,18 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
}
/** Merge `tp1` into `tp2` if tp1 is a supertype of some |-summand of tp2.
+ * @param canConstrain If true, new constraints might be added to make the merge possible.
*/
- private def mergeIfSuper(tp1: Type, tp2: Type): Type =
- if (isSubTypeWhenFrozen(tp2, tp1))
- if (isSubTypeWhenFrozen(tp1, tp2)) tp2 else tp1 // keep existing type if possible
+ private def mergeIfSuper(tp1: Type, tp2: Type, canConstrain: Boolean): Type =
+ if (isSubType(tp2, tp1, whenFrozen = !canConstrain))
+ if (isSubType(tp1, tp2, whenFrozen = !canConstrain)) tp2 else tp1 // keep existing type if possible
else tp2 match {
case tp2 @ OrType(tp21, tp22) =>
- val higher1 = mergeIfSuper(tp1, tp21)
+ val higher1 = mergeIfSuper(tp1, tp21, canConstrain)
if (higher1 eq tp21) tp2
else if (higher1.exists) higher1 | tp22
else {
- val higher2 = mergeIfSuper(tp1, tp22)
+ val higher2 = mergeIfSuper(tp1, tp22, canConstrain)
if (higher2 eq tp22) tp2
else if (higher2.exists) tp21 | higher2
else NoType
@@ -1491,9 +1493,9 @@ class ExplainingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
super.hasMatchingMember(name, tp1, tp2)
}
- override def lub(tp1: Type, tp2: Type) =
- traceIndented(s"lub(${show(tp1)}, ${show(tp2)})") {
- super.lub(tp1, tp2)
+ override def lub(tp1: Type, tp2: Type, canConstrain: Boolean = false) =
+ traceIndented(s"lub(${show(tp1)}, ${show(tp2)}, canConstrain=$canConstrain)") {
+ super.lub(tp1, tp2, canConstrain)
}
override def glb(tp1: Type, tp2: Type) =
diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala
index 6c40794e3..3d2906320 100644
--- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala
+++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala
@@ -282,7 +282,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
*/
def harmonizeUnion(tp: Type): Type = tp match {
case tp: OrType =>
- joinIfScala2(typeComparer.fluidly(tp.tp1 | tp.tp2))
+ joinIfScala2(ctx.typeComparer.lub(harmonizeUnion(tp.tp1), harmonizeUnion(tp.tp2), canConstrain = true))
case tp @ AndType(tp1, tp2) =>
tp derived_& (harmonizeUnion(tp1), harmonizeUnion(tp2))
case tp: RefinedType =>
diff --git a/tests/pos/constraining-lub.scala b/tests/pos/constraining-lub.scala
new file mode 100644
index 000000000..80da2ec86
--- /dev/null
+++ b/tests/pos/constraining-lub.scala
@@ -0,0 +1,34 @@
+class Inv[A](x: A)
+object Inv {
+ def empty[A]: Inv[A] = new Inv(???)
+}
+
+class Inv2[A](x: A)
+object Inv2 {
+ def empty[A]: Inv2[A] = new Inv2(???)
+}
+
+object Test {
+ def inv(cond: Boolean) =
+ if (cond)
+ new Inv(1)
+ else
+ Inv.empty
+
+ val x: Inv[Int] = inv(true)
+
+ def inv2(cond: Boolean) =
+ if (cond) {
+ if (cond)
+ new Inv(1)
+ else
+ Inv.empty
+ } else {
+ if (cond)
+ new Inv2(1)
+ else
+ Inv2.empty
+ }
+
+ val y: Inv[Int] | Inv2[Int] = inv2(true)
+}