Skip to content

Commit

Permalink
Balance And/Or types when forming lubs and glbs
Browse files Browse the repository at this point in the history
  • Loading branch information
odersky committed Jun 24, 2021
1 parent 2ccf681 commit 15d54c9
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 2 deletions.
5 changes: 3 additions & 2 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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), _ | _, _ & _)
}
}

Expand Down
58 changes: 58 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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))
Expand Down Expand Up @@ -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")

Expand Down Expand Up @@ -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)
Expand Down
105 changes: 105 additions & 0 deletions tests/pos/i12915.scala
Original file line number Diff line number Diff line change
@@ -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
)
}

0 comments on commit 15d54c9

Please sign in to comment.