-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix spurious subtype check pruning when both sides have unions (#18213)
Fixes #17465. In TypeComparer, `fourthTry` calls `isNewSubType` and `isCovered` to detect the subtype queries that have been covered by previous attempts and prune them. However, the pruning is spurious when both sides contain union types, as exemplified by the following subtype trace before the PR: ``` ==> isSubType (test1 : (Int | String){def foo(x: Int): Int}) <:< Int | String? ==> isSubType (Int | String){def foo(x: Int): Int} <:< Int | String? ==> isSubType (Int | String){def foo(x: Int): Int} <:< Int? <== isSubType (Int | String){def foo(x: Int): Int} <:< Int = false ==> isSubType (Int | String){def foo(x: Int): Int} <:< String? ==> isSubType (Int | String){def foo(x: Int): Int} <:< String? <== isSubType (Int | String){def foo(x: Int): Int} <:< String = false <== isSubType (Int | String){def foo(x: Int): Int} <:< String = false // (1): follow-up subtype checks are pruned here by isNewSubType <== isSubType (Int | String){def foo(x: Int): Int} <:< Int | String = false <== isSubType (test1 : (Int | String){def foo(x: Int): Int}) <:< Int | String = false ``` At `(1)`, the pruning condition is met, and follow-up recursions are skipped. However, in this case, only after `(1)` are the refinement on LHS dropped and the subtype between two identical OrTypes are accepted. The pruning is spurious. This PR tempers the pruning conditions specified in `isCovered` and `isNewSubType` to fix these false negatives.
- Loading branch information
Showing
2 changed files
with
77 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
def test1[A, B]: Unit = { | ||
def f[T](x: T{ def *(y: Int): T }): T = ??? | ||
def test = f[scala.collection.StringOps | String]("Hello") | ||
locally: | ||
val test1 : (scala.collection.StringOps | String) { def *(y: Int): (scala.collection.StringOps | String) } = ??? | ||
val test2 : (scala.collection.StringOps | String) { def *(y: Int): (scala.collection.StringOps | String) } = test1 | ||
|
||
locally: | ||
val test1 : (Int | String) { def foo(x: Int): Int } = ??? | ||
val test2 : (Int | String) { def foo(x: Int): Int } = test1 | ||
|
||
locally: | ||
val test1 : ((Int | String) & Any) { def foo(): Int } = ??? | ||
val test2 : ((Int | String) & Any) { def foo(): Int } = test1 | ||
|
||
locally: | ||
val test1 : Int { def foo(): Int } = ??? | ||
val test2 : Int { def foo(): Int } = test1 | ||
|
||
locally: | ||
val test1 : (Int | String) { def foo(): Int } = ??? | ||
val test2 : (Int | String) & Any = test1 | ||
|
||
locally: | ||
val test1 : (Int | B) { def *(y: Int): Int } = ??? | ||
val test2 : (Int | B) { def *(y: Int): Int } = test1 | ||
|
||
locally: | ||
val test1 : (Int | String) = ??? | ||
val test2 : (Int | String) = test1 | ||
|
||
type Foo = Int | String | ||
locally: | ||
val test1 : Foo { type T = Int } = ??? | ||
val test2 : (Int | String) = test1 | ||
} | ||
|
||
def test2: Unit = { | ||
import reflect.Selectable.reflectiveSelectable | ||
|
||
trait A[T](x: T{ def *(y: Int): T }): | ||
def f: T = x * 2 | ||
|
||
class B extends A("Hello") | ||
} |