Skip to content

Commit aefb05a

Browse files
Fix #9107: Restore constraints after MT reduction
Because of `caseLambda = constrained(cas)` in `def matchCase`, subtyping wrongly things that it's OK to further constrain types introduced in match type patterns. This commit fixes the issue with a stronger version of `inFrozenConstraint` around match type reduction.
1 parent bdca541 commit aefb05a

File tree

2 files changed

+39
-18
lines changed

2 files changed

+39
-18
lines changed

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

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2848,25 +2848,32 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
28482848
MatchTypeTrace.noMatches(scrut, cases)
28492849
NoType
28502850

2851-
inFrozenConstraint {
2852-
// Empty types break the basic assumption that if a scrutinee and a
2853-
// pattern are disjoint it's OK to reduce passed that pattern. Indeed,
2854-
// empty types viewed as a set of value is always a subset of any other
2855-
// types. As a result, we first check that the scrutinee isn't empty
2856-
// before proceeding with reduction. See `tests/neg/6570.scala` and
2857-
// `6570-1.scala` for examples that exploit emptiness to break match
2858-
// type soundness.
2859-
2860-
// If we revered the uncertainty case of this empty check, that is,
2861-
// `!provablyNonEmpty` instead of `provablyEmpty`, that would be
2862-
// obviously sound, but quite restrictive. With the current formulation,
2863-
// we need to be careful that `provablyEmpty` covers all the conditions
2864-
// used to conclude disjointness in `provablyDisjoint`.
2865-
if (provablyEmpty(scrut))
2866-
NoType
2867-
else
2868-
recur(cases)
2851+
// inFrozenConstraint is not sufficient here as it doesn't prevent
2852+
// parameters of type lambdas from being added to `constraint`, see
2853+
// ConstraintHandling.canConstrain and tests/neg/9107.scala.
2854+
val saved = constraint
2855+
try {
2856+
inFrozenConstraint {
2857+
// Empty types break the basic assumption that if a scrutinee and a
2858+
// pattern are disjoint it's OK to reduce passed that pattern. Indeed,
2859+
// empty types viewed as a set of value is always a subset of any other
2860+
// types. As a result, we first check that the scrutinee isn't empty
2861+
// before proceeding with reduction. See `tests/neg/6570.scala` and
2862+
// `6570-1.scala` for examples that exploit emptiness to break match
2863+
// type soundness.
2864+
2865+
// If we reversed the uncertainty case of this empty check, that is,
2866+
// `!provablyNonEmpty` instead of `provablyEmpty`, that would be
2867+
// obviously sound, but quite restrictive. With the current formulation,
2868+
// we need to be careful that `provablyEmpty` covers all the conditions
2869+
// used to conclude disjointness in `provablyDisjoint`.
2870+
if (provablyEmpty(scrut))
2871+
NoType
2872+
else
2873+
recur(cases)
2874+
}
28692875
}
2876+
finally constraint = saved
28702877
}
28712878
}
28722879

tests/neg/9107.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
trait M[F[_]]
2+
trait Inv[T]
3+
4+
object Test {
5+
def ev[X] = implicitly[
6+
(X match { case Inv[t] => Int }) =:=
7+
(X match { case Inv[t] => t })
8+
] // error
9+
10+
def ev2[X] = implicitly[
11+
(M[[t] =>> runtime.MatchCase[Inv[t], Int]]) =:=
12+
(M[[t] =>> runtime.MatchCase[Inv[t], t]])
13+
] // error
14+
}

0 commit comments

Comments
 (0)