@@ -4,7 +4,7 @@ Author: Erik Ernst
4
4
5
5
Status: Draft
6
6
7
- Version: 1.3
7
+ Version: 1.4
8
8
9
9
Experiment flag: primary-constructors
10
10
@@ -81,16 +81,15 @@ The basic idea is that a parameter list that occurs just after the class
81
81
name specifies both a constructor declaration and a declaration of one
82
82
instance variable for each formal parameter in said parameter list.
83
83
84
- A primary constructor cannot have a body, and it cannot have an normal
85
- initializer list (and hence, it cannot have a superinitializer, e.g.,
86
- ` super.name(...) ` ). However, it can have assertions, it can have
87
- initializing formals (` this.p ` ) and it can have super parameters
88
- (` super.p ` ).
84
+ A primary constructor cannot have a body. However, it can have assertions,
85
+ it can have initializing formals (` this.p ` ), it can have super parameters
86
+ (` super.p ` ), and it can have an initializer list.
89
87
90
- The motivation for these restrictions is that a primary constructor is
91
- intended to be small and easy to read at a glance. If more machinery is
92
- needed then it is always possible to express the same thing as a body
93
- constructor (i.e., any constructor which isn't a primary constructor).
88
+ The motivation for the missing support for a body is that a primary
89
+ constructor is intended to be small and easy to read at a glance. If more
90
+ machinery is needed then it is always possible to express the same thing as
91
+ a body constructor (i.e., any constructor which isn't a primary
92
+ constructor).
94
93
95
94
The parameter list uses the same syntax as constructors and other functions
96
95
(specified in the grammar by the non-terminal ` <formalParameterList> ` ).
@@ -211,7 +210,7 @@ class const Point(int x, int y);
211
210
enum E(String s) { one('a'), two('b') }
212
211
```
213
212
214
- Finally, an extension type declaration is specified to use a
213
+ Note that an extension type declaration is specified to use a
215
214
primary constructor (in that case there is no other choice,
216
215
it is in the grammar rules):
217
216
@@ -276,7 +275,7 @@ class const D<TypeVariable extends Bound>.named(
276
275
]) extends A with M implements B, C;
277
276
```
278
277
279
- Finally, it is possible to specify assertions on a primary constructor,
278
+ It is possible to specify assertions on a primary constructor,
280
279
just like the ones that we can specify in the initializer list of a
281
280
regular (not primary) constructor:
282
281
@@ -292,6 +291,48 @@ class Point {
292
291
class Point(int x, int y): assert(0 <= x && x <= y * y);
293
292
```
294
293
294
+ Finally, it is possible to use an initializer list in order to
295
+ invoke a superconstructor and/or initialize some explicitly
296
+ declared instance variables with a computed value.
297
+
298
+ ``` dart
299
+ // Current syntax.
300
+
301
+ class A {
302
+ final int x;
303
+ const A.someName(this.x);
304
+ }
305
+
306
+ class B extends A {
307
+ final String s1;
308
+ final String s2;
309
+
310
+ const B(int x, int y, {required this.s2})
311
+ : s1 = y.toString(), super.someName(x + 1);
312
+ }
313
+
314
+ // Using primary constructors.
315
+
316
+ class const A.someName(int x);
317
+
318
+ class const B(int x, int y, {required String s2})
319
+ : s1 = y.toString(), assert(s2.isNotEmpty), super.someName(x + 1)
320
+ extends A {
321
+ final String s1;
322
+ }
323
+ ```
324
+
325
+ A formal parameter of a primary constructor which is used in a variable
326
+ initialization or in a superinitializer does not implicitly induce an
327
+ instance variable.
328
+
329
+ In particular, ` int x ` does not give rise to an instance variable in the
330
+ class ` B ` because ` x ` is used in the superinitializer. Similarly, ` int y `
331
+ does not give rise to an instance variable because it is used in the
332
+ initializer list element ` s1 = y.toString() ` . However, ` s2 ` _ does_ give
333
+ rise to an instance variable (it is used in the assertion, but that does
334
+ not prevent the instance variable from being induced).
335
+
295
336
## Specification
296
337
297
338
### Syntax
@@ -309,19 +350,16 @@ constructors as well.
309
350
<primaryConstructorNoConst> ::= // New rule.
310
351
<typeIdentifier> <typeParameters>?
311
352
('.' <identifierOrNew>)? <formalParameterList>
312
- <assertions>?
313
-
314
- <assertions> ::= // New rule.
315
- ':' <assertion> (',' <assertion>)*
353
+ <initializers>?
316
354
317
355
<classNamePartNoConst> ::= // New rule.
318
356
<primaryConstructorNoConst>
319
357
| <typeWithParameters>;
320
-
358
+
321
359
<classNamePart> ::= // New rule.
322
360
'const'? <primaryConstructorNoConst>
323
361
| <typeWithParameters>;
324
-
362
+
325
363
<typeWithParameters> ::= <typeIdentifier> <typeParameters>?
326
364
327
365
<classBody> ::= // New rule.
@@ -378,8 +416,11 @@ and `final`. *A final instance variable cannot be covariant, because being
378
416
covariant is a property of the setter.*
379
417
380
418
Conversely, it is not an error for the modifier ` covariant ` to occur on
381
- other formal parameters of a primary constructor (this extends the
382
- existing allowlist of places where ` covariant ` can occur).
419
+ another formal parameter _ p_ of a primary constructor (this extends the
420
+ existing allowlist of places where ` covariant ` can occur), unless _ p_
421
+ occurs in an initializer element of the primary constructor which is not
422
+ an assertion. * For example, ` class C(covariant int p): super(p + 1); ` is an
423
+ error.*
383
424
384
425
The desugaring consists of the following steps, where _ D_ is the class,
385
426
extension type, or enum declaration in the program that includes a primary
@@ -393,56 +434,28 @@ Where no processing is mentioned below, _D2_ is identical to _D_. Changes
393
434
occur as follows:
394
435
395
436
Assume that ` p ` is an optional formal parameter in _ D_ which is not an
396
- initializing formal and not a super parameter. Assume that ` p ` does not
397
- have a declared type, but it does have a default value whose static type in
398
- the empty context is a type (not a type schema) ` T ` which is not ` Null ` . In
399
- that case ` p ` is considered to have the declared type ` T ` . When ` T ` is
400
- ` Null ` , ` p ` is considered to have the declared type ` Object? ` . If ` p `
401
- does not have a declared type nor a default value then ` p ` is considered
402
- to have the declared type ` Object? ` .
437
+ initializing formal, and not a super parameter. Assume that ` p ` does not
438
+ occur in the initializer list of _ D_ , except possibly in some assertions.
439
+ Assume that ` p ` does not have a declared type, but it does have a default
440
+ value whose static type in the empty context is a type (not a type schema)
441
+ ` T ` which is not ` Null ` . In that case ` p ` is considered to have the
442
+ declared type ` T ` . When ` T ` is ` Null ` , ` p ` is considered to have the
443
+ declared type ` Object? ` . If ` p ` does not have a declared type nor a default
444
+ value then ` p ` is considered to have the declared type ` Object? ` .
403
445
404
446
* Dart has traditionally assumed the type ` dynamic ` in such situations. We
405
447
have chosen the more strictly checked type ` Object? ` instead, in order to
406
448
avoid introducing run-time type checking implicitly.*
407
449
408
- The current scope of the formal parameter list of the primary constructor
409
- in _ D_ is the type parameter scope of the enclosing class, if it exists,
410
- and otherwise the enclosing library scope * (in other words, the default
411
- values cannot see declarations in the class body)* .
412
-
413
- * Note that every occurrence of a type variable of _ D_ in a default value is
414
- an error, because no constant expression contains a type variable. Hence,
415
- we can proceed under the assumption that there are no such occurrences.*
450
+ The current scope of the formal parameter list and initializer list (if
451
+ any) of the primary constructor in _ D_ is the body scope of the class.
416
452
417
453
* We need to ensure that the meaning of default value expressions is
418
- well-defined, taking into account that the primary constructor is actually
419
- located in a different scope than normal non-primary constructors. One way
420
- to specify this is to use a syntactic transformation:*
421
-
422
- Every default value in the primary constructor of _ D_ is replaced by a
423
- fresh private name ` _n ` , and a constant variable named ` _n ` is added to the
424
- top-level of the current library, with an initializing expression which is
425
- said default value.
426
-
427
- * This means that we can move the parameter declarations including the
428
- default value without changing its meaning. Implementations are free to
429
- use this particular desugaring based technique, or any other technique
430
- which has the same observable behavior. In particular, it should not be
431
- possible for such a default value to obtain a new meaning because an
432
- identifier in the default value resolves to a declaration in the class body
433
- when it occurs in _ k_ after the transformation, but it used to resolve to
434
- a top-level or imported declaration before the transformation.*
435
-
436
- For each of these constant variable declarations, the declared type is the
437
- formal parameter type of the corresponding formal parameter, except: In the
438
- case where the corresponding formal parameter has a type ` T ` where one or
439
- more type variables declared by _ D_ occur, the declared type of the
440
- constant variable is the least closure of ` T ` with respect to the type
441
- parameters of the class.
442
-
443
- * For example, if the default value is ` const [] ` and the parameter type is
444
- ` List<X> ` , the top-level constant will be ` const List<Never> _n = []; ` for
445
- some fresh name ` _n ` .*
454
+ well-defined, taking into account that the primary constructor is
455
+ physically located in a different scope than normal non-primary
456
+ constructors. We do this by specifying the current scope explicitly as the
457
+ body scope, in spite of the fact that the primary constructor is actually
458
+ placed outside the braces that delimit the class body.*
446
459
447
460
Next, _ k_ has the modifier ` const ` iff the keyword ` const ` occurs just
448
461
before the name of _ D_ , or _ D_ is an ` enum ` declaration.
@@ -461,23 +474,25 @@ type parameter list, if any, and `.id`, if any.
461
474
The formal parameter list _ L2_ of _ k_ is identical to _ L_ , except that each
462
475
formal parameter is processed as follows.
463
476
464
- In particular, the formal parameters in _ L_ and _ L2_ occur in the same
465
- order, and mandatory positional parameters remain mandatory, and named
466
- parameters preserve the name and the modifier ` required ` , if any. An
467
- optional positional or named parameter remains optional; if it has a
468
- default value ` d ` in _ L_ then it has the transformed default value ` _n ` in
469
- _ L2_ , where ` _n ` is the name of the constant variable created for that
470
- default value.
477
+ The formal parameters in _ L_ and _ L2_ occur in the same order, and
478
+ mandatory positional parameters remain mandatory, and named parameters
479
+ preserve the name and the modifier ` required ` , if any. An optional
480
+ positional or named parameter remains optional; if it has a default value
481
+ ` d ` in _ L_ then it has the default value ` d ` in _ L2_ as well.
471
482
472
483
- An initializing formal parameter * (e.g., ` this.x ` )* is copied from _ L_ to
473
- _ L2_ , using said transformed default value, if any, and otherwise
474
- unchanged.
475
- - A super parameter is copied from _ L_ to _ L2_ using said transformed
476
- default value, if any, and is otherwise unchanged.
477
- - A formal parameter (named or positional) of the form ` T p ` or ` final T p `
478
- where ` T ` is a type and ` p ` is an identifier is replaced in _ L2_ by
479
- ` this.p ` . A parameter of the same form but with a default value uses said
480
- transformed default value.
484
+ _ L2_ , along with the default value, if any, and is otherwise unchanged.
485
+ - A super parameter is copied from _ L_ to _ L2_ along with the default
486
+ value, if any, and is otherwise unchanged.
487
+ - Assume that _ p_ is a formal parameter (named or positional) of the form
488
+ ` T p ` or ` final T p ` where ` T ` is a type and ` p ` is an identifier.
489
+ Assume that _ p_ occurs in the initializer list of _ D_ , in an element
490
+ which is not an assertion. In this case, _ p_ occurs without changes in
491
+ _ L2_ . * Note that the parameter cannot be covariant in this case, that is
492
+ an error.*
493
+ - Otherwise, a formal parameter (named or positional) of the form ` T p ` or
494
+ ` final T p ` where ` T ` is a type and ` p ` is an identifier is replaced in
495
+ _ L2_ by ` this.p ` , along with its default value, if any.
481
496
Next, an instance variable declaration of the form ` T p; ` or ` final T p; `
482
497
is added to _ D2_ . The instance variable has the modifier ` final ` if the
483
498
parameter in _ L_ is ` final ` , or _ D_ is an ` extension type ` declaration,
@@ -487,48 +502,45 @@ default value.
487
502
removed from the parameter in _ L2_ , and it is added to the instance
488
503
variable declaration named ` p ` .
489
504
490
- If there are any assertions following the formal parameter list _ L_ then
491
- _ k_ has an initializer list with the same assertions in the same order.
492
-
493
- The current scope of the assertions in _ D_ is the formal parameter
494
- initializer scope of the formal parameter list * (that is, they can see the
495
- parameters including any initializing formals, the type parameters, and
496
- everything in the library scope that isn't shadowed by the scopes in
497
- between)* .
505
+ If there is an initializer list following the formal parameter list _ L_ then
506
+ _ k_ has an initializer list with the same elements in the same order.
498
507
499
- The expressions in the assertions are subject to a transformation that
500
- preserves the resolution of every identifier in said expressions when they
501
- occur as part of the initializer list of _ k_ . * (In particular, an
502
- identifier in an assertion expression cannot resolve to a declaration in
503
- the class body)* .
508
+ * The current scope of the initializer list in _ D_ is the body scope
509
+ of the enclosing declaration, which means that they preserve their
510
+ semantics when moved into the body.*
504
511
505
512
Finally, _ k_ is added to _ D2_ , and _ D_ is replaced by _ D2_ .
506
513
507
514
### Discussion
508
515
509
- It could be argued that primary constructors should support arbitrary
510
- superinvocations using the specified superclass:
516
+ It could be argued that primary constructors should not support
517
+ superinitializers because the resulting declaration is too complex to be
518
+ conveniently readable, and developers could just write a regular primary
519
+ constructor instead.
520
+
521
+ We expect that primary constructors will in practice be small and simple,
522
+ but they may use different subsets of the expressive power of the
523
+ mechanism. For example,
524
+
511
525
512
526
``` dart
513
- class B extends A { // OK.
514
- B(int a): super(a);
515
- }
527
+ // Use super parameters.
516
528
517
- class B(int a) extends A(a); // Could be supported, but isn't!
518
- ```
529
+ class const Point2D(int x, int y);
530
+
531
+ class const Point3D(super.x, super.y, int z) extends Point2D;
532
+
533
+ // Use a named constructor and a computed super argument.
519
534
520
- There are several reasons why this is not supported. First, primary
521
- constructors should be small and easy to read. Next, it is not obvious how
522
- the superconstructor arguments would fit into a mixin application (e.g.,
523
- when the superclass is ` A with M1, M2 ` ), or how readable it would be if the
524
- superconstructor is named (` class B(int a) extends A with M1, M2.name(a); ` ).
525
- For instance, would it be obvious to all readers that the superclass is ` A `
526
- and not ` A.name ` , and that all other constructors than the primary
527
- constructor will ignore the implied superinitialization ` super.name(a) ` and
528
- do their own thing (which might be implicit)?
535
+ class A._(int x);
529
536
530
- In short, if you need to write a complex superinitialization like
531
- ` super.name(e1, otherName: e2) ` then you need to use a body constructor.
537
+ class B(int y): assert(y > 2), super._(y - 1)
538
+ extends A with Mixin1, Mixin2;
539
+ ```
540
+
541
+ Like many other language mechanisms, primary constructors need developers
542
+ to use their human judgment to create declarations that are both readable,
543
+ useful, and maintainable.
532
544
533
545
There was a [ proposal from Bob] [ ] that the primary constructor should be
534
546
expressed at the end of the class header, in order to avoid readability
@@ -598,17 +610,15 @@ class Point {
598
610
class final Point(int x, int y); // Not supported!
599
611
```
600
612
601
- Most likely, there is an easy workaround: Make the constructor ` const ` . It
602
- is very often possible to make the constructor ` const ` , even in the case
603
- where the class isn't necessarily intended to be used in constant
604
- expressions: There is no initializer list, no superinitialization, no
605
- body. The only way it can be an error to use ` const ` on a primary
606
- constructor is if the superclass doesn't have a constant constructor, or if
607
- the class has a mutable or late instance variable, or it has some
608
- non-constant expressions in instance variable declarations. (Those issues
609
- can only be created by instance variables that are declared explicitly in
610
- the class body whereas the ones that are created by primary constructor
611
- parameters will necessarily satisfy the ` const ` requirements).
613
+ There is an easy partial workaround: Make the constructor ` const ` . It is
614
+ very often possible to make the constructor ` const ` , even in the case where
615
+ the class isn't necessarily intended to be used in constant expressions:
616
+ There is no body. The only ways it can be an error to use ` const ` on a
617
+ primary constructor is if the superclass doesn't have a constant
618
+ constructor, or if the class has a mutable or late instance variable, or it
619
+ has some non-constant expressions in instance variable declarations or in
620
+ the initializer list. Using ` const ` is not a complete solution, but
621
+ probably OK in practice.
612
622
613
623
Finally, we could allow a primary constructor to be declared in the body of
614
624
a class or similar declaration, possibly using a modifier like ` primary ` ,
@@ -631,11 +641,10 @@ class D<TypeVariable extends Bound> extends A with M implements B, C {
631
641
```
632
642
633
643
This approach offers more flexibility in that a primary constructor in the
634
- body of the declaration can have initializers and a body, just like other
635
- constructors. In other words, ` primary ` on a constructor has one effect
636
- only, which is to introduce instance variables for formal parameters in the
637
- same way as a primary constructor in the header of the declaration. For
638
- example:
644
+ body of the declaration can have a body, just like other constructors. In
645
+ other words, ` primary ` on a constructor has one effect only, which is to
646
+ introduce instance variables for formal parameters in the same way as a
647
+ primary constructor in the header of the declaration. For example:
639
648
640
649
``` dart
641
650
// Current syntax.
@@ -698,13 +707,20 @@ class E extends A {
698
707
```
699
708
700
709
We may get rid of all those occurrences of ` required ` in the situation
701
- where it is a compile-time error to not have them, but that is a
710
+ where it is a compile-time error to not have them, but that is a
702
711
[ separate proposal] [ inferred-required ] .
703
712
704
713
[ inferred-required ] : https://github.com/dart-lang/language/blob/main/working/0015-infer-required/feature-specification.md
705
714
706
715
### Changelog
707
716
717
+ 1.4 - November 12, 2024
718
+
719
+ * Add support for a full initializer list (which adds elements of the form
720
+ ` x = e ` and ` super(...) ` or ` super.name(...) ` ). Add the rule that a
721
+ parameter introduces an instance variable except when used in the
722
+ initializer list.
723
+
708
724
1.3 - July 12, 2024
709
725
710
726
* Add support for assertions in the primary constructor. Add support for
0 commit comments