Skip to content

Commit 2af79ef

Browse files
committed
[patterns] Add a shorthand for destructuring named fields to variables.
1 parent 8c8b512 commit 2af79ef

File tree

1 file changed

+81
-17
lines changed

1 file changed

+81
-17
lines changed

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

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

55
Status: In progress
66

7-
Version 1.1 (see [CHANGELOG](#CHANGELOG) at end)
7+
Version 1.2 (see [CHANGELOG](#CHANGELOG) at end)
88

99
## Summary
1010

@@ -557,10 +557,24 @@ expressions evaluate to equivalent values.
557557
A record pattern destructures fields from a record.
558558

559559
```
560-
recordBinder ::= '(' recordFieldBinders ')'
560+
recordBinder ::= '(' recordFieldBinders ')'
561561
562-
recordFieldBinders ::= recordFieldBinder ( ',' recordFieldBinder )* ','?
563-
recordFieldBinder ::= ( identifier ':' )? binder
562+
recordFieldBinders ::= recordFieldBinder ( ',' recordFieldBinder )* ','?
563+
recordFieldBinder ::= ( identifier ':' )? binder
564+
| identifier ':'
565+
```
566+
567+
Each field is either a binder which destructures a positional field, or a binder
568+
prefixed with an identifier and `:` which destructures a named field.
569+
570+
When destructuring named fields, it's common to want to bind the resulting value
571+
to a variable with the same name. As a convenience, the binder can be omitted on
572+
a named field. In that case, the field implicitly contains a variable binder
573+
subpattern with the same name. These are equivalent:
574+
575+
```dart
576+
var (first: first, second: second) = (first: 1, second: 2);
577+
var (first:, second:) = (first: 1, second: 2);
564578
```
565579

566580
**TODO: Allow a `...` element in order to ignore some positional fields while
@@ -753,7 +767,23 @@ Destructures fields from records and objects.
753767
recordMatcher ::= '(' recordFieldMatchers ')'
754768
755769
recordFieldMatchers ::= recordFieldMatcher ( ',' recordFieldMatcher )* ','?
756-
recordFieldMatcher ::= ( identifier ':' )? matcher
770+
recordFieldMatcher ::= ( identifier ':' )? matcher
771+
| identifier ':'
772+
```
773+
774+
Each field is either a positional matcher which destructures a positional field,
775+
or a matcher prefixed with an identifier and `:` which destructures a named
776+
field.
777+
778+
As with record binders, a named field without a matcher is implicitly treated as
779+
containing a variable matcher with the same name as the field. The variable is
780+
always `final`. These cases are equivalent:
781+
782+
```dart
783+
switch (obj) {
784+
case (first: final first, second: final second): ...
785+
case (first:, second:): ...
786+
}
757787
```
758788

759789
**TODO: Add a `...` syntax to allow ignoring positional fields?**
@@ -843,9 +873,9 @@ var (a, b) = (1, 2);
843873
```
844874

845875
Here, the `(a, b)` pattern is being matched against the expression `(1, 2)`.
846-
When a pattern contains subpatterns, those subpatterns are matched against
847-
values destructured from the value the outer pattern is matched against. Here,
848-
`a` is matched against `1` and `b` is matched against `2`.
876+
When a pattern contains subpatterns, each subpattern is matched against a value
877+
destructured from the value that the outer pattern is matched against. Here, `a`
878+
is matched against `1` and `b` is matched against `2`.
849879

850880
When calculating the context type schema or static type of a pattern, any
851881
occurrence of `typePattern` in a type is treated as `Object?`.
@@ -899,7 +929,7 @@ getters.
899929

900930
**TODO: Type inference doesn't currently look at getter return types to infer
901931
the type arguments of a generic class's constructor, so more work is needed
902-
here.**
932+
here if we want this to actually infer type arguments.**
903933

904934
The context type schema for a pattern `p` is:
905935

@@ -917,11 +947,12 @@ The context type schema for a pattern `p` is:
917947
* **Record binder or matcher**:
918948
* If the pattern has any positional fields, then the base type schema is
919949
`Destructure_n_<F...>` where `_n_` is the number of fields and `F...` is
920-
the context type schema of all of the positional fields.
950+
the context type schemas of all of the positional fields.
921951
* Else the base type schema is `Object?`.
922952
* The base type schema is extended with getters for each named field
923953
subpattern in `p` where each getter's type schema is the type schema of
924-
the corresponding subpattern.
954+
the corresponding subpattern. (If there is no subpattern because it's
955+
an implicit variable pattern like `(field:)`, the type schema is `?`.)
925956

926957
* **Variable binder**:
927958
* If `p` has a type annotation, the context type schema is that type.
@@ -940,7 +971,7 @@ The context type schema for a pattern `p` is:
940971
constraint?**
941972

942973
* **Literal matcher** or **constant matcher**: The context type schema is the
943-
static type of the pattern's value expression.
974+
static type of the pattern's constant value expression.
944975

945976
* **Declaration matcher**: The context type schema is the same as the context
946977
type schema of the inner binder.
@@ -980,8 +1011,8 @@ Putting this together, it means the process of completely inferring the types of
9801011
a construct using patterns works like:
9811012

9821013
1. Calculate the context type schema of the pattern.
983-
2. Use that in downwards inference to calculate the type of the value.
984-
3. Use that to calculate the static type of a pattern.
1014+
2. Use that in downwards inference to calculate the type of the matched value.
1015+
3. Use that to calculate the static type of the pattern.
9851016

9861017
The static type of a pattern `p` being matched against a value of type `M` is:
9871018

@@ -1056,13 +1087,37 @@ The static type of a pattern `p` being matched against a value of type `M` is:
10561087
10571088
* **Record binder or matcher**:
10581089
1090+
1. Calculate the static types of the field subpatterns:
1091+
1092+
1. It is a compile-time error if there are positional fields, `M` is
1093+
not `dynamic`, and `M` does not implement `Destructure_n_` with as
1094+
many type arguments as there are positional fields.
1095+
1096+
1. Calculate the type of each of `f`'s positional field subpatterns
1097+
using the corresponding type argument in `M`'s implementation of
1098+
`Destructure_n_` as the matched value type.
1099+
1100+
1. Calculate the type of `f`'s named field subpatterns using the
1101+
return type of the getter on `M` with the same name as the field
1102+
as the matched value type. If `M` is `dynamic`, then use `dynamic`
1103+
as the matched value type. It is a compile-time error if `M` is
1104+
not `dynamic` and does not have a getter whose name matches the
1105+
subpattern's field name.
1106+
1107+
(If the named field has no subpattern like `(field:)`, treat it as
1108+
if it has a variable subpattern with the same name as the field and
1109+
calculate the static type of that subpattern like a normal variable
1110+
pattern.)
1111+
10591112
1. If `p` has any positional fields, then the static type of `p` is
10601113
`Destructure_n_<args...>` where `_n_` is the number of positional
10611114
fields and `args...` is a type argument list built from the static
10621115
types of the positional field subpatterns, in order.
10631116
10641117
2. Else the static type of `p` is `Object?`. *You can destructure named
1065-
fields on an object of any type by calling its getters.*
1118+
fields on an object of any type by calling its getters. In other words,
1119+
named fields are treated structurally and don't form part of the record
1120+
pattern's overall static type.*
10661121
10671122
3. If `M` is not `dynamic`:
10681123
* It is a compile-time error if `p` has a field with name `n` and `M`
@@ -1115,7 +1170,9 @@ patterns binds depend on what kind of pattern it is:
11151170
11161171
* **List binder or matcher**, **map binder or matcher**, or **record binder or
11171172
matcher**: These do not introduce variables themselves but may contain type
1118-
patterns and subpatterns that do.
1173+
patterns and subpatterns that do. A named record field with no subpattern
1174+
implicitly defines a variable with the same name as the field. If the
1175+
pattern is a matcher, the variable is `final`.
11191176
11201177
* **Literal matcher**, **constant matcher**, or **wildcard binder or
11211178
matcher**: These do not introduce any variables.
@@ -1387,7 +1444,9 @@ To match a pattern `p` against a value `v`:
13871444
treating that as a match failure.*
13881445

13891446
2. Match the subpattern of `f` against `r`. If the match fails,
1390-
the record match fails.
1447+
the record match fails. (If `f` has no subpattern because it's an
1448+
implicit field pattern like `(field:)`, treat it like a the
1449+
subpattern is a variable pattern with the same name.)
13911450

13921451
3. If all field subpatterns match, the record pattern matches.
13931452

@@ -1459,6 +1518,11 @@ main() {
14591518

14601519
## Changelog
14611520

1521+
### 1.2
1522+
1523+
- Add a shorthand for destructuring a named record field to a variable with
1524+
the same name.
1525+
14621526
### 1.1
14631527

14641528
- Copy editing and clean up.

0 commit comments

Comments
 (0)