Skip to content

Commit 46832a1

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 cc8d6c3 commit 46832a1

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
@@ -2622,25 +2622,32 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
26222622
case Nil => NoType
26232623
}
26242624

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

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)