@@ -4,7 +4,7 @@ Author: Bob Nystrom
44
55Status: In progress
66
7- Version 1.3 (see [ CHANGELOG] ( #CHANGELOG ) at end)
7+ Version 1.4 (see [ CHANGELOG] ( #CHANGELOG ) at end)
88
99## Motivation
1010
@@ -50,7 +50,8 @@ var tuple = ("first", 2, true);
5050```
5151
5252A 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:
5455
5556``` dart
5657var record = (number: 123, name: "Main", type: "Street");
@@ -63,26 +64,29 @@ record has a series of positional fields, and a collection of named fields:
6364var record = (1, 2, a: 3, b: 4);
6465```
6566
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
7169first-class object, literally a subtype of Object. Its fields cannot be
7270modified, but may contain references to mutable objects. It implements
7371` hashCode ` and ` == ` structurally based on its fields to provide value-type
7472semantics.
7573
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+
7680## Core library
7781
7882These primitive types are added to ` dart:core ` :
7983
8084### The ` Record ` class
8185
8286A 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.*
8690
8791## Syntax
8892
@@ -92,29 +96,28 @@ A record is created using a record expression, like the examples above. The
9296grammar is:
9397
9498```
95- // Existing rule:
9699literal ::= record
97- | // Existing literal productions...
100+ | // Existing literal productions...
98101record ::= '(' recordField ( ',' recordField )* ','? ')'
99102recordField ::= (identifier ':' )? expression
100103```
101104
102105This 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:
107108
108- It is a compile-time error if a record has any of:
109+ * The same field name more than once.
109110
110- * the same field name more than once.
111+ * No named fields and only one positional field. * This avoids ambiguity with
112+ parenthesized expressions.*
111113
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 ` .
114115
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.*
118121
119122### Record type annotations
120123
@@ -123,9 +126,8 @@ record type annotations is:
123126
124127```
125128// Existing rule:
126- typeNotVoidNotFunction ::= typeName typeArguments? '?'?
127- | 'Function' '?'?
128- | recordType // New production.
129+ typeNotVoidNotFunction ::= recordType
130+ | // Existing typeNotVoidNotFunction productions...
129131
130132recordType ::= '(' recordTypeFields ','? ')'
131133 | '(' ( recordTypeFields ',' )?
@@ -139,15 +141,28 @@ recordTypeNamedFields ::= '{' recordTypeNamedField
139141recordTypeNamedField ::= type identifier
140142```
141143
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:
144159
145160``` dart
146161(int, String, bool) triple;
147162```
148163
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:
151166
152167``` dart
153168({int n, String s}) pair;
@@ -168,12 +183,6 @@ parentheses:
168183
169184Like record expressions, a record type must have at least one field.
170185
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-
177186## Static semantics
178187
179188We 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
193202getters for each named field where the name of the getter is the field's name
194203and the getter's type is the field's type.
195204
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.*
199207
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:
202210
203211``` 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;
208215}
209216```
210217
@@ -228,7 +235,7 @@ of the corresponding field in the original types.
228235``` dart
229236(num, String) a = (1.2, "s");
230237(int, Object) b = (2, true);
231- var c = cond ? a : b; // (num, Object)
238+ var c = cond ? a : b; // c has type ` (num, Object)`.
232239```
233240
234241Likewise, 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:
237244``` dart
238245a((num, String)) {}
239246b((int, Object)) {}
240- var c = cond ? a : b; // Function((int, String))
247+ var c = cond ? a : b; // c has type ` Function((int, String))`.
241248```
242249
243250The least upper bound of two record types with different shapes is ` Record ` .
244251
245252``` dart
246253(num, String) a = (1.2, "s");
247254(num, String, bool) b = (2, "s", true);
248- var c = cond ? a : b; // Record
255+ var c = cond ? a : b; // c has type ` Record`.
249256```
250257
251258The greatest lower bound of records with different shapes is ` Never ` .
@@ -260,16 +267,6 @@ fields are) and collection literals.
260267
261268## Runtime semantics
262269
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-
273270### Records
274271
275272#### Members
@@ -284,17 +281,18 @@ The `toString()` method's behavior is unspecified.
284281
285282Records behave similar to other primitive types in Dart with regards to
286283equality. 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.
289287
290288``` dart
291- var a = (1, 2);
292- var b = (1, 2 );
289+ var a = (x: 1, 2);
290+ var b = (2, x: 1 );
293291print(a == b); // true.
294292```
295293
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.
298296
299297#### Identity
300298
@@ -362,6 +360,21 @@ covariant in their field types.
362360
363361## CHANGELOG
364362
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+
365378### 1.3
366379
367380- Remove the ` Destructure_n_ ` interfaces.
0 commit comments