Skip to content

Commit

Permalink
Fix #5980: Distribute type parameters of covariant type constructors
Browse files Browse the repository at this point in the history
When performing subtyping comparisons of the `F[A] & F[B] <:< C` kind,
if `F` is a type parameter (e.g. as in `trait Foo[F[+_]]`), the compiler
decides the relationship to be true if either `F[A] <: C` or `F[B] <: C`.
However, in case of covariant `F[+_]`, it is possible to distribute the
type arguments when performing the `&` operation: `F[A] & F[B] ~> F[A & B]`.
Hence there is one more case to be considered when checking `<:<`
relationship. This commit adds such a check to the `<:<` logic. Now,
`F[A] & F[B] <:< C` is true (for covariant `F`) if either of the following
is true:
- F[A] <: C
- F[B] <: C
- F[A & B] <: C
  • Loading branch information
anatoliykmetyuk committed Mar 10, 2019
1 parent 51d8cab commit 7cdab92
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 11 deletions.
31 changes: 20 additions & 11 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 =>
Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand Down
File renamed without changes.
34 changes: 34 additions & 0 deletions tests/pos/i5980.scala
Original file line number Diff line number Diff line change
@@ -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
}
10 changes: 10 additions & 0 deletions tests/pos/infinite-loop-potential.scala
Original file line number Diff line number Diff line change
@@ -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[_] =>
}
}

0 comments on commit 7cdab92

Please sign in to comment.