@@ -4,7 +4,7 @@ Author: Erik Ernst
44
55Status: Draft
66
7- Version: 1.3
7+ Version: 1.4
88
99Experiment flag: primary-constructors
1010
@@ -81,16 +81,15 @@ The basic idea is that a parameter list that occurs just after the class
8181name specifies both a constructor declaration and a declaration of one
8282instance variable for each formal parameter in said parameter list.
8383
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.
8987
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).
9493
9594The parameter list uses the same syntax as constructors and other functions
9695(specified in the grammar by the non-terminal ` <formalParameterList> ` ).
@@ -211,7 +210,7 @@ class const Point(int x, int y);
211210enum E(String s) { one('a'), two('b') }
212211```
213212
214- Finally, an extension type declaration is specified to use a
213+ Note that an extension type declaration is specified to use a
215214primary constructor (in that case there is no other choice,
216215it is in the grammar rules):
217216
@@ -276,7 +275,7 @@ class const D<TypeVariable extends Bound>.named(
276275]) extends A with M implements B, C;
277276```
278277
279- Finally, it is possible to specify assertions on a primary constructor,
278+ It is possible to specify assertions on a primary constructor,
280279just like the ones that we can specify in the initializer list of a
281280regular (not primary) constructor:
282281
@@ -292,6 +291,48 @@ class Point {
292291class Point(int x, int y): assert(0 <= x && x <= y * y);
293292```
294293
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+
295336## Specification
296337
297338### Syntax
@@ -309,19 +350,16 @@ constructors as well.
309350<primaryConstructorNoConst> ::= // New rule.
310351 <typeIdentifier> <typeParameters>?
311352 ('.' <identifierOrNew>)? <formalParameterList>
312- <assertions>?
313-
314- <assertions> ::= // New rule.
315- ':' <assertion> (',' <assertion>)*
353+ <initializers>?
316354
317355<classNamePartNoConst> ::= // New rule.
318356 <primaryConstructorNoConst>
319357 | <typeWithParameters>;
320-
358+
321359<classNamePart> ::= // New rule.
322360 'const'? <primaryConstructorNoConst>
323361 | <typeWithParameters>;
324-
362+
325363<typeWithParameters> ::= <typeIdentifier> <typeParameters>?
326364
327365<classBody> ::= // New rule.
@@ -378,8 +416,11 @@ and `final`. *A final instance variable cannot be covariant, because being
378416covariant is a property of the setter.*
379417
380418Conversely, 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.*
383424
384425The desugaring consists of the following steps, where _ D_ is the class,
385426extension 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
393434occur as follows:
394435
395436Assume 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? ` .
403445
404446* Dart has traditionally assumed the type ` dynamic ` in such situations. We
405447have chosen the more strictly checked type ` Object? ` instead, in order to
406448avoid introducing run-time type checking implicitly.*
407449
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.
416452
417453* 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.*
446459
447460Next, _ k_ has the modifier ` const ` iff the keyword ` const ` occurs just
448461before the name of _ D_ , or _ D_ is an ` enum ` declaration.
@@ -461,23 +474,25 @@ type parameter list, if any, and `.id`, if any.
461474The formal parameter list _ L2_ of _ k_ is identical to _ L_ , except that each
462475formal parameter is processed as follows.
463476
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.
471482
472483- 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.
481496 Next, an instance variable declaration of the form ` T p; ` or ` final T p; `
482497 is added to _ D2_ . The instance variable has the modifier ` final ` if the
483498 parameter in _ L_ is ` final ` , or _ D_ is an ` extension type ` declaration,
@@ -487,48 +502,45 @@ default value.
487502 removed from the parameter in _ L2_ , and it is added to the instance
488503 variable declaration named ` p ` .
489504
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.
498507
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.*
504511
505512Finally, _ k_ is added to _ D2_ , and _ D_ is replaced by _ D2_ .
506513
507514### Discussion
508515
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+
511525
512526``` dart
513- class B extends A { // OK.
514- B(int a): super(a);
515- }
527+ // Use super parameters.
516528
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.
519534
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);
529536
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.
532544
533545There was a [ proposal from Bob] [ ] that the primary constructor should be
534546expressed at the end of the class header, in order to avoid readability
@@ -598,17 +610,15 @@ class Point {
598610class final Point(int x, int y); // Not supported!
599611```
600612
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.
612622
613623Finally, we could allow a primary constructor to be declared in the body of
614624a 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 {
631641```
632642
633643This 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:
639648
640649``` dart
641650// Current syntax.
@@ -698,13 +707,20 @@ class E extends A {
698707```
699708
700709We 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
702711[ separate proposal] [ inferred-required ] .
703712
704713[ inferred-required ] : https://github.com/dart-lang/language/blob/main/working/0015-infer-required/feature-specification.md
705714
706715### Changelog
707716
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+
7087241.3 - July 12, 2024
709725
710726* Add support for assertions in the primary constructor. Add support for
0 commit comments