Skip to content

Commit 113dcc4

Browse files
authored
[patterns] Tease apart type testing from refutability. (#2454)
Some patterns fail to match if the value doesn't have a certain type. We still allow those patterns in irrefutable contexts if the matched value is statically known to always have that type (or be dynamic). Instead of specifying by having the refutability state of those patterns depend on their type, make refutability a syntactic property and then define the compile-time error for the types during type checking. Fix #2450.
1 parent 82451b8 commit 113dcc4

File tree

1 file changed

+82
-108
lines changed

1 file changed

+82
-108
lines changed

working/0546-patterns/patterns-feature-specification.md

Lines changed: 82 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1165,6 +1165,66 @@ That is less important here since the only other case is the else branch.
11651165

11661166
## Static semantics
11671167

1168+
### Refutable and irrefutable patterns and contexts
1169+
1170+
Patterns appear inside a number of other constructs in the language. This
1171+
proposal extends Dart to allow patterns in:
1172+
1173+
* Local variable declarations.
1174+
* For loop variable declarations.
1175+
* Assignment expressions.
1176+
* Switch statement cases.
1177+
* A new switch expression form's cases.
1178+
* A new pattern-if statement.
1179+
1180+
When a pattern appears in a switch case, any variables bound by the pattern are
1181+
only in scope in that case's body. If the pattern fails to match, the case body
1182+
is skipped. This ensures that the variables can't be used when the pattern
1183+
failed to match and they have no defined value. Likewise, the variables bound by
1184+
a pattern-if statement's pattern are only in scope in the then branch. That
1185+
branch is skipped if the pattern fails to match.
1186+
1187+
When patterns appear in variable declarations and assignments, there is no
1188+
natural control flow that can skip over uses of the variables if the pattern
1189+
fails to match. For example:
1190+
1191+
```dart
1192+
main() {
1193+
var (a, 5) = (1, 2);
1194+
print(a + b);
1195+
}
1196+
```
1197+
1198+
What happens when `a` is printed in the example above?
1199+
1200+
To avoid that, we restrict which patterns can be used in variable declarations
1201+
and pattern assignments. Only *irrefutable* patterns that never fail to match
1202+
are allowed in contexts where match failure can't be handled. Since a literal
1203+
pattern like `5` in the above example might fail to match, it's prohibited and
1204+
the above example has a compile error.
1205+
1206+
We define an *irrefutable context* as the pattern in a
1207+
`localVariableDeclaration`, `forLoopParts`, or `patternAssignment` or any of its
1208+
subpatterns. It is a compile-time error if any of these patterns appear in an
1209+
irrefutable context:
1210+
1211+
* Logical-or
1212+
* Relational
1213+
* Null-check
1214+
* Literal
1215+
* Constant
1216+
1217+
*Logical-or patterns are refutable because there is no point in using one with
1218+
an irrefutable left operand. We could make null-check patterns irrefutable if
1219+
`V` is assignable to its static type, but whenever that is true the pattern does
1220+
nothing useful since its only behavior is a type test.*
1221+
1222+
*The remaining patterns are allowed syntactically to appear in a refutable
1223+
context. Patterns that do type tests like variables and lists produce a
1224+
compile-time error when used in an irrefutable context if the matched value
1225+
isn't assignable to their required type. This error is specified under type
1226+
checking.*
1227+
11681228
### Type inference
11691229

11701230
Type inference in Dart allows type information in one part of the program to
@@ -1380,15 +1440,16 @@ The context type schema for a pattern `p` is:
13801440
// ^-- Infer Foo<num>.
13811441
```
13821442
1383-
#### Type checking and pattern static type
1443+
#### Type checking and pattern required type
13841444
13851445
Once the value a pattern is matched against has a static type (which means
13861446
downwards inference on it using the pattern's context type schema is complete),
1387-
we can type check the pattern. We also calculate a static type for the patterns
1388-
that only match certain types: null-check, variable, list, map, record, and
1389-
extractor.
1447+
we can type check the pattern.
13901448
1391-
Some examples and the corresponding pattern static types:
1449+
Also variable, list, map, record, and extractor patterns only match a value of a
1450+
certain *required type*. These patterns are prohibited in an irrefutable context
1451+
if the matched value isn't assignable to that type. We define the required type
1452+
for those patterns here. Some examples and the corresponding required types:
13921453
13931454
```dart
13941455
var <int>[a, b] = <num>[1, 2]; // List<int> (and compile error).
@@ -1413,8 +1474,6 @@ To type check a pattern `p` being matched against a value of type `M`:
14131474

14141475
2. Type-check the subpattern using `N` as the matched value type.
14151476

1416-
3. If `p` is a null-check pattern, then the static type of `p` is `N`.
1417-
14181477
[nonnull]: https://github.com/dart-lang/language/blob/master/accepted/2.12/nnbd/feature-specification.md#null-promotion
14191478

14201479
* **Literal** or **constant**: Type check the pattern's value expression in
@@ -1473,7 +1532,7 @@ To type check a pattern `p` being matched against a value of type `M`:
14731532
14741533
*both `a` and `b` use `num` as their matched value type.*
14751534
1476-
3. The static type of `p` is `List<S>` where:
1535+
3. The required type of `p` is `List<S>` where:
14771536
14781537
1. If `p` has a type argument, `S` is that type. *If the list pattern
14791538
has an explicit type argument, that wins.*
@@ -1500,7 +1559,7 @@ To type check a pattern `p` being matched against a value of type `M`:
15001559
15011560
*Here, both `a` and `b` use `Object` as the matched value type.*
15021561
1503-
3. The static type of `p` is `Map<L, W>` where:
1562+
3. The required type of `p` is `Map<L, W>` where:
15041563
15051564
1. If `p` has type arguments, `L` and `W` are those type arguments.
15061565
*If the map pattern is explicitly typed, that wins.*
@@ -1520,7 +1579,7 @@ To type check a pattern `p` being matched against a value of type `M`:
15201579
corresponding named field on `M` as the matched value type or `Object?`
15211580
if `M` is not a record type with the corresponding field.
15221581
1523-
3. The static type of `p` is a record type with the same shape as `p` and
1582+
3. The required type of `p` is a record type with the same shape as `p` and
15241583
`Object?` for all fields. *If the matched value's type is `dynamic` or
15251584
some record supertype like `Object`, then the record pattern should
15261585
match any record with the right shape and then delegate to its field
@@ -1537,107 +1596,22 @@ To type check a pattern `p` being matched against a value of type `M`:
15371596
a compile-time error if `X` does not have a getter whose name matches
15381597
the subpattern's field name.
15391598
1540-
2. The static type of `p` is `X`.
1599+
2. The required type of `p` is `X`.
15411600
1542-
It is a compile-time error if the type of an expression in a guard clause is not
1543-
`bool` or `dynamic`.
1601+
It is a compile-time error if:
1602+
1603+
* The type of an expression in a guard clause is not `bool` or `dynamic`.
1604+
1605+
* `p` is in an irrefutable context, it has a required type `T`, and `M` is not
1606+
assignable to `T`. *Destructuring and variable patterns can only be used in
1607+
declarations and assignments if we can statically tell that the
1608+
destructuring and variable binding won't fail.*
15441609
15451610
### Switch expression type
15461611
15471612
The static type of a switch expression is the least upper bound of the static
15481613
types of all of the case expressions.
15491614
1550-
## Refutable and irrefutable patterns
1551-
1552-
Patterns appear inside a number of other constructs in the language. This
1553-
proposal extends Dart to allow patterns in:
1554-
1555-
* Local variable declarations.
1556-
* For loop variable declarations.
1557-
* Assignment expressions.
1558-
* Switch statement cases.
1559-
* A new switch expression form's cases.
1560-
* A new pattern-if statement.
1561-
1562-
When a pattern appears in a switch case, any variables bound by the pattern are
1563-
only in scope in that case's body. If the pattern fails to match, the case body
1564-
is skipped. This ensures that the variables can't be used when the pattern
1565-
failed to match and they have no defined value. Likewise, the variables bound by
1566-
a pattern-if statement's pattern are only in scope in the then branch. That
1567-
branch is skipped if the pattern fails to match.
1568-
1569-
The other places patterns can appear are various kinds of variable declarations,
1570-
like:
1571-
1572-
```dart
1573-
main() {
1574-
var (a, b) = (1, 2);
1575-
print(a + b);
1576-
}
1577-
```
1578-
1579-
Variable declarations have no natural control flow attached to them, so what
1580-
happens if the pattern fails to match? What happens when `a` is printed in the
1581-
example above?
1582-
1583-
To avoid that, we restrict which patterns can be used in variable declarations.
1584-
Only *irrefutable* patterns that never fail to match are allowed in contexts
1585-
where match failure can't be handled. For example, this is an error:
1586-
1587-
```dart
1588-
main() {
1589-
var (== 2, == 3) = (1, 2);
1590-
}
1591-
```
1592-
1593-
We define an *irrefutable context* as the pattern in a
1594-
`localVariableDeclaration`, `forLoopParts`, or `patternAssignment`. A *refutable
1595-
context* is the pattern in a `caseHead` or `ifCondition`.
1596-
1597-
Refutability is not just a property of the pattern itself. It also depends on
1598-
the static type of the value being matched. Consider:
1599-
1600-
```dart
1601-
irrefutable((int, int) obj) {
1602-
var (a, b) = obj;
1603-
}
1604-
1605-
refutable(Object obj) {
1606-
var (a, b) = obj;
1607-
}
1608-
```
1609-
1610-
In the first function, the `(a, b)` pattern will always successfully destructure
1611-
the record because `obj` is known to be a record type of the right shape. But in
1612-
the second function, `obj` may fail to match because the value may not be a
1613-
record. *This implies that we can't determine whether a pattern in a variable
1614-
declaration is incorrectly refutable until after type checking.*
1615-
1616-
Refutability of a pattern `p` matching a value of type `V` is:
1617-
1618-
* **Logical-and**, **parenthesized**, **null-assert**, or **cast**:
1619-
Irrefutable if and only if all subpatterns are irrefutable.
1620-
1621-
* **Logical-or**, **relational**, **null-check**, **literal**, or
1622-
**constant**: Always refutable. *Logical-or patterns are refutable because
1623-
there is no point in using one with an irrefutable left operand. We could
1624-
make null-check patterns irrefutable if `V` is assignable to its static
1625-
type, but whenever that is true the pattern does nothing useful since its
1626-
only behavior is a type test.*
1627-
1628-
* **variable**, **list**, **map**, **record**, or **extractor**: Irrefutable
1629-
if and only if `V` is assignable to the static type of `p` and all
1630-
subpatterns are irrefutable. *If `p` is a variable pattern with no type
1631-
annotation, the type is inferred from `V`, so it is never refutable.*
1632-
1633-
It is a compile-time error if a refutable pattern appears in an irrefutable
1634-
context. *This means that the explicit predicate patterns like constants and
1635-
literals can never appear in pattern variable declarations or pattern
1636-
assignments. The patterns that do type tests directly or implicitly can appear
1637-
in variable declarations or assignments only if the tested type is assignable
1638-
from the value type. In other words, any pattern that needs to "downcast" to
1639-
match is refutable.*
1640-
16411615
### Variables and scope
16421616
16431617
Patterns often exist to introduce new variable bindings. A "wildcard" identifier
@@ -1949,8 +1923,8 @@ To match a pattern `p` against a value `v`:
19491923
* **Null-assert**:
19501924
19511925
1. If `v` is null then throw a runtime exception. *Note that we throw even
1952-
if this appears in a refutable context. The intent of this pattern is to
1953-
assert that a value *must* not be null.*
1926+
if this appears outside of a refutable context. The intent of this
1927+
pattern is to assert that a value *must* not be null.*
19541928
19551929
2. Otherwise, match the inner pattern against `v`.
19561930
@@ -1970,8 +1944,8 @@ To match a pattern `p` against a value `v`:
19701944
19711945
1. If the runtime type of `v` is not a subtype of the static type of `p`
19721946
then throw a runtime exception. *Note that we throw even if this appears
1973-
in a refutable context. The intent of this pattern is to assert that a
1974-
value *must* have some type.*
1947+
outside of a refutable context. The intent of this pattern is to assert
1948+
that a value *must* have some type.*
19751949
19761950
2. Otherwise, bind the variable's identifier to `v` and the match succeeds.
19771951

0 commit comments

Comments
 (0)