Skip to content

Commit 94028f3

Browse files
authored
[patterns] A handful of mostly small changes to patterns and records: (#2493)
* [patterns] A handful of mostly small changes to patterns and records: - Clarify that patterns can call extension members. Fix #2457. - Non-boolean results throw in relational patterns. Fix #2461. - Maps and extractors are evaluated in source order. Fix #2466. - Specify non-exhaustive switch errors and warnings. Fix #2474. - Allow `final` before type annotated variables. Fix #2486. - Rename some grammars to align with Analyzer AST names. Fix #2491. * Add record types to the set of exhaustive types. * Remove outdated remark. * Fix bullet number.
1 parent 1050bbb commit 94028f3

File tree

2 files changed

+129
-45
lines changed

2 files changed

+129
-45
lines changed

accepted/future-releases/records/records-feature-specification.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Author: Bob Nystrom
44

55
Status: Accepted
66

7-
Version 1.8 (see [CHANGELOG](#CHANGELOG) at end)
7+
Version 1.9 (see [CHANGELOG](#CHANGELOG) at end)
88

99
## Motivation
1010

@@ -498,6 +498,18 @@ The implementation of `hashCode` follows this. The hash code returned should
498498
depend on the field values such that two records that compare equal must have
499499
the same hash code.
500500

501+
### Primitive equality
502+
503+
A record object has a primitive `==` operator if all of its field have primitive
504+
`==` operators.
505+
506+
*Note that this is a dynamic property of a record object, not a static property
507+
of its type. Since primitive equality only comes into play in constants, the
508+
compiler can see the actual field values for a relevant record at compile time
509+
because it has the actual constant record value with all of its constant fields.
510+
This means records can be used in constant sets and maps keys, but only when the
511+
records' fields could be as well.*
512+
501513
### Identity
502514

503515
We expect records to often be used for multiple return values. In that case, and
@@ -565,9 +577,13 @@ covariant in their field types.
565577

566578
## CHANGELOG
567579

580+
### 1.9
581+
582+
- Specify that a record has a primitive `==` when its fields all do.
583+
568584
### 1.8
569585

570-
- Move to accepted
586+
- Move to `accepted/`.
571587

572588
### 1.7
573589

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

Lines changed: 111 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Author: Bob Nystrom
44

55
Status: In progress
66

7-
Version 2.6 (see [CHANGELOG](#CHANGELOG) at end)
7+
Version 2.7 (see [CHANGELOG](#CHANGELOG) at end)
88

99
Note: This proposal is broken into a couple of separate documents. See also
1010
[records][] and [exhaustiveness][].
@@ -483,7 +483,7 @@ expression.
483483
### Variable pattern
484484

485485
```
486-
variablePattern ::= ( 'var' | 'final' | type )? identifier
486+
variablePattern ::= ( 'var' | 'final' | 'final'? type )? identifier
487487
```
488488

489489
A variable pattern binds the matched value to a new variable. These usually
@@ -732,13 +732,13 @@ against the initializer's value.
732732
Add this new rule:
733733

734734
```
735-
patternDeclaration ::= ( 'final' | 'var' ) outerPattern '=' expression
735+
patternVariableDeclaration ::= ( 'final' | 'var' ) outerPattern '=' expression
736736
737-
outerPattern ::= parenthesizedPattern
738-
| listPattern
739-
| mapPattern
740-
| recordPattern
741-
| extractorPattern
737+
outerPattern ::= parenthesizedPattern
738+
| listPattern
739+
| mapPattern
740+
| recordPattern
741+
| extractorPattern
742742
```
743743

744744
The `outerPattern` rule defines a subset of the patterns that are allowed as the
@@ -772,7 +772,7 @@ variables like so:
772772
```
773773
localVariableDeclaration ::=
774774
| initializedVariableDeclaration ';' // Existing.
775-
| patternDeclaration ';' // New.
775+
| patternVariableDeclaration ';' // New.
776776
777777
forLoopParts ::=
778778
| // Existing productions...
@@ -840,9 +840,11 @@ It is a compile-time error if:
840840
We extend switch statements to allow patterns in cases:
841841
842842
```
843-
switchStatement ::= 'switch' '(' expression ')' '{' switchCase* defaultCase? '}'
844-
switchCase ::= label* caseHead ':' statements
845-
caseHead ::= 'case' pattern ( 'when' expression )?
843+
switchStatement ::= 'switch' '(' expression ')'
844+
'{' switchStatementCase* switchStatementDefault? '}'
845+
switchStatementCase ::= label* caseHead ':' statements
846+
caseHead ::= 'case' pattern ( 'when' expression )?
847+
switchStatementDefault ::= label* 'default' ':' statements
846848
```
847849
848850
Allowing patterns in cases significantly increases the expressiveness of what
@@ -1070,13 +1072,13 @@ Color shiftHue(Color color) {
10701072
The grammar is:
10711073

10721074
```
1073-
primary ::= // Existing productions...
1074-
| switchExpression
1075+
primary ::= // Existing productions...
1076+
| switchExpression
10751077
1076-
switchExpression ::= 'switch' '(' expression ')' '{'
1077-
switchExpressionCase* defaultExpressionCase? '}'
1078-
switchExpressionCase ::= caseHead '=>' expression ';'
1079-
defaultExpressionCase ::= 'default' '=>' expression ';'
1078+
switchExpression ::= 'switch' '(' expression ')' '{'
1079+
switchExpressionCase* switchExpressionDefault? '}'
1080+
switchExpressionCase ::= caseHead '=>' expression ';'
1081+
switchExpressionDefault ::= 'default' '=>' expression ';'
10801082
```
10811083

10821084
Slotting into `primary` means it can be used anywhere any expression can appear,
@@ -1606,6 +1608,12 @@ To type check a pattern `p` being matched against a value of type `M`:
16061608
*Here, the `1` constant pattern in the case is inferred in a context type of
16071609
`double` to be `1.0` and so does match.*
16081610
1611+
*Note that the pattern's value must be a constant, but there is no
1612+
restriction that it must have a primitive operator `==`. Unlike switch cases
1613+
in current Dart, you can have a constant with a user-defined operator `==`
1614+
method. This lets you use constant patterns for user-defined types with
1615+
custom value semantics.*
1616+
16091617
* **Variable**:
16101618
16111619
1. In an assignment context, the required type of `p` is the (unpromoted)
@@ -1755,9 +1763,9 @@ The variables a patterns binds depend on what kind of pattern it is:
17551763
17561764
* **Variable**: When not in an assignment context, introduces a variable whose
17571765
name is the pattern's identifier. In a declaration context, the variable is
1758-
final if the surrounding `patternDeclaration` has a `final` modifier. In a
1759-
matching context, the variable is final if the variable pattern is marked
1760-
`final` and is not otherwise.
1766+
final if the surrounding `patternVariableDeclaration` has a `final`
1767+
modifier. In a matching context, the variable is final if the variable
1768+
pattern is marked `final` and is not otherwise.
17611769
17621770
[2473]: https://github.com/dart-lang/language/issues/2473
17631771
@@ -1803,21 +1811,44 @@ type.**
18031811

18041812
### Exhaustiveness and reachability
18051813

1806-
A switch is *exhaustive* if all possible values of the matched value's type will
1807-
definitely match at least one case, or there is a default case. Dart currently
1808-
shows a warning if a switch statement on an enum type does not have cases for
1809-
all enum values (or a default). This is helpful for code maintainance: when you
1810-
add a new value to an enum type, the language shows you every switch statement
1811-
that may need a new case to handle it.
1814+
A switch is *exhaustive* if all possible values of the matched value's static
1815+
type will definitely match at least one case, or there is a default case. Dart
1816+
currently shows a warning if a switch statement on an enum type does not have
1817+
cases for all enum values (or a default). This is helpful for code maintainance:
1818+
when you add a new value to an enum type, the language shows you every switch
1819+
statement that may need a new case to handle it.
18121820

18131821
This checking is even more important with this proposal. Exhaustiveness checking
18141822
is a key part of maintaining code written in an algebraic datatype style. It's
18151823
the functional equivalent of the error reported when a concrete class fails to
18161824
implement an abstract method.
18171825

18181826
Exhaustiveness checking over arbitrarily deeply nested record and extractor
1819-
patterns can be complex, so the proposal for that is in a [separate
1820-
document][exhaustiveness].
1827+
patterns is complex, so the proposal to define how it works is in a [separate
1828+
document][exhaustiveness]. That tells us if the cases in a switch statement
1829+
or expression are exhaustive or not. Given that:
1830+
1831+
* It is a compile-time error if the cases in a switch expression are not
1832+
exhaustive. *Since an expression must yield a value, the only other option
1833+
is to throw an error and most Dart users prefer to catch those kinds of
1834+
mistakes at compile time.*
1835+
1836+
* It is a compile-time error if the static type of the matched value in a
1837+
switch statement is an *exhaustive type* and the cases are not exhaustive.
1838+
An exhaustive type is:
1839+
1840+
* `bool`
1841+
* `Null`
1842+
* A type whose declaration is marked sealed
1843+
* `T?` where `T` is exhaustive
1844+
* `FutureOr<T>` for some type `T` that is exhaustive
1845+
* A record type whose fields are all exhaustive types
1846+
1847+
**TODO: Finalize the syntax for marking a class as a sealed family.**
1848+
1849+
* It is a compile-time warning if the static type of the matched value in a
1850+
switch statement is an enum type or a nullable enum type and the cases are
1851+
not exhaustive.
18211852

18221853
[exhaustiveness]: https://github.com/dart-lang/language/blob/master/working/0546-patterns/exhaustiveness.md
18231854

@@ -1917,7 +1948,7 @@ fail in some way.*
19171948
A statement of the form:
19181949

19191950
```dart
1920-
for (<patternDeclaration>; <condition>; <increment>) <statement>
1951+
for (<patternVariableDeclaration>; <condition>; <increment>) <statement>
19211952
```
19221953

19231954
Is executed similar to a traditional for loop except that multiple variables may
@@ -2038,16 +2069,38 @@ To match a pattern `p` against a value `v`:
20382069
20392070
1. Evaluate the right-hand constant expression to `c`.
20402071
2041-
2. A `== c` pattern matches if `v == c` evaluates to true. *This takes into
2042-
account the built-in semantics that `null` is only equal to `null`.*
2072+
2. If the operator is `==`:
2073+
2074+
1. Let `r` be the result of `v == c`.
2075+
2076+
2. If `r` is not a Boolean then throw a runtime error. *This can
2077+
happen if operator `==` on `v`'s type returns `dynamic`.*
2078+
2079+
3. The pattern matches if `r` is true and fails otherwise. *This takes
2080+
into account the built-in semantics that `null` is only equal to
2081+
`null`.*
2082+
2083+
2. Else if the operator is `!=`:
2084+
2085+
1. Let `r` be the result of `v == c`.
2086+
2087+
2. If `r` is not a Boolean then throw a runtime error. *This can
2088+
happen if operator `==` on `v`'s type returns `dynamic`.*
20432089
2044-
3. A `!= c` pattern matches if `v == e` evaluates to false. *This takes
2045-
into account the built-in semantics that `null` is not equal to anything
2046-
but `null`.*
2090+
3. The pattern matches if `r` is false and fails otherwise. *This takes
2091+
into account the built-in semantics that `null` is only equal to
2092+
`null`.*
20472093
2048-
4. For any other operator, the pattern matches if calling the operator
2049-
method of the same name on the matched value, with `c` as the argument
2050-
returns true.
2094+
3. Else the operator is a comparison operator `op`:
2095+
2096+
1. Let `r` be the result of calling `op` on `v` with argument `c`.
2097+
2098+
2. If `r` is not a Boolean then throw a runtime error. *This can happen
2099+
if the operator on `v`'s type returns `dynamic`.*
2100+
2101+
3. The pattern matches if `r` is true and fails otherwise. *This takes
2102+
into account the built-in semantics that `null` is only equal to
2103+
`null`.*
20512104
20522105
* **Cast**:
20532106
@@ -2126,7 +2179,7 @@ To match a pattern `p` against a value `v`:
21262179
becomes a runtime exception if the map pattern is in a variable
21272180
declaration.*
21282181
2129-
3. Otherwise, for each entry in `p`:
2182+
3. Otherwise, for each entry in `p`, in source order:
21302183
21312184
1. Evaluate the key `expression` to `k` and call `containsKey()` on the
21322185
value. If this returns `false`, the map does not match.
@@ -2137,9 +2190,6 @@ To match a pattern `p` against a value `v`:
21372190
21382191
4. The match succeeds if all entry subpatterns match.
21392192
2140-
*Note that, unlike with lists, a matched map may have additional entries
2141-
that are not checked by the pattern.*
2142-
21432193
* **Record**:
21442194
21452195
1. If the runtime type of `v` is not a record type with the same type as
@@ -2159,9 +2209,10 @@ To match a pattern `p` against a value `v`:
21592209
1. If the runtime type of `v` is not a subtype of the static type of `p`
21602210
then the match fails.
21612211
2162-
3. Otherwise, for each field `f` in `p`:
2212+
2. Otherwise, for each field `f` in `p`, in source order:
21632213
21642214
1. Call the getter with the same name as `f` on `v` to a result `r`.
2215+
The getter may be an in-scope extension member.
21652216
21662217
2. Match the subpattern of `f` against `r`. If the match fails, the
21672218
extractor match fails.
@@ -2251,6 +2302,23 @@ Here is one way it could be broken down into separate pieces:
22512302
22522303
## Changelog
22532304
2305+
### 2.7
2306+
2307+
- Clarify that relational and extractor patterns can call extension members
2308+
(#2457).
2309+
2310+
- Non-boolean results throw in relational patterns instead of failing the
2311+
match (#2461).
2312+
2313+
- Specify that map and extractor subpatterns are evaluated in source order
2314+
(#2466).
2315+
2316+
- Specify non-exhaustive switch errors and warnings (#2474).
2317+
2318+
- Allow `final` before type annotated variable patterns (#2486).
2319+
2320+
- Rename some grammars to align with Analyzer AST names (#2491).
2321+
22542322
### 2.6
22552323
22562324
- Change logical-or and logical-and patterns to be left-associative.

0 commit comments

Comments
 (0)