diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 4f07801bb597..86adc48fa47f 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -738,7 +738,9 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] { return recur(AndType(tp11, tp121), tp2) && recur(AndType(tp11, tp122), tp2) case _ => } - either(recur(tp11, tp2), recur(tp12, tp2)) + val tp1norm = simplifyAndTypeWithFallback(tp11, tp12, tp1) + if (tp1 ne tp1norm) recur(tp1norm, tp2) + else either(recur(tp11, tp2), recur(tp12, tp2)) case tp1: MatchType => def compareMatch = tp2 match { case tp2: MatchType => @@ -1641,6 +1643,18 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] { NoType } + private[this] def andTypeGen(tp1: Type, tp2: Type, op: (Type, Type) => Type, + original: (Type, Type) => Type = _ & _, isErased: Boolean = ctx.erasedTypes): Type = trace(s"glb(${tp1.show}, ${tp2.show})", subtyping, show = true) { + val t1 = distributeAnd(tp1, tp2) + if (t1.exists) t1 + else { + val t2 = distributeAnd(tp2, tp1) + if (t2.exists) t2 + else if (isErased) erasedGlb(tp1, tp2, isJava = false) + else liftIfHK(tp1, tp2, op, original) + } + } + /** Form a normalized conjunction of two types. * Note: For certain types, `&` is distributed inside the type. This holds for * all types which are not value types (e.g. TypeBounds, ClassInfo, @@ -1659,16 +1673,11 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] { * * In these cases, a MergeError is thrown. */ - final def andType(tp1: Type, tp2: Type, isErased: Boolean = ctx.erasedTypes): Type = trace(s"glb(${tp1.show}, ${tp2.show})", subtyping, show = true) { - val t1 = distributeAnd(tp1, tp2) - if (t1.exists) t1 - else { - val t2 = distributeAnd(tp2, tp1) - if (t2.exists) t2 - else if (isErased) erasedGlb(tp1, tp2, isJava = false) - else liftIfHK(tp1, tp2, AndType(_, _), _ & _) - } - } + final def andType(tp1: Type, tp2: Type, isErased: Boolean = ctx.erasedTypes): Type = + andTypeGen(tp1, tp2, AndType(_, _), isErased = isErased) + + final def simplifyAndTypeWithFallback(tp1: Type, tp2: Type, fallback: Type): Type = + andTypeGen(tp1, tp2, (_, _) => fallback) /** Form a normalized conjunction of two types. * Note: For certain types, `|` is distributed inside the type. This holds for diff --git a/tests/neg/i3430.scala b/tests/pending/neg/i3430.scala similarity index 100% rename from tests/neg/i3430.scala rename to tests/pending/neg/i3430.scala diff --git a/tests/pos/i5980.scala b/tests/pos/i5980.scala new file mode 100644 index 000000000000..60e744bbbfca --- /dev/null +++ b/tests/pos/i5980.scala @@ -0,0 +1,34 @@ +trait A +trait B + +trait Covariant[F[+_]] { + trait G[+X] + + def fx: F[A & B] = fy + def fy: F[A] & F[B] = fx + + def gx: G[A & B] = gy + def gy: G[A] & G[B] = gx +} + +trait Contravariant[F[-_]] { + trait G[-X] + + def fx: F[A | B] = fy + def fy: F[A] & F[B] = fx + + def gx: G[A | B] = gy + def gy: G[A] & G[B] = gx +} + +trait LiskovViolation[F[+_]] { + trait A { def children: F[A] } + trait B { def children: F[B] } + trait C extends A with B { def children: F[A] & F[B] = ??? } + + def fc1: C = new C {} + def fc2: A & B = fc1 + + def fy1: F[A & B] = fc1.children + def fy2: F[A & B] = fc2.children +} diff --git a/tests/pos/infinite-loop-potential.scala b/tests/pos/infinite-loop-potential.scala new file mode 100644 index 000000000000..89a2bdd2a9a2 --- /dev/null +++ b/tests/pos/infinite-loop-potential.scala @@ -0,0 +1,10 @@ +object InfiniteSubtypingLoopPossibility { + trait A[X] + trait B extends A[B] + trait Min[+S <: B with A[S]] + + def c: Any = ??? + c match { + case pc: Min[_] => + } +}