@@ -4,7 +4,7 @@ Author: Bob Nystrom
44
55Status: Accepted
66
7- Version 2.17 (see [ CHANGELOG] ( #CHANGELOG ) at end)
7+ Version 2.18 (see [ CHANGELOG] ( #CHANGELOG ) at end)
88
99Note: 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```
215215pattern ::= logicalOrPattern
216216
217- logicalOrPattern ::= ( logicalOrPattern '||' )? logicalAndPattern
218- logicalAndPattern ::= ( logicalAndPattern '&&' )? relationalPattern
217+ logicalOrPattern ::= logicalOrPattern ( '||' logicalAndPattern )*
218+ logicalAndPattern ::= logicalAndPattern ( '&&' relationalPattern )*
219219relationalPattern ::= ( equalityOperator | relationalOperator) bitwiseOrExpression
220220 | unaryPattern
221221
@@ -429,8 +429,9 @@ switch (row) {
429429```
430430constantPattern ::= 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
445446supporting 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
892924We 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
9781010For 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]:
9861020case 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
10001034We 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
24632497Refutable patterns usually occur in a context where match refutation causes
24642498execution 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
24662500runtime 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
24702505To 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
26712780You 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