diff --git a/working/2364 - primary constructors/feature-specification.md b/working/2364 - primary constructors/feature-specification.md index de0cab1172..f946e73f2f 100644 --- a/working/2364 - primary constructors/feature-specification.md +++ b/working/2364 - primary constructors/feature-specification.md @@ -13,11 +13,6 @@ one constructor and a set of instance variables to be specified in a concise form in the header of the declaration. In order to use this feature, the given constructor must satisfy certain constraints, e.g., it cannot have a body. -A primary constructor can also be declared in the body of a class or -similar declaration, using the modifier `primary`, in which case it can -have an initializer list and a body, and it still has the ability to -introduce instance variable declarations implicitly. - One variant of this feature has been proposed in the [struct proposal][], several other proposals have appeared elsewhere, and prior art exists in 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 that the primary constructor uses a formal parameter syntax which is completely like that of any other formal parameter list. -Just use a normal declaration and use an initializing formal in a primary -constructor to initialize it from the primary constructor, if needed. An -`external` instance variable amounts to an `external` getter and an +An `external` instance variable amounts to an `external` getter and an `external` setter. Such "variables" cannot be initialized by an initializing formal anyway, so they will just need to be declared using a normal `external` variable declaration. @@ -254,10 +247,6 @@ class Point { class Point(int x, {required int y}); ``` -In this declaration it is possible to omit the modifier `required` on the -named parameter `y`, because it is implied by the fact that the type of `y` -is non-nullable (potentially non-nullable is enough). - The class header can have additional elements, just like class headers where there is no primary constructor: @@ -270,92 +259,10 @@ class D extends A with M implements B, C { } // Using a primary constructor. -class const D.named(int x, [int y = 0]) - extends A with M implements B, C; -``` - -In the case where the header gets unwieldy it is possible to declare the -primary constructor in the body of the class using the `primary` keyword: - -```dart -// Current syntax. -class D extends A with M implements B, C { - final int x; - final int y; - const D.named(this.x, [this.y = 0]); -} - -// Using a primary constructor. -class D extends A with M implements B, C { - primary const D.named(int x, [int y = 0]); -} -``` - -This approach offers more flexibility in that a primary constructor in the -body of the declaration can have initializers and a body, just like other -constructors. In other words, `primary` on a constructor has one effect -only, which is to introduce instance variables for formal parameters in the -same way as a primary constructor in the header of the declaration. For -example: - -```dart -// Current syntax. -class A { - A(String _); -} - -class E extends A { - LongTypeExpression x1; - LongTypeExpression x2; - LongTypeExpression x3; - LongTypeExpression x4; - LongTypeExpression x5; - LongTypeExpression x6; - LongTypeExpression x7; - LongTypeExpression x8; - external int y; - int z; - final List w; - - E({ - required this.x1, - required this.x2, - required this.x3, - required this.x4, - required this.x5, - required this.x6, - required this.x7, - required this.x8, - required this.y, - }) : z = 1, - w = const [], - super('Something') { - // A normal constructor body. - } -} - -// Using a primary constructor. -class E extends A { - external int y; - int z; - final List w; - - primary E({ - LongTypeExpression x1, - LongTypeExpression x2, - LongTypeExpression x3, - LongTypeExpression x4, - LongTypeExpression x5, - LongTypeExpression x6, - LongTypeExpression x7, - LongTypeExpression x8, - this.y, - }) : z = 1, - w = const [], - super('Something') { - // A normal constructor body. - } -} +class const D.named( + int x, [ + int y = 0 +]) extends A with M implements B, C; ``` ## Specification @@ -367,33 +274,33 @@ for extension type declarations, because they're intended to use primary constructors as well. ``` - ::= - - | // New alternative. - | ...; - ::= // First alternative modified. ( | ) 'class' ? ? | ...; + ::= // New rule. + ? + ('.' )? + + ::= // New rule. + + | ; + ::= // New rule. - 'const'? ? + 'const'? | ; + + ::= ? ::= // New rule. '{' ( )* '}' | ';'; - ::= - 'extension' 'type' 'const'? - - ? + ::= // Modified rule. + 'extension' 'type' ? ; - ::= - ('.' )? '(' ')'; - ::= ; ::= @@ -401,30 +308,12 @@ constructors as well. | ';'; ::= // Modified rule. - 'enum' ? ? '{' + 'enum' ? ? '{' (',' )* (',')? (';' ( )*)? '}'; - - ::= - 'primary'? - | 'primary'? - | ... // Other cases unchanged. - | 'primary'? ; - - ::= - ... // Other cases unchanged. - | 'primary'? - | 'primary'? ( | )? - | 'primary'? ( | )?; ``` -The word `type` is now used in the grammar, but it is not a reserved word -or a built-in identifier. A parser that encounters the tokens `extension` -and then `type` at a location where top-level declaration is expected shall -commit to parsing it as an ``. *This eliminates -an ambiguity with `extension` (not `extension type`) declarations.* - A class declaration whose class body is `;` is treated as a class declaration whose class body is `{}`. @@ -446,10 +335,6 @@ extension type declaration without a primary constructor. An enum declaration with a primary constructor is desugared using the same steps. This determines the dynamic semantics of a primary constructor. -A compile-time error occurs if a class, extension type, or enum declaration -has a primary constructor in the header as well as a constructor with the -modifier `primary` in the body. - The following errors apply to formal parameters of a primary constructor. Let _p_ be a formal parameter of a primary constructor in a class `C`: @@ -507,8 +392,8 @@ then _k_ has the name `C`. If it exists, _D2_ omits the part derived from `'.' ` that follows the name and type parameter list, if any, in _D_. -_D2_ omits the formal parameter list _L_ that follows the name, type -parameter list, if any, and `.id`, if any. +Moreover, _D2_ omits the formal parameter list _L_ that follows the name, +type parameter list, if any, and `.id`, if any. The formal parameter list _L2_ of _k_ is identical to _L_, except that each formal parameter is processed as follows. @@ -519,9 +404,7 @@ parameters preserve the name and the modifier `required`, if any. An optional positional or named parameter remains optional; if it has a default value `d` in _L_ then it has the transformed default value `_n` in _L2_, where `_n` is the name of the constant variable created for that -default value. Finally, if `p` is an optional named parameter in _L_ with -no default value whose type is potentially non-nullable then `required` is -added to `p` in _L2_. +default value. - An initializing formal parameter *(e.g., `this.x`)* is copied from _L_ to _L2_, using said transformed default value, if any, and otherwise @@ -543,13 +426,6 @@ added to `p` in _L2_. Finally, _k_ is added to _D2_, and _D_ is replaced by _D2_. -Assume that _D_ is a class, extension type, or enum declaration in the -program that includes a constructor declaration _k_ in the body which has -the modifier `primary`. In this case, no transformations are applied to the -default values of formal parameters of _k_, but otherwise the formal -parameters of _k_ are processed in the same way as they are with a primary -constructor in the declaration header. - ### Discussion 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 where it occurs as `new.named` or `const.named` because that particular constructor has been expressed as a primary constructor. -A variant of this idea, from Leaf, is that we could allow one constructor -in a class with no primary constructor in the header to be marked as a -"primary constructor in the body". This proposal has now been made part -of the proposal. - A proposal which was mentioned during the discussions about primary constructors was that the keyword `final` could be used in order to specify that all instance variables introduced by the primary constructor are @@ -635,8 +506,104 @@ class Point { class final Point(int x, int y); // Not supported! ``` +Finally, we could allow a primary constructor to be declared in the body of +a class or similar declaration, using the modifier `primary`, in which case +it could have an initializer list and a body, and it would still have the +ability to introduce instance variable declarations implicitly: + +```dart +// Current syntax. +class D extends A with M implements B, C { + final int x; + final int y; + const D.named(this.x, [this.y = 0]); +} + +// Using a primary constructor in the class body. +class D extends A with M implements B, C { + primary const D.named(int x, [int y = 0]); +} +``` + +This approach offers more flexibility in that a primary constructor in the +body of the declaration can have initializers and a body, just like other +constructors. In other words, `primary` on a constructor has one effect +only, which is to introduce instance variables for formal parameters in the +same way as a primary constructor in the header of the declaration. For +example: + +```dart +// Current syntax. +class A { + A(String _); +} + +class E extends A { + LongTypeExpression x1; + LongTypeExpression x2; + LongTypeExpression x3; + LongTypeExpression x4; + LongTypeExpression x5; + LongTypeExpression x6; + LongTypeExpression x7; + LongTypeExpression x8; + external int y; + int z; + final List w; + + E({ + required this.x1, + required this.x2, + required this.x3, + required this.x4, + required this.x5, + required this.x6, + required this.x7, + required this.x8, + required this.y, + }) : z = 1, + w = const [], + super('Something') { + // A normal constructor body. + } +} + +// Using a primary constructor in the class body. +class E extends A { + external int y; + int z; + final List w; + + primary E({ + required LongTypeExpression x1, + required LongTypeExpression x2, + required LongTypeExpression x3, + required LongTypeExpression x4, + required LongTypeExpression x5, + required LongTypeExpression x6, + required LongTypeExpression x7, + required LongTypeExpression x8, + required this.y, + }) : z = 1, + w = const [], + super('Something') { + // A normal constructor body. + } +} +``` + +We may get rid of all those occurrences of `required` in the situation +where it is a compile-time error to not have them, but that is a +[separate proposal][inferred-required]. + +[inferred-required]: https://github.com/dart-lang/language/blob/main/working/0015-infer-required/feature-specification.md + ### Changelog +1.2 - May 24, 2024 + +* Remove support for primary constructors in the body of a declaration. + 1.1 - August 22, 2023 * Update to refer to extension types rather than inline classes. diff --git a/working/2364 - primary constructors/scripts/inline_class.json b/working/2364 - primary constructors/scripts/inline_class.json index ed66ba50b5..7c821f85f3 100644 --- a/working/2364 - primary constructors/scripts/inline_class.json +++ b/working/2364 - primary constructors/scripts/inline_class.json @@ -1,5 +1,5 @@ { - "isInline": true, + "isExtensionType": true, "name": "Point", "fields": [ { diff --git a/working/2364 - primary constructors/scripts/inline_const_class.json b/working/2364 - primary constructors/scripts/inline_const_class.json index 3f963365b1..d9539083a7 100644 --- a/working/2364 - primary constructors/scripts/inline_const_class.json +++ b/working/2364 - primary constructors/scripts/inline_const_class.json @@ -1,5 +1,5 @@ { - "isInline": true, + "isExtensionType": true, "name": "Point", "isConst": true, "fields": [ diff --git a/working/2364 - primary constructors/scripts/inline_generic_class.json b/working/2364 - primary constructors/scripts/inline_generic_class.json index 142ceb3918..76b56810e2 100644 --- a/working/2364 - primary constructors/scripts/inline_generic_class.json +++ b/working/2364 - primary constructors/scripts/inline_generic_class.json @@ -1,5 +1,5 @@ { - "isInline": true, + "isExtensionType": true, "name": "Point", "typeParameters": "", "fields": [ diff --git a/working/2364 - primary constructors/scripts/inline_named_class.json b/working/2364 - primary constructors/scripts/inline_named_class.json index 80e809ce86..1f41b5cef6 100644 --- a/working/2364 - primary constructors/scripts/inline_named_class.json +++ b/working/2364 - primary constructors/scripts/inline_named_class.json @@ -1,5 +1,5 @@ { - "isInline": true, + "isExtensionType": true, "name": "Point", "constructorName": "_", "fields": [ diff --git a/working/2364 - primary constructors/scripts/inline_named_const_class.json b/working/2364 - primary constructors/scripts/inline_named_const_class.json index eed0bc5b5a..ac20b31710 100644 --- a/working/2364 - primary constructors/scripts/inline_named_const_class.json +++ b/working/2364 - primary constructors/scripts/inline_named_const_class.json @@ -1,5 +1,5 @@ { - "isInline": true, + "isExtensionType": true, "name": "Point", "isConst": true, "constructorName": "_", diff --git a/working/2364 - primary constructors/scripts/inline_subtyped_class.json b/working/2364 - primary constructors/scripts/inline_subtyped_class.json index ebb1864c1c..5aa04521ad 100644 --- a/working/2364 - primary constructors/scripts/inline_subtyped_class.json +++ b/working/2364 - primary constructors/scripts/inline_subtyped_class.json @@ -1,5 +1,5 @@ { - "isInline": true, + "isExtensionType": true, "name": "Point", "superinterfaces": "implements B, C", "fields": [ diff --git a/working/2364 - primary constructors/scripts/inline_subtyped_const_class.json b/working/2364 - primary constructors/scripts/inline_subtyped_const_class.json index a49a82e10b..77300a9706 100644 --- a/working/2364 - primary constructors/scripts/inline_subtyped_const_class.json +++ b/working/2364 - primary constructors/scripts/inline_subtyped_const_class.json @@ -1,5 +1,5 @@ { - "isInline": true, + "isExtensionType": true, "name": "Point", "isConst": true, "superinterfaces": "implements B, C", diff --git a/working/2364 - primary constructors/scripts/inline_subtyped_generic_class.json b/working/2364 - primary constructors/scripts/inline_subtyped_generic_class.json index 6fb9f4e1dd..5d444f6478 100644 --- a/working/2364 - primary constructors/scripts/inline_subtyped_generic_class.json +++ b/working/2364 - primary constructors/scripts/inline_subtyped_generic_class.json @@ -1,5 +1,5 @@ { - "isInline": true, + "isExtensionType": true, "name": "Point", "typeParameters": "", "superinterfaces": "implements B, C", diff --git a/working/2364 - primary constructors/scripts/inline_subtyped_generic_const_class.json b/working/2364 - primary constructors/scripts/inline_subtyped_generic_const_class.json index f13c2bd83f..8d6f9ef1b7 100644 --- a/working/2364 - primary constructors/scripts/inline_subtyped_generic_const_class.json +++ b/working/2364 - primary constructors/scripts/inline_subtyped_generic_const_class.json @@ -1,5 +1,5 @@ { - "isInline": true, + "isExtensionType": true, "name": "Point", "isConst": true, "typeParameters": "", diff --git a/working/2364 - primary constructors/scripts/show_primary_constructors.dart b/working/2364 - primary constructors/scripts/show_primary_constructors.dart index 2ed71c47fd..4f0bc3a54d 100755 --- a/working/2364 - primary constructors/scripts/show_primary_constructors.dart +++ b/working/2364 - primary constructors/scripts/show_primary_constructors.dart @@ -135,7 +135,7 @@ class ClassSpec { final String provenance; // Identify where we got this class from. final String name; final String? constructorName; - final bool isInline; + final bool isExtensionType; final List fields; final bool isConst; final String? superinterfaces; @@ -145,7 +145,7 @@ class ClassSpec { this.provenance, this.name, this.constructorName, - this.isInline, + this.isExtensionType, this.fields, this.isConst, this.typeParameters, @@ -155,7 +155,7 @@ class ClassSpec { factory ClassSpec.fromJson(String source, Map jsonSpec) { var name = jsonSpec['name']!; var constructorName = jsonSpec['constructorName']; - var isInline = jsonSpec['isInline'] ?? false; + var isExtensionType = jsonSpec['isExtensionType'] ?? false; var jsonFields = jsonSpec['fields']!; var isConst = jsonSpec['isConst'] ?? false; var typeParameters = jsonSpec['typeParameters']; @@ -170,7 +170,7 @@ class ClassSpec { source, name, constructorName, - isInline, + isExtensionType, fields, isConst, typeParameters, @@ -270,10 +270,10 @@ String ppNormal(ClassSpec classSpec) { superinterfaces = ' $specSuperinterfaces'; } - var inlinity = classSpec.isInline ? 'inline ' : ''; + var classKind = classSpec.isExtensionType ? 'extension type' : 'class'; var body = Options.includeBody ? ' // ...\n' : ''; - return "${inlinity}class $className$typeParameters$superinterfaces" + return "$classKind $className$typeParameters$superinterfaces" " {$fieldsSource$constructorSource$body}"; } @@ -294,7 +294,7 @@ String ppKeyword(ClassSpec classSpec) { } var finality = ''; if (field.isFinal) { - if (classSpec.isConst || classSpec.isInline) { + if (classSpec.isConst || classSpec.isExtensionType) { if (Options.explicitFinal) finality = 'final '; } else { finality = 'final '; @@ -338,12 +338,12 @@ String ppKeyword(ClassSpec classSpec) { String constructorPhrase = '$keyword'; var constructorNameSpec = classSpec.constructorName; if (constructorNameSpec != null) { - constructorPhrase = '$keyword.$constructorNameSpec'; + constructorPhrase = '$keyword$typeParameters.$constructorNameSpec'; } - var inlinity = classSpec.isInline ? 'inline ' : ''; + var classKind = classSpec.isExtensionType ? 'extension type' : 'class'; var classHeader = - "${inlinity}class $className$typeParameters$superinterfaces" + "$classKind $className$superinterfaces" " $constructorPhrase($parametersSource)"; var body = Options.includeBody ? ' {\n // ...\n\}' : ';'; return "$classHeader$body"; @@ -366,7 +366,7 @@ String ppStruct(ClassSpec classSpec) { } var finality = ''; if (field.isFinal) { - if (classSpec.isConst || classSpec.isInline) { + if (classSpec.isConst || classSpec.isExtensionType) { if (Options.explicitFinal) finality = 'final '; } else { finality = 'final '; @@ -410,14 +410,14 @@ String ppStruct(ClassSpec classSpec) { String constructorNamePart = '$className'; var constructorNameSpec = classSpec.constructorName; if (constructorNameSpec != null) { - constructorNamePart = '$className.$constructorNameSpec$typeParameters'; + constructorNamePart = '$className$typeParameters.$constructorNameSpec'; } else { constructorNamePart = '$className$typeParameters'; } - var inlinity = classSpec.isInline ? 'inline ' : ''; + var classKind = classSpec.isExtensionType ? 'extension type' : 'class'; var classHeader = - "${inlinity}class $constNess$constructorNamePart" + "$classKind $constNess$constructorNamePart" "($parametersSource)" "$superinterfaces"; var body = Options.includeBody ? ' {\n // ...\n\}' : ';';