@@ -4,7 +4,7 @@ Author: Bob Nystrom
4
4
5
5
Status: In progress
6
6
7
- Version 1.3 (see [ CHANGELOG] ( #CHANGELOG ) at end)
7
+ Version 1.4 (see [ CHANGELOG] ( #CHANGELOG ) at end)
8
8
9
9
## Motivation
10
10
@@ -50,7 +50,8 @@ var tuple = ("first", 2, true);
50
50
```
51
51
52
52
A tuple is an ordered list of unnamed positional fields. These languages also
53
- often have ** record** types. In a record, the fields are unordered, but named:
53
+ often have ** record** types. In a record, the fields are unordered and
54
+ identified by name instead:
54
55
55
56
``` dart
56
57
var record = (number: 123, name: "Main", type: "Street");
@@ -63,26 +64,29 @@ record has a series of positional fields, and a collection of named fields:
63
64
var record = (1, 2, a: 3, b: 4);
64
65
```
65
66
66
- Very much like an argument list to a function call both in syntax and semantics.
67
- A given record may have no positional fields or no named fields, but cannot be
68
- totally empty. (There is no "unit type".)
69
-
70
- A record expression like the above examples produces a record value. This is a
67
+ The expression syntax looks much like an argument list to a function call. A
68
+ record expression like the above examples produces a record value. This is a
71
69
first-class object, literally a subtype of Object. Its fields cannot be
72
70
modified, but may contain references to mutable objects. It implements
73
71
` hashCode ` and ` == ` structurally based on its fields to provide value-type
74
72
semantics.
75
73
74
+ A record may have only positional fields or only named fields, but cannot be
75
+ totally empty. * There is no "unit type".* A record with no named fields must
76
+ have at least two positional fields. * This prevents confusion around whether a
77
+ single positional element record is equivalent to its underlying value, and
78
+ avoids a syntactic ambiguity with parenthesized expressions.*
79
+
76
80
## Core library
77
81
78
82
These primitive types are added to ` dart:core ` :
79
83
80
84
### The ` Record ` class
81
85
82
86
A built-in class ` Record ` with no members except those inherited from ` Object ` .
83
- This type cannot be constructed, extended, mixed in, or implemented by
84
- user-defined classes. * It's similar to how the ` Function ` class is the
85
- superclass for function types.*
87
+ All record types are a subtype of this class. This type cannot be constructed,
88
+ extended, mixed in, or implemented by user-defined classes. * This is similar to
89
+ how the ` Function ` class is the superclass for all function types.*
86
90
87
91
## Syntax
88
92
@@ -92,29 +96,28 @@ A record is created using a record expression, like the examples above. The
92
96
grammar is:
93
97
94
98
```
95
- // Existing rule:
96
99
literal ::= record
97
- | // Existing literal productions...
100
+ | // Existing literal productions...
98
101
record ::= '(' recordField ( ',' recordField )* ','? ')'
99
102
recordField ::= (identifier ':' )? expression
100
103
```
101
104
102
105
This is identical to the grammar for a function call argument list. There are a
103
- couple of syntactic restrictions not captured by the grammar. A parenthesized
104
- expression without a trailing comma is ambiguously either a record or grouping
105
- expression. To resolve the ambiguity, it is always treated as a grouping
106
- expression.
106
+ couple of syntactic restrictions not captured by the grammar. It is a
107
+ compile-time error if a record has any of:
107
108
108
- It is a compile-time error if a record has any of:
109
+ * The same field name more than once.
109
110
110
- * the same field name more than once.
111
+ * No named fields and only one positional field. * This avoids ambiguity with
112
+ parenthesized expressions.*
111
113
112
- * a field name that collides with the implicit name defined for a
113
- positional field (see below).
114
+ * A field named ` hashCode ` , ` runtimeType ` , ` noSuchMethod ` , or ` toString ` .
114
115
115
- * a field named ` hashCode ` , ` runtimeType ` , ` noSuchMethod ` , or ` toString ` .
116
-
117
- ** TODO: Can field names be private? If so, are they actually private?**
116
+ * A field name that starts with an underscore. * If we allow a record to have
117
+ private field names, then those fields would not be visible outside of the
118
+ library where the record was declared. That would lead to a record that has
119
+ hidden state. Two such records might unexpectedly compare unequal even
120
+ though all of the fields the user can see are equal.*
118
121
119
122
### Record type annotations
120
123
@@ -123,9 +126,8 @@ record type annotations is:
123
126
124
127
```
125
128
// Existing rule:
126
- typeNotVoidNotFunction ::= typeName typeArguments? '?'?
127
- | 'Function' '?'?
128
- | recordType // New production.
129
+ typeNotVoidNotFunction ::= recordType
130
+ | // Existing typeNotVoidNotFunction productions...
129
131
130
132
recordType ::= '(' recordTypeFields ','? ')'
131
133
| '(' ( recordTypeFields ',' )?
@@ -139,15 +141,28 @@ recordTypeNamedFields ::= '{' recordTypeNamedField
139
141
recordTypeNamedField ::= type identifier
140
142
```
141
143
142
- This is somewhat similar to a parameter list. You have zero or more positional
143
- fields where each field is a type annotation:
144
+ It is a compile-time error if a record type has any of:
145
+
146
+ * The same field name more than once.
147
+
148
+ * No named fields and only one positional field. * This isn't ambiguous, since
149
+ there are no parenthesized type expressions in Dart. But there is no reason
150
+ to allow single positional element record types when the corresponding
151
+ record values are prohibited.*
152
+
153
+ * A field named ` hashCode ` , ` runtimeType ` , ` noSuchMethod ` , or ` toString ` .
154
+
155
+ * A field name that starts with an underscore.
156
+
157
+ The syntax is similar to a function type's parameter list. You have zero or more
158
+ positional fields where each field is a type annotation:
144
159
145
160
``` dart
146
161
(int, String, bool) triple;
147
162
```
148
163
149
- Then an optional brace-delimited section for named fields. Each named field is
150
- a type and name pair:
164
+ Then a brace-delimited section for named fields. Each named field is a type and
165
+ name pair:
151
166
152
167
``` dart
153
168
({int n, String s}) pair;
@@ -168,12 +183,6 @@ parentheses:
168
183
169
184
Like record expressions, a record type must have at least one field.
170
185
171
- Unlike expressions, a trailing comma is not required in the single positional
172
- field case. ` (int) ` is a valid record type and is distinct from the type ` int ` .
173
-
174
- It is a compile-time error if two record type fields have the same name or if
175
- a named field collides with the implicit name of a positional field.
176
-
177
186
## Static semantics
178
187
179
188
We define ** shape** to mean the number of positional fields (the record's
@@ -193,18 +202,16 @@ A record type declares all of the members defined on `Object`. It also exposes
193
202
getters for each named field where the name of the getter is the field's name
194
203
and the getter's type is the field's type.
195
204
196
- In addition, for each positional field, the record type declares a getter named
197
- ` field<n> ` where ` <n> ` is the number of preceding positional fields and where
198
- the getter's type is the field's type.
205
+ Positional fields are not exposed as getters. * Record patterns in pattern
206
+ matching can be used to access a record's positional fields.*
199
207
200
- For example, the record expression ` (1, s: "string" , true) ` has a record type
201
- whose signature is like:
208
+ For example, the record expression ` (1.2, name: 's' , true, count: 3 ) ` has a
209
+ record type whose signature is like:
202
210
203
211
``` dart
204
- class {
205
- int get field0;
206
- String get s;
207
- bool get field1;
212
+ class extends Record {
213
+ String get name;
214
+ int get count;
208
215
}
209
216
```
210
217
@@ -228,7 +235,7 @@ of the corresponding field in the original types.
228
235
``` dart
229
236
(num, String) a = (1.2, "s");
230
237
(int, Object) b = (2, true);
231
- var c = cond ? a : b; // (num, Object)
238
+ var c = cond ? a : b; // c has type ` (num, Object)`.
232
239
```
233
240
234
241
Likewise, the greatest lower bound of two record types with the same shape is
@@ -237,15 +244,15 @@ the greatest lower bound of their component fields:
237
244
``` dart
238
245
a((num, String)) {}
239
246
b((int, Object)) {}
240
- var c = cond ? a : b; // Function((int, String))
247
+ var c = cond ? a : b; // c has type ` Function((int, String))`.
241
248
```
242
249
243
250
The least upper bound of two record types with different shapes is ` Record ` .
244
251
245
252
``` dart
246
253
(num, String) a = (1.2, "s");
247
254
(num, String, bool) b = (2, "s", true);
248
- var c = cond ? a : b; // Record
255
+ var c = cond ? a : b; // c has type ` Record`.
249
256
```
250
257
251
258
The greatest lower bound of records with different shapes is ` Never ` .
@@ -260,16 +267,6 @@ fields are) and collection literals.
260
267
261
268
## Runtime semantics
262
269
263
- ### The ` Record ` type
264
-
265
- The ` positionalFields() ` method takes a record and returns an ` Iterable ` of all
266
- of the record's positional fields in order.
267
-
268
- The ` namedFields() ` method takes a record and returns a Map with entries for
269
- each named field in the record where each key is the field's name and the
270
- corresponding value is the value of that field. (The methods are static to avoid
271
- colliding with fields in an actual record object.)
272
-
273
270
### Records
274
271
275
272
#### Members
@@ -284,17 +281,18 @@ The `toString()` method's behavior is unspecified.
284
281
285
282
Records behave similar to other primitive types in Dart with regards to
286
283
equality. They implement ` == ` such that two records are equal iff they have the
287
- same shape and all corresponding pairs of fields are equal (determined using
288
- ` == ` ).
284
+ same shape and all corresponding pairs of fields are equal. Fields are compared
285
+ for equality by calling ` == ` on the corresponding field values in the same
286
+ order that ` == ` was called on the records.
289
287
290
288
``` dart
291
- var a = (1, 2);
292
- var b = (1, 2 );
289
+ var a = (x: 1, 2);
290
+ var b = (2, x: 1 );
293
291
print(a == b); // true.
294
292
```
295
293
296
- The implementation of ` hashCode ` follows this. Two records that are equal have
297
- the same hash code.
294
+ The implementation of ` hashCode ` follows this. Two records that are equal must
295
+ have the same hash code.
298
296
299
297
#### Identity
300
298
@@ -362,6 +360,21 @@ covariant in their field types.
362
360
363
361
## CHANGELOG
364
362
363
+ ### 1.4
364
+
365
+ - Remove the reflective static members on ` Record ` . Like other reflective
366
+ features, supporting these operations may incur a global cost in generated
367
+ code size for unknown benefit (#1275 , #1277 ).
368
+
369
+ - Remove support for single positional element records. They don't have any
370
+ current use and are a syntactic wart. If we later add support for spreading
371
+ argument lists and single element positional records become useful, we can
372
+ re-add them then.
373
+
374
+ - Remove synthesized getters for positional fields. This avoids problems if a
375
+ positional field's synthesized getter collides with an explicit named field
376
+ (#1291 ).
377
+
365
378
### 1.3
366
379
367
380
- Remove the ` Destructure_n_ ` interfaces.
0 commit comments