Skip to content

Commit 785d314

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 af37d64 commit 785d314

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
@@ -2612,25 +2612,32 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
26122612
case Nil => NoType
26132613
}
26142614

2615-
inFrozenConstraint {
2616-
// Empty types break the basic assumption that if a scrutinee and a
2617-
// pattern are disjoint it's OK to reduce passed that pattern. Indeed,
2618-
// empty types viewed as a set of value is always a subset of any other
2619-
// types. As a result, we first check that the scrutinee isn't empty
2620-
// before proceeding with reduction. See `tests/neg/6570.scala` and
2621-
// `6570-1.scala` for examples that exploit emptiness to break match
2622-
// type soundness.
2623-
2624-
// If we revered the uncertainty case of this empty check, that is,
2625-
// `!provablyNonEmpty` instead of `provablyEmpty`, that would be
2626-
// obviously sound, but quite restrictive. With the current formulation,
2627-
// we need to be careful that `provablyEmpty` covers all the conditions
2628-
// used to conclude disjointness in `provablyDisjoint`.
2629-
if (provablyEmpty(scrut))
2630-
NoType
2631-
else
2632-
recur(cases)
2615+
// inFrozenConstraint is not sufficient here as it doesn't prevent
2616+
// parameters of type lambdas from being added to `constraint`, see
2617+
// ConstraintHandling.canConstrain and tests/neg/9107.scala.
2618+
val saved = constraint
2619+
try {
2620+
inFrozenConstraint {
2621+
// Empty types break the basic assumption that if a scrutinee and a
2622+
// pattern are disjoint it's OK to reduce passed that pattern. Indeed,
2623+
// empty types viewed as a set of value is always a subset of any other
2624+
// types. As a result, we first check that the scrutinee isn't empty
2625+
// before proceeding with reduction. See `tests/neg/6570.scala` and
2626+
// `6570-1.scala` for examples that exploit emptiness to break match
2627+
// type soundness.
2628+
2629+
// If we revered the uncertainty case of this empty check, that is,
2630+
// `!provablyNonEmpty` instead of `provablyEmpty`, that would be
2631+
// obviously sound, but quite restrictive. With the current formulation,
2632+
// we need to be careful that `provablyEmpty` covers all the conditions
2633+
// used to conclude disjointness in `provablyDisjoint`.
2634+
if (provablyEmpty(scrut))
2635+
NoType
2636+
else
2637+
recur(cases)
2638+
}
26332639
}
2640+
finally constraint = saved
26342641
}
26352642
}
26362643

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)