Skip to content

Commit dcbdba1

Browse files
committed
[Patterns] Fix a handful of minor issues.
- Support negative number literals in patterns. - Allow map patterns in pattern assignments. - Allow symbol literals in patterns. - Give compilers more leeway on runtime semantics maps. - Elide type tests that can only fail on legacy types. Fix #2663. Fix #2662. Fix #2636. Fix #2634. Fix #2619.
1 parent 9a9d958 commit dcbdba1

File tree

1 file changed

+144
-20
lines changed

1 file changed

+144
-20
lines changed

accepted/future-releases/0546-patterns/feature-specification.md

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

55
Status: Accepted
66

7-
Version 2.17 (see [CHANGELOG](#CHANGELOG) at end)
7+
Version 2.18 (see [CHANGELOG](#CHANGELOG) at end)
88

99
Note: This proposal is broken into a couple of separate documents. See also
1010
[records][] and [exhaustiveness][].
@@ -214,8 +214,8 @@ Here is the overall grammar for the different kinds of patterns:
214214
```
215215
pattern ::= logicalOrPattern
216216
217-
logicalOrPattern ::= ( logicalOrPattern '||' )? logicalAndPattern
218-
logicalAndPattern ::= ( logicalAndPattern '&&' )? relationalPattern
217+
logicalOrPattern ::= logicalOrPattern ( '||' logicalAndPattern )*
218+
logicalAndPattern ::= logicalAndPattern ( '&&' relationalPattern )*
219219
relationalPattern ::= ( equalityOperator | relationalOperator) bitwiseOrExpression
220220
| unaryPattern
221221
@@ -429,8 +429,9 @@ switch (row) {
429429
```
430430
constantPattern ::= booleanLiteral
431431
| nullLiteral
432-
| numericLiteral
432+
| '-'? numericLiteral
433433
| stringLiteral
434+
| symbolLiteral
434435
| identifier
435436
| qualifiedName
436437
| constObjectExpression
@@ -445,7 +446,10 @@ syntactically overlap other kinds of patterns. We avoid ambiguity while
445446
supporting terse forms of the most common constant expressions like so:
446447

447448
* Simple "primitive" literals like Booleans and numbers are valid patterns
448-
since they aren't ambiguous.
449+
since they aren't ambiguous. We also allow unary `-` expressions on
450+
numeric literals since users think of `-2` as a single literal and not the
451+
literal `2` with a unary `-` applied to it (which is how the language
452+
views it).
449453

450454
* Named constants are also allowed because they aren't ambiguous. That
451455
includes simple identifiers like `someConstant`, prefixed constants like
@@ -887,6 +891,34 @@ It is a compile-time error if:
887891
[a, a, a] = [1, 2, 3];
888892
```
889893
894+
#### Map patterns in pattern assignments
895+
896+
The language specifies:
897+
898+
> An expression statement consists of an expression that does not begin with a
899+
> '{' character.
900+
901+
This avoids an ambiguity between blocks and map literals. But with map patterns
902+
in assignments, it is useful to have an expression statement that begins with
903+
`{`:
904+
905+
```dart
906+
var map = {'a': 1, 'b': 2};
907+
int a, b;
908+
// more code...
909+
910+
// later...
911+
{'a': a, 'b': b} = map;
912+
```
913+
914+
To support this while still avoiding the ambiguity between blocks and map
915+
literals, we change the above rule to:
916+
917+
The expression of a statement expression cannot start with a `{` token which
918+
starts a set or map literal. It may start with a `{` only if that starts a map
919+
pattern of a pattern assignment expression, in which case the corresponding
920+
closing `}` must be immediately followed by a `=`.
921+
890922
### Switch statement
891923

892924
We extend switch statements to allow patterns in cases:
@@ -976,7 +1008,9 @@ The specific kinds of switches whose behavior changes are:
9761008
These nine cases represent 0.009% of the cases found.
9771009
9781010
For any switch case that is broken by this proposal, you can revert back to the
979-
original behavior by prefixing the case expression (now pattern) with `const`:
1011+
original behavior by prefixing the case expression (now pattern) with `const`
1012+
and wrapping it in parentheses if the expression is not a collection literal
1013+
or const constructor call:
9801014
9811015
```dart
9821016
// List or map literal:
@@ -986,15 +1020,15 @@ case const [a, b]:
9861020
case const SomeClass(1, 2):
9871021
9881022
// Other constant expression:
989-
case const A + A:
990-
case const A + 'b':
991-
case const -ERR_LDS_ICAO_SIGNED_DATA_SIGNER_INFOS_EMPTY:
992-
case const -sigkill:
993-
case const List<RPChoice>:
994-
case const 720 * 1280:
995-
case const 1080 * 1920:
996-
case const 1440 * 2560:
997-
case const 2160 * 3840:
1023+
case const (A + A):
1024+
case const (A + 'b'):
1025+
case const (-ERR_LDS_ICAO_SIGNED_DATA_SIGNER_INFOS_EMPTY):
1026+
case const (-sigkill):
1027+
case const (List<RPChoice>):
1028+
case const (720 * 1280):
1029+
case const (1080 * 1920):
1030+
case const (1440 * 2560):
1031+
case const (2160 * 3840):
9981032
```
9991033

10001034
We can determine syntactically whether an existing switch case's behavior will
@@ -2462,10 +2496,11 @@ the pattern may also *destructure* data from the object or *bind* variables.
24622496
24632497
Refutable patterns usually occur in a context where match refutation causes
24642498
execution to skip over the body of code where any variables bound by the pattern
2465-
are in scope. If a pattern match failure occurs in irrefutable context, a
2499+
are in scope. If a pattern match failure occurs in an irrefutable context, a
24662500
runtime error is thrown. *This can happen when matching against a value of type
2467-
`dynamic`, or when a list pattern in a variable declaration is matched against a
2468-
list of a different length.*
2501+
`dynamic`, when a list pattern in a variable declaration is matched against a
2502+
list of a different length, when a map pattern in a pattern assignment is
2503+
matched against a map that lacks some of the destructured keys, etc.*
24692504
24702505
To match a pattern `p` against a value `v`:
24712506
@@ -2557,6 +2592,9 @@ To match a pattern `p` against a value `v`:
25572592
25582593
2. If the runtime type of `v` is not a subtype of `T` then the match fails.
25592594
2595+
*This type test may get elided. See "Pointless type tests and legacy
2596+
types" below.*
2597+
25602598
3. Otherwise, store `v` in `p`'s variable and the match succeeds.
25612599
25622600
* **Parenthesized**: Match the subpattern against `v` and succeed if it
@@ -2569,6 +2607,9 @@ To match a pattern `p` against a value `v`:
25692607
some `T` determined either by the pattern's explicit type argument or
25702608
inferred from the matched value type.*
25712609
2610+
*This type test may get elided. See "Pointless type tests and legacy
2611+
types" below.*
2612+
25722613
2. Let `l` be the length of the list determined by calling `length` on `v`.
25732614
25742615
3. Let `h` be the number of non-rest element subpatterns preceding the rest
@@ -2618,6 +2659,9 @@ To match a pattern `p` against a value `v`:
26182659
some `K` and `V` determined either by the pattern's explicit type
26192660
arguments or inferred from the matched value type.*
26202661
2662+
*This type test may get elided. See "Pointless type tests and legacy
2663+
types" below.*
2664+
26212665
2. Let `l` be the length of the map determined by calling `length` on `v`.
26222666
26232667
3. If `p` has no rest element and `l` is not equal to the number of
@@ -2628,20 +2672,38 @@ To match a pattern `p` against a value `v`:
26282672
26292673
4. Otherwise, for each (non-rest) entry in `p`, in source order:
26302674
2631-
1. Evaluate the key `expression` to `k` and call `containsKey()` on the
2632-
value. If this returns `false`, the map does not match.
2675+
1. Evaluate the key `expression` to `k` and call `containsKey(k)` on
2676+
the value. If this returns `false`, the map does not match.
26332677
26342678
2. Otherwise, evaluate `v[k]` and match the resulting value against
26352679
this entry's value subpattern. If it does not match, the map does
26362680
not match.
26372681
2682+
A compiler is free to call `v[k]` and `containsKey()` in either order,
2683+
or to elide calling one or both if it determines that doing so will
2684+
produce the same result. It may assume that the map adheres to the
2685+
following protocol:
2686+
2687+
* If `containsKey(k)` returns `false` for some key, then `v[k]` will
2688+
return `null`.
2689+
2690+
* If `containsKey(k)` returns `true` for some key, then `v[k]` returns
2691+
an instance of the map's value type.
2692+
2693+
*In particular, if the map's value type is non-nullable, then when
2694+
`v[k]` returns `null`, the compiler can assume that the key is absent
2695+
and `containsKey(k)` would return `false` too.*
2696+
26382697
5. The match succeeds if all entry subpatterns match.
26392698
26402699
* **Record**:
26412700
26422701
1. If the runtime type of `v` is not a subtype of the required type of `p`,
26432702
then the match fails.
26442703
2704+
*This type test may get elided. See "Pointless type tests and legacy
2705+
types" below.*
2706+
26452707
2. For each field `f` in `p`, in source order:
26462708
26472709
1. Access the corresponding field in record `v` as `r`.
@@ -2656,6 +2718,9 @@ To match a pattern `p` against a value `v`:
26562718
1. If the runtime type of `v` is not a subtype of the required type of `p`
26572719
then the match fails.
26582720
2721+
*This type test may get elided. See "Pointless type tests and legacy
2722+
types" below.*
2723+
26592724
2. Otherwise, for each field `f` in `p`, in source order:
26602725
26612726
1. Call the getter with the same name as `f` on `v`, and let the result
@@ -2666,6 +2731,50 @@ To match a pattern `p` against a value `v`:
26662731
26672732
3. The match succeeds if all field subpatterns match.
26682733
2734+
### Pointless type tests and legacy types
2735+
2736+
Variable, map, list, record, and object patterns all do a runtime type test on
2737+
the matched object against the pattern's static type (variables and wildcards)
2738+
or required type (maps, lists, records, and objects). If the matched value's
2739+
static type is a subtype of the pattern's static or required type, then no
2740+
runtime type test is performed.
2741+
2742+
*When the pattern's type is a supertype of the matched value's static type, then
2743+
it seems like the runtime type test is guaranteed to pass. That implies there's
2744+
no need to _specify_ that the check is elided. But these otherwise pointless
2745+
runtime type tests _can_ fail in a mixed-mode program if a legacy typed value
2746+
flows into a pattern. For example:*
2747+
2748+
```dart
2749+
// legacy.dart
2750+
int legacyInt = null;
2751+
2752+
// current.dart
2753+
import 'legacy.dart';
2754+
2755+
f(int i) {
2756+
if (i case _) { // Wildcard has inferred static type non-legacy int.
2757+
print('matched');
2758+
} else {
2759+
print('unreachable');
2760+
}
2761+
}
2762+
2763+
main() {
2764+
f(legacyInt);
2765+
}
2766+
```
2767+
2768+
*If we always require the type test, then this would print "unreachable". But
2769+
that would require inserting type tests which are especially confusing in
2770+
wildcard patterns which users expect should always match. Instead, we allow the
2771+
value to flow through instead of forcing the compiler to insert runtime checks
2772+
that are otherwise pointless and costly in terms of code size. This program
2773+
should print "matched".*
2774+
2775+
*In a fully null-safe program, these type tests can never fail and it is not
2776+
user-visible whether or not an implementation elides them.*
2777+
26692778
### Side effects and exhaustiveness
26702779

26712780
You might expect this to be soundly exhaustive:
@@ -3011,6 +3120,21 @@ Here is one way it could be broken down into separate pieces:
30113120
30123121
## Changelog
30133122
3123+
### 2.18
3124+
3125+
- Support negative number literals in patterns (#2663).
3126+
3127+
- Allow map patterns in pattern assignments in expression statements (#2662).
3128+
3129+
- Remove left recursion in grammar for `||` and `&&` (#2636). (The syntax and
3130+
semantics are unchanged, it's just specified differently.)
3131+
3132+
- Allow symbol literals in patterns (#2636).
3133+
3134+
- Give compilers more leeway on the runtime semantics of map patterns (#2634).
3135+
3136+
- Elide type tests that can only fail on legacy types (#2619).
3137+
30143138
### 2.17
30153139
30163140
- Change logical pattern syntax to `||` and `&&` (#2501).

0 commit comments

Comments
 (0)