@@ -13,11 +13,6 @@ one constructor and a set of instance variables to be specified in a concise
1313form in the header of the declaration. In order to use this feature, the given
1414constructor must satisfy certain constraints, e.g., it cannot have a body.
1515
16- A primary constructor can also be declared in the body of a class or
17- similar declaration, using the modifier ` primary ` , in which case it can
18- have an initializer list and a body, and it still has the ability to
19- introduce instance variable declarations implicitly.
20-
2116One variant of this feature has been proposed in the [ struct proposal] [ ] ,
2217several other proposals have appeared elsewhere, and prior art exists in
2318languages like [ Kotlin] [ kotlin primary constructors ] and Scala (with
@@ -106,9 +101,7 @@ and normal instance variable declarations, and it is probably a useful property
106101that the primary constructor uses a formal parameter syntax which is completely
107102like that of any other formal parameter list.
108103
109- Just use a normal declaration and use an initializing formal in a primary
110- constructor to initialize it from the primary constructor, if needed. An
111- ` external ` instance variable amounts to an ` external ` getter and an
104+ An ` external ` instance variable amounts to an ` external ` getter and an
112105` external ` setter. Such "variables" cannot be initialized by an
113106initializing formal anyway, so they will just need to be declared using a
114107normal ` external ` variable declaration.
@@ -254,10 +247,6 @@ class Point {
254247class Point(int x, {required int y});
255248```
256249
257- In this declaration it is possible to omit the modifier ` required ` on the
258- named parameter ` y ` , because it is implied by the fact that the type of ` y `
259- is non-nullable (potentially non-nullable is enough).
260-
261250The class header can have additional elements, just like class headers
262251where there is no primary constructor:
263252
@@ -270,92 +259,10 @@ class D<TypeVariable extends Bound> extends A with M implements B, C {
270259}
271260
272261// Using a primary constructor.
273- class const D<TypeVariable extends Bound>.named(int x, [int y = 0])
274- extends A with M implements B, C;
275- ```
276-
277- In the case where the header gets unwieldy it is possible to declare the
278- primary constructor in the body of the class using the ` primary ` keyword:
279-
280- ``` dart
281- // Current syntax.
282- class D<TypeVariable extends Bound> extends A with M implements B, C {
283- final int x;
284- final int y;
285- const D.named(this.x, [this.y = 0]);
286- }
287-
288- // Using a primary constructor.
289- class D<TypeVariable extends Bound> extends A with M implements B, C {
290- primary const D.named(int x, [int y = 0]);
291- }
292- ```
293-
294- This approach offers more flexibility in that a primary constructor in the
295- body of the declaration can have initializers and a body, just like other
296- constructors. In other words, ` primary ` on a constructor has one effect
297- only, which is to introduce instance variables for formal parameters in the
298- same way as a primary constructor in the header of the declaration. For
299- example:
300-
301- ``` dart
302- // Current syntax.
303- class A {
304- A(String _);
305- }
306-
307- class E extends A {
308- LongTypeExpression x1;
309- LongTypeExpression x2;
310- LongTypeExpression x3;
311- LongTypeExpression x4;
312- LongTypeExpression x5;
313- LongTypeExpression x6;
314- LongTypeExpression x7;
315- LongTypeExpression x8;
316- external int y;
317- int z;
318- final List<String> w;
319-
320- E({
321- required this.x1,
322- required this.x2,
323- required this.x3,
324- required this.x4,
325- required this.x5,
326- required this.x6,
327- required this.x7,
328- required this.x8,
329- required this.y,
330- }) : z = 1,
331- w = const <Never>[],
332- super('Something') {
333- // A normal constructor body.
334- }
335- }
336-
337- // Using a primary constructor.
338- class E extends A {
339- external int y;
340- int z;
341- final List<String> w;
342-
343- primary E({
344- LongTypeExpression x1,
345- LongTypeExpression x2,
346- LongTypeExpression x3,
347- LongTypeExpression x4,
348- LongTypeExpression x5,
349- LongTypeExpression x6,
350- LongTypeExpression x7,
351- LongTypeExpression x8,
352- this.y,
353- }) : z = 1,
354- w = const <Never>[],
355- super('Something') {
356- // A normal constructor body.
357- }
358- }
262+ class const D<TypeVariable extends Bound>.named(
263+ int x, [
264+ int y = 0
265+ ]) extends A with M implements B, C;
359266```
360267
361268## Specification
@@ -367,64 +274,46 @@ for extension type declarations, because they're intended to use primary
367274constructors as well.
368275
369276```
370- <topLevelDefinition> ::=
371- <classDeclaration>
372- | <extensionTypeDeclaration> // New alternative.
373- | ...;
374-
375277<classDeclaration> ::= // First alternative modified.
376278 (<classModifiers> | <mixinClassModifiers>)
377279 'class' <classNamePart> <superclass>? <interfaces>? <classBody>
378280 | ...;
379281
282+ <primaryConstructorNoConst> ::= // New rule.
283+ <typeIdentifier> <typeParameters>?
284+ ('.' <identifierOrNew>)? <formalParameterList>
285+
286+ <classNamePartNoConst> ::= // New rule.
287+ <primaryConstructorNoConst>
288+ | <typeWithParameters>;
289+
380290<classNamePart> ::= // New rule.
381- 'const'? <constructorName> <typeParameters>? <formalParameterList >
291+ 'const'? <primaryConstructorNoConst >
382292 | <typeWithParameters>;
293+
294+ <typeWithParameters> ::= <typeIdentifier> <typeParameters>?
383295
384296<classBody> ::= // New rule.
385297 '{' (<metadata> <classMemberDeclaration>)* '}'
386298 | ';';
387299
388- <extensionTypeDeclaration> ::=
389- 'extension' 'type' 'const'? <typeWithParameters>
390- <representationDeclaration>
391- <interfaces>?
300+ <extensionTypeDeclaration> ::= // Modified rule.
301+ 'extension' 'type' <classNamePart> <interfaces>?
392302 <extensionTypeBody>;
393303
394- <representationDeclaration> ::=
395- ('.' <identifierOrNew>)? '(' <metadata> <type> <identifier> ')';
396-
397304<extensionTypeMemberDeclaration> ::= <classMemberDeclaration>;
398305
399306<extensionTypeBody> ::=
400307 '{' (<metadata> <extensionTypeMemberDeclaration>)* '}'
401308 | ';';
402309
403310<enumType> ::= // Modified rule.
404- 'enum' <classNamePart > <mixins>? <interfaces>? '{'
311+ 'enum' <classNamePartNoConst > <mixins>? <interfaces>? '{'
405312 <enumEntry> (',' <enumEntry>)* (',')?
406313 (';' (<metadata> <classMemberDeclaration>)*)?
407314 '}';
408-
409- <methodSignature> ::=
410- 'primary'? <constructorSignature> <initializers>
411- | 'primary'? <factoryConstructorSignature>
412- | ... // Other cases unchanged.
413- | 'primary'? <constructorSignature>;
414-
415- <declaration> ::=
416- ... // Other cases unchanged.
417- | 'primary'? <redirectingFactoryConstructorSignature>
418- | 'primary'? <constantConstructorSignature> (<redirection> | <initializers>)?
419- | 'primary'? <constructorSignature> (<redirection> | <initializers>)?;
420315```
421316
422- The word ` type ` is now used in the grammar, but it is not a reserved word
423- or a built-in identifier. A parser that encounters the tokens ` extension `
424- and then ` type ` at a location where top-level declaration is expected shall
425- commit to parsing it as an ` <extensionTypeDeclaration> ` . * This eliminates
426- an ambiguity with ` extension ` (not ` extension type ` ) declarations.*
427-
428317A class declaration whose class body is ` ; ` is treated as a class declaration
429318whose class body is ` {} ` .
430319
@@ -446,10 +335,6 @@ extension type declaration without a primary constructor. An enum
446335declaration with a primary constructor is desugared using the same
447336steps. This determines the dynamic semantics of a primary constructor.
448337
449- A compile-time error occurs if a class, extension type, or enum declaration
450- has a primary constructor in the header as well as a constructor with the
451- modifier ` primary ` in the body.
452-
453338The following errors apply to formal parameters of a primary constructor.
454339Let _ p_ be a formal parameter of a primary constructor in a class ` C ` :
455340
@@ -507,8 +392,8 @@ then _k_ has the name `C`.
507392If it exists, _ D2_ omits the part derived from ` '.' <identifierOrNew> ` that
508393follows the name and type parameter list, if any, in _ D_ .
509394
510- _ D2_ omits the formal parameter list _ L_ that follows the name, type
511- parameter list, if any, and ` .id ` , if any.
395+ Moreover, _ D2_ omits the formal parameter list _ L_ that follows the name,
396+ type parameter list, if any, and ` .id ` , if any.
512397
513398The formal parameter list _ L2_ of _ k_ is identical to _ L_ , except that each
514399formal parameter is processed as follows.
@@ -519,9 +404,7 @@ parameters preserve the name and the modifier `required`, if any. An
519404optional positional or named parameter remains optional; if it has a
520405default value ` d ` in _ L_ then it has the transformed default value ` _n ` in
521406_ L2_ , where ` _n ` is the name of the constant variable created for that
522- default value. Finally, if ` p ` is an optional named parameter in _ L_ with
523- no default value whose type is potentially non-nullable then ` required ` is
524- added to ` p ` in _ L2_ .
407+ default value.
525408
526409- An initializing formal parameter * (e.g., ` this.x ` )* is copied from _ L_ to
527410 _ L2_ , using said transformed default value, if any, and otherwise
@@ -543,13 +426,6 @@ added to `p` in _L2_.
543426
544427Finally, _ k_ is added to _ D2_ , and _ D_ is replaced by _ D2_ .
545428
546- Assume that _ D_ is a class, extension type, or enum declaration in the
547- program that includes a constructor declaration _ k_ in the body which has
548- the modifier ` primary ` . In this case, no transformations are applied to the
549- default values of formal parameters of _ k_ , but otherwise the formal
550- parameters of _ k_ are processed in the same way as they are with a primary
551- constructor in the declaration header.
552-
553429### Discussion
554430
555431It could be argued that primary constructors should support arbitrary
@@ -607,11 +483,6 @@ constructor using `D.named`, and that would fail if we use the approach
607483where it occurs as ` new.named ` or ` const.named ` because that particular
608484constructor has been expressed as a primary constructor.
609485
610- A variant of this idea, from Leaf, is that we could allow one constructor
611- in a class with no primary constructor in the header to be marked as a
612- "primary constructor in the body". This proposal has now been made part
613- of the proposal.
614-
615486A proposal which was mentioned during the discussions about primary
616487constructors was that the keyword ` final ` could be used in order to specify
617488that all instance variables introduced by the primary constructor are
@@ -635,8 +506,104 @@ class Point {
635506class final Point(int x, int y); // Not supported!
636507```
637508
509+ Finally, we could allow a primary constructor to be declared in the body of
510+ a class or similar declaration, using the modifier ` primary ` , in which case
511+ it could have an initializer list and a body, and it would still have the
512+ ability to introduce instance variable declarations implicitly:
513+
514+ ``` dart
515+ // Current syntax.
516+ class D<TypeVariable extends Bound> extends A with M implements B, C {
517+ final int x;
518+ final int y;
519+ const D.named(this.x, [this.y = 0]);
520+ }
521+
522+ // Using a primary constructor in the class body.
523+ class D<TypeVariable extends Bound> extends A with M implements B, C {
524+ primary const D.named(int x, [int y = 0]);
525+ }
526+ ```
527+
528+ This approach offers more flexibility in that a primary constructor in the
529+ body of the declaration can have initializers and a body, just like other
530+ constructors. In other words, ` primary ` on a constructor has one effect
531+ only, which is to introduce instance variables for formal parameters in the
532+ same way as a primary constructor in the header of the declaration. For
533+ example:
534+
535+ ``` dart
536+ // Current syntax.
537+ class A {
538+ A(String _);
539+ }
540+
541+ class E extends A {
542+ LongTypeExpression x1;
543+ LongTypeExpression x2;
544+ LongTypeExpression x3;
545+ LongTypeExpression x4;
546+ LongTypeExpression x5;
547+ LongTypeExpression x6;
548+ LongTypeExpression x7;
549+ LongTypeExpression x8;
550+ external int y;
551+ int z;
552+ final List<String> w;
553+
554+ E({
555+ required this.x1,
556+ required this.x2,
557+ required this.x3,
558+ required this.x4,
559+ required this.x5,
560+ required this.x6,
561+ required this.x7,
562+ required this.x8,
563+ required this.y,
564+ }) : z = 1,
565+ w = const <Never>[],
566+ super('Something') {
567+ // A normal constructor body.
568+ }
569+ }
570+
571+ // Using a primary constructor in the class body.
572+ class E extends A {
573+ external int y;
574+ int z;
575+ final List<String> w;
576+
577+ primary E({
578+ required LongTypeExpression x1,
579+ required LongTypeExpression x2,
580+ required LongTypeExpression x3,
581+ required LongTypeExpression x4,
582+ required LongTypeExpression x5,
583+ required LongTypeExpression x6,
584+ required LongTypeExpression x7,
585+ required LongTypeExpression x8,
586+ required this.y,
587+ }) : z = 1,
588+ w = const <Never>[],
589+ super('Something') {
590+ // A normal constructor body.
591+ }
592+ }
593+ ```
594+
595+ We may get rid of all those occurrences of ` required ` in the situation
596+ where it is a compile-time error to not have them, but that is a
597+ [ separate proposal] [ inferred-required ] .
598+
599+ [ inferred-required ] : https://github.com/dart-lang/language/blob/main/working/0015-infer-required/feature-specification.md
600+
638601### Changelog
639602
603+ 1.2 - May 24, 2024
604+
605+ * Remove support for primary constructors in the body of a declaration.
606+
6406071.1 - August 22, 2023
641608
642609* Update to refer to extension types rather than inline classes.
0 commit comments