-
Notifications
You must be signed in to change notification settings - Fork 237
Scopes for field initializer expressions with augmentations and primary constructors #4729
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -293,7 +293,7 @@ always uses the same `on` clause as the introductory declaration. | |||||
|
|
||||||
| ## Primary constructors | ||||||
|
|
||||||
| A `class`, `enum` or `extension type` declarationscan use the primary | ||||||
| A `class`, `enum` or `extension type` declarations can use the primary | ||||||
| constructor syntax for declaring an initializing _(non-redirecting | ||||||
| generative)_ constructor. | ||||||
|
|
||||||
|
|
@@ -311,6 +311,9 @@ expressions refers to a variable introduced by the constructor's | |||||
| initializer list scope, and the surrounding class or enum declaration does | ||||||
| _not_ have a primary constructor declaration which declares the | ||||||
| corresponding parameter's name. | ||||||
| See more details in the [Instance variable initializer](instance-variable-initialization-during-constructor-invocation) | ||||||
| section. | ||||||
|
|
||||||
| _A primary constructor must declare all parameters, but it can omit declaring | ||||||
| a positional parameter's name by using `_` instead of the name. | ||||||
| If it does so, that parameter's name cannot be used by instance variable | ||||||
|
|
@@ -768,7 +771,7 @@ augmentation. In that case, it augments the corresponding member (the | |||||
| introducing member with the same name) in the same augmentation context, | ||||||
| according to the rules in the following subsections. | ||||||
|
|
||||||
| It's a **compile-time** error if: | ||||||
| It's a **compile-time error** if: | ||||||
|
|
||||||
| * An augmenting class declaration has an `extends` clause and any prior | ||||||
| declaration for the same class also has an `extends` clause. | ||||||
|
|
@@ -940,10 +943,10 @@ introduced implicitly with the value `null` in the case where the parameter | |||||
| has a nullable declared type, and no default values for that parameter are | ||||||
| specified in the augmentation chain. | ||||||
|
|
||||||
| It's a **compile-time** error if: | ||||||
| It's a **compile-time error** if: | ||||||
|
|
||||||
| * The signature of the augmenting function does not [match][signature | ||||||
| matching] the signature of the corresponding introductory declaration. | ||||||
| * The signature of the augmenting function does not [match][signature matching] | ||||||
| the signature of the corresponding introductory declaration. | ||||||
|
|
||||||
| * More than one declaration in the augmentation chain specifies a default | ||||||
| value for the same optional parameter. This is an error even in the | ||||||
|
|
@@ -984,8 +987,8 @@ setter with a non-abstract variable declaration.* | |||||
|
|
||||||
| It's a **compile-time error** if: | ||||||
|
|
||||||
| * The signature of the augmenting getter or setter does not [match][signature | ||||||
| matching] the signature of the corresponding introductory getter or setter. | ||||||
| * The signature of the augmenting getter or setter does not [match][signature matching] | ||||||
| the signature of the corresponding introductory getter or setter. | ||||||
|
|
||||||
| * A `const` variable declaration is augmented or augmenting. | ||||||
|
|
||||||
|
|
@@ -1241,6 +1244,12 @@ contain constructor declarations where: | |||||
| report every declaration that has an error compared to the introductory | ||||||
| declaration._ | ||||||
|
|
||||||
| * There is a `const` initializing constructor declaration and: | ||||||
| * A complete instance variable declaration which is `late`, | ||||||
| or which is neither `final` nor `external`. | ||||||
| * A complete instance variable declaration with an initializer | ||||||
| expression which is not a potentially constant expression. | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it not already an error to have a
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd also say that these errors are the existing ones in pre-augmentations Dart plus the general principle that static analysis (including emission of compile-time errors) should be performed according to the semantic declarations, not according to one fragment at a time. |
||||||
|
|
||||||
| * The signature of an augmenting constructor does not [match][signature | ||||||
| matching] the signature of the corresponding introductory constructor. | ||||||
| _The signature of a constructor using the privately-named-parameters | ||||||
|
|
@@ -1286,45 +1295,111 @@ contain constructor declarations where: | |||||
| #### Instance variable initialization during constructor invocation | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to settle a terminology that clearly distinguishes the fragments that are represented as syntactic class/enum/... declarations from the semantic declarations that are obtained by applying all augmentations. We could say 'declaration fragments' when we mean the syntactic declarations, and 'semantic declarations' when that's what we mean, but we really need to keep those two concepts strictly separated. |
||||||
|
|
||||||
| When invoking an initializing generative constructor to initialize | ||||||
| a new object, instance variable initialization happens before executing | ||||||
| the initializer list. | ||||||
| a new object, non-`late` instance variable initialization happens | ||||||
| before executing the initializer list. | ||||||
|
|
||||||
| This is true whether or not the class or enum has a primary constructor. | ||||||
|
|
||||||
| When invoking the initializing constructor to initialize a new object, | ||||||
| the first thing that happens is that actual arguments are bound to formal | ||||||
| parameters, which provides the bindings for the initializer list scope. | ||||||
|
|
||||||
| Then all non-`late` instance variable declarations of the class which have | ||||||
| an initializer expression are processed in their source order. | ||||||
|
|
||||||
| Each instance variable is initialized in turn, by evaluating its initializer | ||||||
| expression. | ||||||
| If the class or enum has a primary constructor, the initializer | ||||||
| expression is evaluated in the initializer list scope, otherwise it's evaluated | ||||||
| in the body scope of the surrounding class or enum | ||||||
| *(extension and extension type declarations cannot contain instance variable | ||||||
| declarations, mixins and mixin-application classes cannot have primary | ||||||
| constructors)*. | ||||||
|
|
||||||
| *If the class has a constant generative constructor, then it's still a compile- | ||||||
| time error if the class has any non-`final` instance variables, and it's still | ||||||
| a compile-time error if an instance variable has an initializer expression | ||||||
| that is not a potentially constant expression.* | ||||||
|
|
||||||
| *Whether the evaluation uses the body scope or the initializer list scope, | ||||||
| which has the body scope as parent scope, it's still an error if the | ||||||
| expression refers to any instance member in the body scope.* | ||||||
|
|
||||||
| After all instance variable initializers have been executed, | ||||||
| constructor execution continues with executing as in Dart before this feature, | ||||||
| starting with the variable initialization of the parameter list, by initializing | ||||||
| formals and declaring parameters. | ||||||
|
|
||||||
| _This is how primary constructors already work. The only difference is that | ||||||
| because a single constructor can be introduced by more than one declaration, | ||||||
| the complete declaration, the actual implementation, of a primary constructor | ||||||
| might not be a primary constructor declaration._ | ||||||
| When invoking the initializing constructor with a valid argument list to | ||||||
| initialize a new object, perform instance variable initialization on | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just to be safe, we could spell out lateness here again:
Suggested change
|
||||||
| each declaration of the class or enum in source order, with that | ||||||
| given argument list. | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 'given' where? Perhaps there should be an extra sentence above introducing the invocation and in particular its actual argument list. |
||||||
|
|
||||||
| To perform instance variable initialization on a class or enum declaration: | ||||||
|
|
||||||
| * If the class or enum does not have a primary constructor, | ||||||
| the lexical scope for instance variable initializers is the class/enum body | ||||||
| scope. | ||||||
| At initialization time, for each non-`late` instance variable with | ||||||
| an initializer expression, in source order, evaluate the initializer expression | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think "in source" order is sufficiently precise to cover how instance variable initializers from different augmentations are ordered. Probably need to say something like: At initialization time:
|
||||||
| in the runtime body scope, then initialize the variable to the result. | ||||||
|
|
||||||
| * If the class or enum has a primary constructor, each class or enum declaration | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this would be a little clearer as: |
||||||
| introduces a _field initializer scope_ which: | ||||||
| * Has an entry for each parameter with a name in the combined constructor | ||||||
| signature of that constructor. | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we have defined 'combined constructor signature'. It should suffice to talk about 'the constructor declaration' in 'the semantic class declaration', or just 'the semantic constructor declaration'. One reason why we might want to avoid 'combined' here is that we already use 'combined member signature' to talk about the handling of non-identical member signatures in superinterfaces. Another reason is that the semantic constructor declaration has a signature which isn't combined in the usual sense of that word, it is the semantic signature which is obtained by gathering pieces from the entire augmentation chain of the declaration, but those pieces are never allowed to disagree on anything, they are only allowed to omit certain elements (like the type or name), which will then be taken from other syntactic declarations. |
||||||
| _No entries for positional parameters where all declarations use `_` | ||||||
| instead of a name, all other parameters represented by their name._ | ||||||
| * If, and only if, that class or enum declaration has a complete primary | ||||||
| constructor declaration which has one or more private named parameters | ||||||
| _(which are initializing formals or declaring parameters)_, then both | ||||||
| the private and the public names are in the field initializer scope. | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm still not a fan of this approach.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here. I'd prefer to treat the declaration using a private name consistently as having this private name for all lexical lookup operations. The only location where the corresponding public name is used is as the label of a named argument at a call site. |
||||||
| * That entry has the type of the parameter in the constructor signature as | ||||||
| its declared type. | ||||||
| _If no declaration of a constructor has an explicit type, then a | ||||||
| type may have been inferred from a default value or it may have defaulted | ||||||
| to `dynamic`._ | ||||||
| * None of these entries are assignable. | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not quite congruent with "a field initializer scope which:". Some other entries in this list have the same issue. |
||||||
| * An identifier which resolves to a name in the field initializer scope | ||||||
| is a compile-time error unless the surrounding class or enum declaration | ||||||
| has a primary constructor declaration which has a corresponding parameter | ||||||
| declaration which has that identifier as name. | ||||||
| * _For a private named parameter, only the private name satisfies this._ | ||||||
|
|
||||||
| This is the lexical scope for instance variable initializers. The parent | ||||||
| scope of the field initializer scope is the body scope of the surrounding | ||||||
| class or enum. | ||||||
|
|
||||||
| When the constructor is invoked, each of the entries are bound to the | ||||||
| corresponding actual argument value. | ||||||
| If there is no corresponding argument, then the parameter must be optional. | ||||||
| If any declaration of the parameter has a default value, the entry is bound | ||||||
| to that value, otherwise the entry is bound to `null` _and the parameter | ||||||
| must be nullable_. | ||||||
|
|
||||||
| Then each non-`late` instance variable with an initializer expression in that | ||||||
| class or enum declaration, in source order, has its initializer expression | ||||||
| evaluated in that runtime field initializer scope, and the variable is | ||||||
| initialized to the result. | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't like that both branches of the top-level if describe the same behavior here. I think it would be simpler if we organize it like: This way, everything that can be left the same between both branches is shared. |
||||||
|
|
||||||
| Whether the evaluation uses the body scope or the field initializer scope, | ||||||
| it is still a compile-time error if the expression refers to any instance member | ||||||
| in the body scope, or to `this`. | ||||||
|
|
||||||
| After having run all non-`late` instance variable initializers in all | ||||||
| declarations of the class or enum, the initializing constructor's implementation | ||||||
| is executed with that argument list to initialize the object in the same way as | ||||||
| without augmentations. | ||||||
|
|
||||||
| _This almost matches how primary constructors already work. The one exception | ||||||
| is that a declaration like:_ | ||||||
| ```dart | ||||||
| const x = 42; | ||||||
| class C({final int _x}) { | ||||||
| final int y = x; | ||||||
| } | ||||||
| ``` | ||||||
| _has a different scope for `x` in the initializer expression. | ||||||
| In the existing primary constructor specification, that `x` would refer to the | ||||||
| top-level constant, and in this specification it is an error. | ||||||
| Removing the public name from the field initializer scope would remove that | ||||||
| discrepancy._ | ||||||
|
|
||||||
| Example: | ||||||
| ```dart | ||||||
| class const Repeat<T>(int count, T value) extends Iterable<T> { | ||||||
| final int _count = count; | ||||||
| } | ||||||
| augment class Repeat<T> { | ||||||
| final T _value; | ||||||
| const Repeat(int count, T value) : _value = value; | ||||||
| Iterator<T> get iterator => Iterable<T>.generate(_count, (_) => _value).iterator; | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| Example with default values and private named parameters: | ||||||
| ```dart | ||||||
| class const Point({int x = 0, int y = 0}) { | ||||||
| abstract final int x; | ||||||
| abstract final int y; | ||||||
| final int _squareDistanceToOrigo = x * x + y * y; | ||||||
| } | ||||||
| augment class const Point({final int _x, final int _y}) { | ||||||
| augment int get x => _x; | ||||||
| augment int get y => _y; | ||||||
| final int _squareDistanceToDiagonal = (_y - _x) * (_y - _x); | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| ### Augmenting extension types | ||||||
|
|
||||||
|
|
@@ -1415,7 +1490,7 @@ same syntactic construct more severely than redundancies that only exist in the | |||||
| semantic declaration, but are syntactically located in different elements of | ||||||
| an augmentation chain.* | ||||||
|
|
||||||
| ### Compile errors with augmentations | ||||||
| ### Compile-time errors with augmentations | ||||||
|
|
||||||
| [compile-time error principle]: #compile-errors-with-augmentations | ||||||
|
|
||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"declarations can" -> "declaration can".