From 00bbb97a5bf6ea32de7ca92cb1a7b32280fa4e9e Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Thu, 16 Mar 2017 20:27:09 +0100 Subject: Better type inference in harmonizeUnion Before this commit, the added testcase failed because the type of `inv` was inferred to be `Inv[Any]` instead of `Inv[Int]`. The situation looks like this: def inv(cond: Boolean) = if (cond) new Inv(1) // : Inv[A] where A >: Int else Inv.empty // : Inv[A'] where A' unconstrained // : Inv[A] | Inv[A'] To get the type of `inv`, we call `harmonizeUnion` which will take the lub of `Inv[A]` and `Inv[A']`, eventually this mean that we do: A' <:< A But since `harmonizeUnion` uses `fluidly`, this does not result in `A'` getting constrained to be a subtype of `A`, instead we constrain `A` to the upper bound of `A'`: Any <:< A We use `fluidly` to avoid creating OrTypes in `lub`, but it turns out that there is a less aggressive solution: `lub` calls `mergeIfSuper` which then calls `isSubTypeWhenFrozen`, if we just make these subtype calls non-frozen, we can achieve what we want. This is what the new `lub` parameter `canConstrain` allows. --- .../dotty/tools/dotc/core/ConstraintHandling.scala | 21 ++++++++-------- .../src/dotty/tools/dotc/core/TypeComparer.scala | 28 ++++++++++++---------- compiler/src/dotty/tools/dotc/core/TypeOps.scala | 2 +- 3 files changed, 26 insertions(+), 25 deletions(-) (limited to 'compiler/src/dotty/tools/dotc/core') 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 => -- cgit v1.2.3