Skip to content

Commit 455aa50

Browse files
committed
Fix spurious subtype check pruning when both sides have unions
1 parent 967aefc commit 455aa50

File tree

2 files changed

+76
-14
lines changed

2 files changed

+76
-14
lines changed

compiler/src/dotty/tools/dotc/core/TypeComparer.scala

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1479,9 +1479,39 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
14791479

14801480
/** Like tp1 <:< tp2, but returns false immediately if we know that
14811481
* the case was covered previously during subtyping.
1482+
*
1483+
* A type has been covered previously in subtype checking if it
1484+
* is some combination of TypeRefs that point to classes, where the
1485+
* combiners are AppliedTypes, RefinedTypes, RecTypes, And/Or-Types or AnnotatedTypes.
1486+
*
1487+
* The exception is that if both sides contain OrTypes, the check hasn't been covered.
1488+
* See #17465.
14821489
*/
14831490
def isNewSubType(tp1: Type): Boolean =
1484-
if (isCovered(tp1) && isCovered(tp2))
1491+
1492+
def isCovered(tp: Type): (Boolean, Boolean) =
1493+
var containsOr: Boolean = false
1494+
@annotation.tailrec def recur(todos: List[Type]): Boolean = todos match
1495+
case tp :: todos =>
1496+
tp.dealiasKeepRefiningAnnots.stripTypeVar match
1497+
case tp: TypeRef =>
1498+
if tp.symbol.isClass && tp.symbol != NothingClass && tp.symbol != NullClass then recur(todos)
1499+
else false
1500+
case tp: AppliedType => recur(tp.tycon :: todos)
1501+
case tp: RefinedOrRecType => recur(tp.parent :: todos)
1502+
case tp: AndType => recur(tp.tp1 :: tp.tp2 :: todos)
1503+
case tp: OrType =>
1504+
containsOr = true
1505+
recur(tp.tp1 :: tp.tp2 :: todos)
1506+
case _ => false
1507+
case Nil => true
1508+
val result = recur(tp :: Nil)
1509+
(result, containsOr)
1510+
1511+
val (covered1, hasOr1) = isCovered(tp1)
1512+
val (covered2, hasOr2) = isCovered(tp2)
1513+
1514+
if covered1 && covered2 && !(hasOr1 && hasOr2) then
14851515
//println(s"useless subtype: $tp1 <:< $tp2")
14861516
false
14871517
else isSubType(tp1, tp2, approx.addLow)
@@ -2091,19 +2121,6 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
20912121
tp1.parent.asInstanceOf[RefinedType],
20922122
tp2.parent.asInstanceOf[RefinedType], limit))
20932123

2094-
/** A type has been covered previously in subtype checking if it
2095-
* is some combination of TypeRefs that point to classes, where the
2096-
* combiners are AppliedTypes, RefinedTypes, RecTypes, And/Or-Types or AnnotatedTypes.
2097-
*/
2098-
private def isCovered(tp: Type): Boolean = tp.dealiasKeepRefiningAnnots.stripTypeVar match {
2099-
case tp: TypeRef => tp.symbol.isClass && tp.symbol != NothingClass && tp.symbol != NullClass
2100-
case tp: AppliedType => isCovered(tp.tycon)
2101-
case tp: RefinedOrRecType => isCovered(tp.parent)
2102-
case tp: AndType => isCovered(tp.tp1) && isCovered(tp.tp2)
2103-
case tp: OrType => isCovered(tp.tp1) && isCovered(tp.tp2)
2104-
case _ => false
2105-
}
2106-
21072124
/** Defer constraining type variables when compared against prototypes */
21082125
def isMatchedByProto(proto: ProtoType, tp: Type): Boolean = tp.stripTypeVar match {
21092126
case tp: TypeParamRef if constraint contains tp => true

tests/pos/i17465.scala

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
def test1[A, B]: Unit = {
2+
def f[T](x: T{ def *(y: Int): T }): T = ???
3+
def test = f[scala.collection.StringOps | String]("Hello")
4+
locally:
5+
val test1 : (scala.collection.StringOps | String) { def *(y: Int): (scala.collection.StringOps | String) } = ???
6+
val test2 : (scala.collection.StringOps | String) { def *(y: Int): (scala.collection.StringOps | String) } = test1
7+
8+
locally:
9+
val test1 : (Int | String) { def foo(x: Int): Int } = ???
10+
val test2 : (Int | String) { def foo(x: Int): Int } = test1
11+
12+
locally:
13+
val test1 : ((Int | String) & Any) { def foo(): Int } = ???
14+
val test2 : ((Int | String) & Any) { def foo(): Int } = test1
15+
16+
locally:
17+
val test1 : Int { def foo(): Int } = ???
18+
val test2 : Int { def foo(): Int } = test1
19+
20+
locally:
21+
val test1 : (Int | String) { def foo(): Int } = ???
22+
val test2 : (Int | String) & Any = test1
23+
24+
locally:
25+
val test1 : (Int | B) { def *(y: Int): Int } = ???
26+
val test2 : (Int | B) { def *(y: Int): Int } = test1
27+
28+
locally:
29+
val test1 : (Int | String) = ???
30+
val test2 : (Int | String) = test1
31+
32+
type Foo = Int | String
33+
locally:
34+
val test1 : Foo { type T = Int } = ???
35+
val test2 : (Int | String) = test1
36+
}
37+
38+
def test2: Unit = {
39+
import reflect.Selectable.reflectiveSelectable
40+
41+
trait A[T](x: T{ def *(y: Int): T }):
42+
def f: T = x * 2
43+
44+
class B extends A("Hello")
45+
}

0 commit comments

Comments
 (0)