Skip to content

Commit 4596fde

Browse files
authored
Remove implicit length checks from map patterns. (#2960)
Fix #2861.
1 parent aa768ca commit 4596fde

File tree

1 file changed

+40
-83
lines changed

1 file changed

+40
-83
lines changed

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

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

55
Status: Accepted
66

7-
Version 2.28 (see [CHANGELOG](#CHANGELOG) at end)
7+
Version 2.29 (see [CHANGELOG](#CHANGELOG) at end)
88

99
Note: This proposal is broken into a couple of separate documents. See also
1010
[records][] and [exhaustiveness][].
@@ -604,7 +604,7 @@ rest element with no subpattern as a *non-matching rest element*.
604604
```
605605
mapPattern ::= typeArguments? '{' mapPatternEntries? '}'
606606
mapPatternEntries ::= mapPatternEntry ( ',' mapPatternEntry )* ','?
607-
mapPatternEntry ::= expression ':' pattern | '...'
607+
mapPatternEntry ::= expression ':' pattern
608608
```
609609

610610
A map pattern matches values that implement `Map` and accesses values by key
@@ -621,45 +621,31 @@ It is a compile-time error if:
621621
expressions in a future release without it being a breaking change, similar
622622
to default values in parameter lists.*
623623

624-
* Any two keys in the map are identical. *Map patterns that don't have a rest
625-
element only match if the `length` of the map is equal to the number of map
626-
entries. If a map pattern has multiple identical key entries, they will
627-
increase the required length for the pattern to match but in all but the
628-
most perverse `Map` implementations will represent the same key. Thus, it's
629-
very unlikely that any map pattern containing identical keys (and no rest
630-
element) will ever match. Duplicate keys are most likely a typo in the
631-
code.*
632-
633-
* Any two record keys which both have primitive equality are equal. *Since
634-
records don't have defined identity, we can't use the previous rule to
635-
detect identical records. But records do support an equality test known at
636-
compile time if all of their fields do, so we use that.*
624+
*Note that we don't require map keys to have primitive equality, to enable
625+
more flexibility in key types.*
637626

638-
* There is more than one `...` element in the map pattern.
627+
#### Open and closed maps
639628

640-
* The `...` element is not the last element in the map pattern.
629+
Unlike list and record patterns (but like object patterns), map patterns don't
630+
require the pattern to match the *entire* map. If a map has extra keys that
631+
aren't destructured by the pattern, it can still match.
641632

642-
*Note that we don't require map keys to have primitive equality, to enable
643-
more flexibility in key types. If the keys have user-defined `==` methods, then
644-
it's possible to have keys that are equal according to those `==` methods, but
645-
the compiler won't detect it.*
646-
647-
#### Rest elements
633+
This aligns with the most common use cases for working with maps where extra
634+
keys should be silently ignored. When maps are used as protocols, it tends to
635+
make pattern matching code over those maps more resilient to protocol evolution.
648636

649-
Like lists, map patterns can also have a rest element. However, there's no
650-
well-defined notion of a map "minus" some set of matched entries. Thus, only a
651-
non-matching rest element is allowed.
637+
Ignoring extra keys also makes maps more reliable to use in irrefutable contexts
638+
where an extra key would otherwise cause a runtime exception.
652639

653-
Also, there is no ordering to entries in a map, so we only allow the `...` to
654-
appear as the last entry. Appearing anywhere else would send a confusing,
655-
meaningless signal.
640+
If you want to check that a map has a given set of keys and no others, the
641+
easiest way is to check the length in a guard:
656642

657-
In practice, this means that the only purpose of `...` in a map pattern is to
658-
allow matching a map that contains extra entries, while ignoring those entries.
659-
By default, a map pattern only matches if the map's length is exactly the same
660-
as the number of entry subpatterns. Adding a `...` element allows the map
661-
pattern to match if the map's length is *at least* the number of (non-rest)
662-
entry subpatterns (and all of those subpatterns match).
643+
```dart
644+
switch (map) {
645+
case {'a': _, 'b': _} when map.length == 2:
646+
print('Only a and b');
647+
}
648+
```
663649

664650
### Record pattern
665651

@@ -997,9 +983,9 @@ in assignments, it is useful to have an expression statement that begins with
997983
```dart
998984
var map = {'a': 1, 'b': 2};
999985
int a, b;
1000-
// more code...
986+
// More code...
1001987
1002-
// later...
988+
// Later...
1003989
{'a': a, 'b': b} = map;
1004990
```
1005991

@@ -3035,48 +3021,7 @@ To match a pattern `p` against a value `v`:
30353021
*This type test may get elided. See "Pointless type tests and legacy
30363022
types" below.*
30373023
3038-
2. Let `n` be the number of non-rest elements.
3039-
3040-
3. Check the length:
3041-
3042-
1. If `p` has a rest element and `n == 0`, then do nothing for checking
3043-
the length.
3044-
3045-
*We only call `length` on the map if needed. If the pattern is
3046-
`{...}`, then any length is allowed, so we don't even ask the map
3047-
for it.*
3048-
3049-
2. Else let `l` be the length of the map determined by calling `length`
3050-
on `v`.
3051-
3052-
3. If `p` has a rest element *(and `n > 0`)*:
3053-
3054-
1. If `l < n` then the match fails.
3055-
3056-
*When there are non-rest elements and a rest element, the map must
3057-
be at least long enough to match the non-rest elements.*
3058-
3059-
4. Else if `n > 0` *(and `p` has no rest element)*:
3060-
3061-
1. If `l != n` then the match fails.
3062-
3063-
*If there are only non-rest elements, then the map must have exactly
3064-
the same number of elements.*
3065-
3066-
5. Else `p` is empty:
3067-
3068-
1. If `l > 0` then the match fails.
3069-
3070-
*An empty map pattern can match only empty maps. Note that this
3071-
treats a misbehaving map whose `length` is negative as an empty map.
3072-
This is important so that a set of map patterns that is clearly
3073-
exhaustive over well-behaving maps will also cover a misbehaving
3074-
one.*
3075-
3076-
*These match failures become runtime exceptions if the map pattern is
3077-
in an irrefutable context.*
3078-
3079-
4. For each non-rest entry in `p`, in source order:
3024+
2. For each entry in `p`, in source order:
30803025
30813026
*Unlike in list patterns, we don't skip wildcard subpatterns. In a map
30823027
pattern, you may want to use a `_` value subpattern to detect whether a
@@ -3123,7 +3068,7 @@ To match a pattern `p` against a value `v`:
31233068
4. Else, match `r` against this entry's value subpattern. If it does
31243069
not match, the map does not match.
31253070
3126-
5. The match succeeds if all entry subpatterns match.
3071+
3. The match succeeds if all entry subpatterns match.
31273072
31283073
* **Record**:
31293074
@@ -3434,9 +3379,7 @@ To bind invocation keys in a pattern `p` using parent invocation `i`:
34343379
34353380
* **Map**:
34363381
3437-
1. Bind `i : ("length", [])` to the `length` getter invocation.
3438-
3439-
2. For each entry in `p`:
3382+
1. For each entry in `p`:
34403383
34413384
1. Bind `i : ("containsKey()", [key])` to the `containsKey()`
34423385
invocation where `key` is entry's key constant value.
@@ -3556,6 +3499,20 @@ Here is one way it could be broken down into separate pieces:
35563499
35573500
## Changelog
35583501
3502+
### 2.29
3503+
3504+
- Map patterns no longer check length.
3505+
3506+
- Remove `...` from map patterns since it is redundant with the previous
3507+
change.
3508+
3509+
- Make it an error to have an empty map pattern. Since map patterns don't
3510+
check their length, an empty map pattern will match all maps, which is
3511+
likely to confuse users. For now, to minimize confusion, we just disallow
3512+
it.
3513+
3514+
- Make it no longer an error for map patterns to have duplicate keys.
3515+
35593516
### 2.28
35603517
35613518
- Clarify that when downwards is used to infer type arguments for an object

0 commit comments

Comments
 (0)