Skip to content

Commit 1d07c2f

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 e2ea418 commit 1d07c2f

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
@@ -2814,25 +2814,32 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
28142814
MatchTypeTrace.noMatches(scrut, cases)
28152815
NoType
28162816

2817-
inFrozenConstraint {
2818-
// Empty types break the basic assumption that if a scrutinee and a
2819-
// pattern are disjoint it's OK to reduce passed that pattern. Indeed,
2820-
// empty types viewed as a set of value is always a subset of any other
2821-
// types. As a result, we first check that the scrutinee isn't empty
2822-
// before proceeding with reduction. See `tests/neg/6570.scala` and
2823-
// `6570-1.scala` for examples that exploit emptiness to break match
2824-
// type soundness.
2825-
2826-
// If we revered the uncertainty case of this empty check, that is,
2827-
// `!provablyNonEmpty` instead of `provablyEmpty`, that would be
2828-
// obviously sound, but quite restrictive. With the current formulation,
2829-
// we need to be careful that `provablyEmpty` covers all the conditions
2830-
// used to conclude disjointness in `provablyDisjoint`.
2831-
if (provablyEmpty(scrut))
2832-
NoType
2833-
else
2834-
recur(cases)
2817+
// inFrozenConstraint is not sufficient here as it doesn't prevent
2818+
// parameters of type lambdas from being added to `constraint`, see
2819+
// ConstraintHandling.canConstrain and tests/neg/9107.scala.
2820+
val saved = constraint
2821+
try {
2822+
inFrozenConstraint {
2823+
// Empty types break the basic assumption that if a scrutinee and a
2824+
// pattern are disjoint it's OK to reduce passed that pattern. Indeed,
2825+
// empty types viewed as a set of value is always a subset of any other
2826+
// types. As a result, we first check that the scrutinee isn't empty
2827+
// before proceeding with reduction. See `tests/neg/6570.scala` and
2828+
// `6570-1.scala` for examples that exploit emptiness to break match
2829+
// type soundness.
2830+
2831+
// If we reversed the uncertainty case of this empty check, that is,
2832+
// `!provablyNonEmpty` instead of `provablyEmpty`, that would be
2833+
// obviously sound, but quite restrictive. With the current formulation,
2834+
// we need to be careful that `provablyEmpty` covers all the conditions
2835+
// used to conclude disjointness in `provablyDisjoint`.
2836+
if (provablyEmpty(scrut))
2837+
NoType
2838+
else
2839+
recur(cases)
2840+
}
28352841
}
2842+
finally constraint = saved
28362843
}
28372844
}
28382845

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] =>> internal.MatchCase[Inv[t], Int]]) =:=
12+
(M[[t] =>> internal.MatchCase[Inv[t], t]])
13+
] // error
14+
}

0 commit comments

Comments
 (0)