From 15d54c9ccbef8a8857f857d1e3b4dec7223817e6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 24 Jun 2021 17:03:12 +0200 Subject: [PATCH] Balance And/Or types when forming lubs and glbs Fixes #12915 --- .../dotty/tools/dotc/core/TypeComparer.scala | 5 +- .../src/dotty/tools/dotc/core/Types.scala | 58 ++++++++++ tests/pos/i12915.scala | 105 ++++++++++++++++++ 3 files changed, 166 insertions(+), 2 deletions(-) create mode 100644 tests/pos/i12915.scala diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index c9d856034237..c276f9621acb 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -1571,6 +1571,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling * @see [[sufficientEither]] for the normal case */ protected def either(op1: => Boolean, op2: => Boolean): Boolean = + Stats.record("TypeComparer.either") if ctx.mode.is(Mode.GadtConstraintInference) || useNecessaryEither then necessaryEither(op1, op2) else @@ -2238,7 +2239,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling * opportunistically merged. */ final def andType(tp1: Type, tp2: Type, isErased: Boolean = ctx.erasedTypes): Type = - andTypeGen(tp1, tp2, AndType(_, _), isErased = isErased) + andTypeGen(tp1, tp2, AndType.balanced(_, _), isErased = isErased) final def simplifyAndTypeWithFallback(tp1: Type, tp2: Type, fallback: Type): Type = andTypeGen(tp1, tp2, (_, _) => fallback) @@ -2260,7 +2261,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling val t2 = distributeOr(tp2, tp1, isSoft) if (t2.exists) t2 else if (isErased) erasedLub(tp1, tp2) - else liftIfHK(tp1, tp2, OrType(_, _, soft = isSoft), _ | _, _ & _) + else liftIfHK(tp1, tp2, OrType.balanced(_, _, soft = isSoft), _ | _, _ & _) } } diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 80e0601a68ee..7e261dc7714a 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1716,6 +1716,14 @@ object Types { /** If this is a proto type, WildcardType, otherwise the type itself */ def dropIfProto: Type = this + /** If this is an AndType, the number of factors, 1 for all other types */ + def andFactorCount: Int = 1 + + /** If this is a OrType, the number of factors if that match `soft`, + * 1 for all other types. + */ + def orFactorCount(soft: Boolean): Int = 1 + // ----- Substitutions ----------------------------------------------------- /** Substitute all types that refer in their symbol attribute to @@ -3110,6 +3118,12 @@ object Types { myBaseClasses } + private var myFactorCount = 0 + override def andFactorCount = + if myFactorCount == 0 then + myFactorCount = tp1.andFactorCount + tp2.andFactorCount + myFactorCount + def derivedAndType(tp1: Type, tp2: Type)(using Context): Type = if ((tp1 eq this.tp1) && (tp2 eq this.tp2)) this else AndType.make(tp1, tp2, checkValid = true) @@ -3135,6 +3149,23 @@ object Types { unchecked(tp1, tp2) } + def balanced(tp1: Type, tp2: Type)(using Context): AndType = + tp1 match + case AndType(tp11, tp12) if tp1.andFactorCount > tp2.andFactorCount * 2 => + if tp11.andFactorCount < tp12.andFactorCount then + return apply(tp12, balanced(tp11, tp2)) + else + return apply(tp11, balanced(tp12, tp2)) + case _ => + tp2 match + case AndType(tp21, tp22) if tp2.andFactorCount > tp1.andFactorCount * 2 => + if tp22.andFactorCount < tp21.andFactorCount then + return apply(balanced(tp1, tp22), tp21) + else + return apply(balanced(tp1, tp21), tp22) + case _ => + apply(tp1, tp2) + def unchecked(tp1: Type, tp2: Type)(using Context): AndType = { assertUnerased() unique(new CachedAndType(tp1, tp2)) @@ -3181,6 +3212,14 @@ object Types { myBaseClasses } + private var myFactorCount = 0 + override def orFactorCount(soft: Boolean) = + if this.isSoft == soft then + if myFactorCount == 0 then + myFactorCount = tp1.orFactorCount(soft) + tp2.orFactorCount(soft) + myFactorCount + else 1 + assert(tp1.isValueTypeOrWildcard && tp2.isValueTypeOrWildcard, s"$tp1 $tp2") @@ -3238,10 +3277,29 @@ object Types { final class CachedOrType(tp1: Type, tp2: Type, override val isSoft: Boolean) extends OrType(tp1, tp2) object OrType { + def apply(tp1: Type, tp2: Type, soft: Boolean)(using Context): OrType = { assertUnerased() unique(new CachedOrType(tp1, tp2, soft)) } + + def balanced(tp1: Type, tp2: Type, soft: Boolean)(using Context): OrType = + tp1 match + case OrType(tp11, tp12) if tp1.orFactorCount(soft) > tp2.orFactorCount(soft) * 2 => + if tp11.orFactorCount(soft) < tp12.orFactorCount(soft) then + return apply(tp12, balanced(tp11, tp2, soft), soft) + else + return apply(tp11, balanced(tp12, tp2, soft), soft) + case _ => + tp2 match + case OrType(tp21, tp22) if tp2.orFactorCount(soft) > tp1.orFactorCount(soft) * 2 => + if tp22.orFactorCount(soft) < tp21.orFactorCount(soft) then + return apply(balanced(tp1, tp22, soft), tp21, soft) + else + return apply(balanced(tp1, tp21, soft), tp22, soft) + case _ => + apply(tp1, tp2, soft) + def make(tp1: Type, tp2: Type, soft: Boolean)(using Context): Type = if (tp1 eq tp2) tp1 else apply(tp1, tp2, soft) diff --git a/tests/pos/i12915.scala b/tests/pos/i12915.scala new file mode 100644 index 000000000000..657df2a5be8b --- /dev/null +++ b/tests/pos/i12915.scala @@ -0,0 +1,105 @@ +trait E[T] + +class X { + val e1: E[Int] = ??? + val e2: E[String] = ??? + val e3: E[List[Int]] = ??? + val e4: E[List[String]] = ??? + val e5: E[Double] = ??? + val e6: E[(String, String)] = ??? + val e7: E[(String, Int)] = ??? + val e8: E[(Int, List[String])] = ??? + val e9: E[Long] = ??? + val e10: E[(Long, Long)] = ??? + val e11: E[(Long, Long, Int)] = ??? + val e12: E[List[Long]] = ??? + val e13: E[List[Int]] = ??? + val e14: E[(String, String)] = ??? + val e15: E[(String, String, String)] = ??? + val e16: E[(Int, String)] = ??? + val e17: E[(String, Long, String)] = ??? + val e18: E[(Long, String, String)] = ??? + val e19: E[(String, String, Long)] = ??? + val e20: E[(String, Int, String)] = ??? + val e21: E[(Int, String, String)] = ??? + val e22: E[(String, String, Int)] = ??? + val e23: E[(String, String, Boolean)] = ??? + val e24: E[(Boolean, Boolean, String)] = ??? + val e25: E[(String, Int, Boolean)] = ??? + val e26: E[List[(String, String)]] = ??? + val e27: E[List[(Int, String)]] = ??? + val e28: E[List[(String, Int)]] = ??? + val e29: E[List[(Long, String)]] = ??? + val e30: E[List[(String, Long)]] = ??? + val e31: E[List[(Boolean, String)]] = ??? + val e32: E[List[(String, Boolean)]] = ??? + val e33: E[List[((String, String), String)]] = ??? + val e34: E[List[((String, Int), String)]] = ??? + val e35: E[List[((Long, String), String)]] = ??? + val e36: E[List[((Boolean, String), String)]] = ??? + val e37: E[List[((String, String), Int)]] = ??? + val e38: E[List[((String, String), (String, Int))]] = ??? + val e39: E[List[((Boolean, Long), (String, Int))]] = ??? + val e40: E[List[((Int, Long), (Boolean, Int))]] = ??? + val e41: E[List[((String, (Int, String)), (String, Int))]] = ??? + val e42: E[List[((Boolean, (Int, String)), (String, Int))]] = ??? + val e43: E[List[((String, (Int, String)), (Boolean, Int))]] = ??? + val e44: E[(Int, List[String], Long)] = ??? + val e45: E[(Int, List[Int], Long)] = ??? + val e46: E[(Int, List[Long], Long)] = ??? + val e47: E[(String, List[String], Long)] = ??? + val e48: E[(Int, List[String], Boolean)] = ??? + val e49: E[Char] = ??? + + val all = List( + e1, + e2, + e3, + e4, + e5, + e6, + e7, + e8, + e9, + e10, + e11, + e12, + e13, + e14, + e15, + e16, + e17, + e18, + e19, + e20, + e21, + e22, + e23, + e24, + e25, + e26, + e27, + e28, + e29, + e30, + e31, + e32, + e33, + e34, + e35, + e36, + e37, + e38, + e39, + e40, + e41, + e42, + e43, + e44, + e45, + e46, + e47, + e48, + e49 + ) +} \ No newline at end of file