Skip to content

Commit 22e08f8

Browse files
committed
Update rules on match term/type checking
Unfortunately, things are not as easy as first hoped for...
1 parent 536c350 commit 22e08f8

File tree

2 files changed

+40
-13
lines changed

2 files changed

+40
-13
lines changed

docs/docs/reference/match-types.md

+28-13
Original file line numberDiff line numberDiff line change
@@ -129,41 +129,56 @@ Within a match type `Match(S, Cs) <: B`, all occurrences of type variables count
129129

130130
## Typing Rules for Match Expressions
131131

132-
Typing rules for match expressions have to account for the difference between sequential match on the term level and parallel match on the type level. As a running example consider:
132+
Typing rules for match expressions are tricky. First, they need some new form of GADT matching for value parameters.
133+
Second, they have to account for the difference between sequential match on the term level and parallel match on the type level. As a running example consider:
133134
```scala
134-
type M[X] = X match {
135+
type M[+X] = X match {
135136
case A => 1
136137
case B => 2
137138
}
139+
```
140+
We'd like to be able to typecheck
141+
```scala
138142
def m[X](x: X): M[X] = x match {
143+
case _: A => 1 // type error
144+
case _: B => 2 // type error
145+
}
146+
```
147+
Unfortunately, this goes nowhere. Let's try the first case. We have: `x.type <: A` and `x.type <: X`. This tells
148+
us nothing about `X`, so we cannot reduce `M` in order to show that the right hand side of the case is valid.
149+
150+
The following variant is more promising:
151+
```scala
152+
def m(x: Any): M[x.type] = x match {
139153
case _: A => 1
140154
case _: B => 2
141155
}
142156
```
143-
As a first approximation, the typing rules for match expressions are as usual. E.g. to typecheck the first case `case _: A => 1` of the definition of `m` above, GADT matching will produce the constraint `X <: A`. Therefore, `M[X]` reduces to the singleton type `1`.
144-
The right hand side `1` of the case conforms to this type, so the case typechecks. Typechecking the second case proceeds similarly.
157+
To make this work, we'd need a new form of GADT checking: If the scrutinee is a term variable `s`, we can make use of
158+
the fact that `s.type` must conform to the pattern's type and derive a GADT constraint from that. For the first case above,
159+
this would be the constraint `x.type <: A`. The new aspect here is that we need GADT constraints over singleton types where
160+
before we just had constraints over type parameters.
161+
162+
Assuming this extension, we can then try to typecheck as usual. E.g. to typecheck the first case `case _: A => 1` of the definition of `m` above, GADT matching will produce the constraint `x.type <: A`. Therefore, `M[x.type]` reduces to the singleton type `1`. The right hand side `1` of the case conforms to this type, so the case typechecks. Typechecking the second case proceeds similarly.
145163

146-
However, it turns out that these rules are not enough for type soundness. To see this, assume that `A` and `B` are traits that are both extended by a common class `C`. In this case, `M[C]` reduces to `1 & 2`, but `m(new C)` reduces to `1`. So the type of the application `m(new C)` does not match the reduced result type of `m`, which means soundness is violated.
164+
However, it turns out that these rules are not enough for type soundness. To see this, assume that `A` and `B` are traits that are both extended by a common class `C`. In this case, and assuming `c: C`, `M[c.type]` reduces to `1 & 2`, but `m(c)` reduces to `1`. So the type of the application `m(c)` does not match the reduced result type of `m`, which means soundness is violated.
147165

148-
To plug the soundness hole, we have to tighten the typing rules for match expressions. In the example above we need to also consider the case where the scrutinee type `X` is a subtype of `A` and `B`. In this case, the match expression still returns `1` but the match type `M[X]` reduces to `1 & 2`, which means there should be a type error. However, this second check can be omitted if `A` and `B` are types that don't overlap. We can omit the check because in that case there is no scrutinee value `x` that could reduce to `1`, so no discrepancy can arise at runtime.
166+
To plug the soundness hole, we have to tighten the typing rules for match expressions. In the example above we need to also consider the case where the scrutinee `x` conforms to `A` and `B`. In this case, the match expression still returns `1` but the match type `M[x.type]` reduces to `1 & 2`, which means there should be a type error. However, this second check can be omitted if `A` and `B` are types that don't overlap. We can omit the check because in that case there is no scrutinee value `x` that could reduce to `1`, so no discrepancy can arise at runtime.
149167

150168
More generally, we proceeed as follows:
151169

152170
When typechecking the `i`th case of a match expression
153171
```
154-
t match { case P_1 => t_1 ... case P_n => t_n
172+
x match { case P_1 => t_1 ... case P_n => t_n
155173
```
156-
where `t` has type `T` and `t_i` has type `T_i`
157-
against an expected match type `R`:
174+
where `t_i` has type `T_i` against an expected match type `R`:
158175

159176
1. Determine all maximal sequences of
160177
patterns `P_j_1, ..., P_j_m` that follow `P_i` in the match expression and that do overlap with `P_i`. That is, `P_i, P_j_1, ..., P_j_m` all match at least one common value.
161178

162-
2. For each such sequence, verify that `T_i <: R` under the GADT constraint arising from matching the scrutinee type `T` against all of the patterns `P_i, P_j_1, ..., P_j_m`.
179+
2. For each such sequence, verify that `T_i <: R` under the GADT constraint arising from matching the scrutinee `x` against all of the patterns `P_i, P_j_1, ..., P_j_m`.
163180

164-
In the example above, `A` and `B` would be overlapping because they have the common subclass `C`. Hence, we have to check that the right-hand side `1` is a subtype of `M[X]`
165-
under the assumptions that `X <: A` and `X <: B`. Under these assumptions `M[X]` reduces
166-
to `1 & 2`, which gives a type error.
181+
In the example above, `A` and `B` would be overlapping because they have the common subclass `C`. Hence, we have to check that the right-hand side `1` is a subtype of `M[x.type]` under the assumptions that `x.type <: A` and `x.type <: B`. Under these assumptions `M[x.type]` reduces to `1 & 2`, which gives a type error.
167182

168183
For simplicity, we have disregarded the `null` value in this discussion. `null` does not cause a fundamental problem but complicates things somewhat because some forms of patterns do not match `null`.
169184

tests/pending/pos/matchterm.scala

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
case class A()
2+
case class B()
3+
object Test {
4+
type T[X] = X match {
5+
case A => Int
6+
case B => String
7+
}
8+
def f(x: Any): T[x.type] = x match {
9+
case A() => 1
10+
case B() => ""
11+
}
12+
}

0 commit comments

Comments
 (0)