@@ -13,11 +13,6 @@ one constructor and a set of instance variables to be specified in a concise
13
13
form in the header of the declaration. In order to use this feature, the given
14
14
constructor must satisfy certain constraints, e.g., it cannot have a body.
15
15
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
-
21
16
One variant of this feature has been proposed in the [ struct proposal] [ ] ,
22
17
several other proposals have appeared elsewhere, and prior art exists in
23
18
languages like [ Kotlin] [ kotlin primary constructors ] and Scala (with
@@ -106,9 +101,7 @@ and normal instance variable declarations, and it is probably a useful property
106
101
that the primary constructor uses a formal parameter syntax which is completely
107
102
like that of any other formal parameter list.
108
103
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
112
105
` external ` setter. Such "variables" cannot be initialized by an
113
106
initializing formal anyway, so they will just need to be declared using a
114
107
normal ` external ` variable declaration.
@@ -254,10 +247,6 @@ class Point {
254
247
class Point(int x, {required int y});
255
248
```
256
249
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
-
261
250
The class header can have additional elements, just like class headers
262
251
where there is no primary constructor:
263
252
@@ -270,92 +259,10 @@ class D<TypeVariable extends Bound> extends A with M implements B, C {
270
259
}
271
260
272
261
// 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;
359
266
```
360
267
361
268
## Specification
@@ -367,64 +274,46 @@ for extension type declarations, because they're intended to use primary
367
274
constructors as well.
368
275
369
276
```
370
- <topLevelDefinition> ::=
371
- <classDeclaration>
372
- | <extensionTypeDeclaration> // New alternative.
373
- | ...;
374
-
375
277
<classDeclaration> ::= // First alternative modified.
376
278
(<classModifiers> | <mixinClassModifiers>)
377
279
'class' <classNamePart> <superclass>? <interfaces>? <classBody>
378
280
| ...;
379
281
282
+ <primaryConstructorNoConst> ::= // New rule.
283
+ <typeIdentifier> <typeParameters>?
284
+ ('.' <identifierOrNew>)? <formalParameterList>
285
+
286
+ <classNamePartNoConst> ::= // New rule.
287
+ <primaryConstructorNoConst>
288
+ | <typeWithParameters>;
289
+
380
290
<classNamePart> ::= // New rule.
381
- 'const'? <constructorName> <typeParameters>? <formalParameterList >
291
+ 'const'? <primaryConstructorNoConst >
382
292
| <typeWithParameters>;
293
+
294
+ <typeWithParameters> ::= <typeIdentifier> <typeParameters>?
383
295
384
296
<classBody> ::= // New rule.
385
297
'{' (<metadata> <classMemberDeclaration>)* '}'
386
298
| ';';
387
299
388
- <extensionTypeDeclaration> ::=
389
- 'extension' 'type' 'const'? <typeWithParameters>
390
- <representationDeclaration>
391
- <interfaces>?
300
+ <extensionTypeDeclaration> ::= // Modified rule.
301
+ 'extension' 'type' <classNamePart> <interfaces>?
392
302
<extensionTypeBody>;
393
303
394
- <representationDeclaration> ::=
395
- ('.' <identifierOrNew>)? '(' <metadata> <type> <identifier> ')';
396
-
397
304
<extensionTypeMemberDeclaration> ::= <classMemberDeclaration>;
398
305
399
306
<extensionTypeBody> ::=
400
307
'{' (<metadata> <extensionTypeMemberDeclaration>)* '}'
401
308
| ';';
402
309
403
310
<enumType> ::= // Modified rule.
404
- 'enum' <classNamePart > <mixins>? <interfaces>? '{'
311
+ 'enum' <classNamePartNoConst > <mixins>? <interfaces>? '{'
405
312
<enumEntry> (',' <enumEntry>)* (',')?
406
313
(';' (<metadata> <classMemberDeclaration>)*)?
407
314
'}';
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>)?;
420
315
```
421
316
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
-
428
317
A class declaration whose class body is ` ; ` is treated as a class declaration
429
318
whose class body is ` {} ` .
430
319
@@ -446,10 +335,6 @@ extension type declaration without a primary constructor. An enum
446
335
declaration with a primary constructor is desugared using the same
447
336
steps. This determines the dynamic semantics of a primary constructor.
448
337
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
-
453
338
The following errors apply to formal parameters of a primary constructor.
454
339
Let _ p_ be a formal parameter of a primary constructor in a class ` C ` :
455
340
@@ -507,8 +392,8 @@ then _k_ has the name `C`.
507
392
If it exists, _ D2_ omits the part derived from ` '.' <identifierOrNew> ` that
508
393
follows the name and type parameter list, if any, in _ D_ .
509
394
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.
512
397
513
398
The formal parameter list _ L2_ of _ k_ is identical to _ L_ , except that each
514
399
formal parameter is processed as follows.
@@ -519,9 +404,7 @@ parameters preserve the name and the modifier `required`, if any. An
519
404
optional positional or named parameter remains optional; if it has a
520
405
default value ` d ` in _ L_ then it has the transformed default value ` _n ` in
521
406
_ 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.
525
408
526
409
- An initializing formal parameter * (e.g., ` this.x ` )* is copied from _ L_ to
527
410
_ L2_ , using said transformed default value, if any, and otherwise
@@ -543,13 +426,6 @@ added to `p` in _L2_.
543
426
544
427
Finally, _ k_ is added to _ D2_ , and _ D_ is replaced by _ D2_ .
545
428
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
-
553
429
### Discussion
554
430
555
431
It 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
607
483
where it occurs as ` new.named ` or ` const.named ` because that particular
608
484
constructor has been expressed as a primary constructor.
609
485
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
-
615
486
A proposal which was mentioned during the discussions about primary
616
487
constructors was that the keyword ` final ` could be used in order to specify
617
488
that all instance variables introduced by the primary constructor are
@@ -635,8 +506,104 @@ class Point {
635
506
class final Point(int x, int y); // Not supported!
636
507
```
637
508
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
+
638
601
### Changelog
639
602
603
+ 1.2 - May 24, 2024
604
+
605
+ * Remove support for primary constructors in the body of a declaration.
606
+
640
607
1.1 - August 22, 2023
641
608
642
609
* Update to refer to extension types rather than inline classes.
0 commit comments