Skip to content

Ordering of augmenting initializers and getters on late variables #3987

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

Closed
lrhn opened this issue Jul 16, 2024 · 4 comments
Closed

Ordering of augmenting initializers and getters on late variables #3987

lrhn opened this issue Jul 16, 2024 · 4 comments
Labels
augmentations Issues related to the augmentations proposal. feature Proposed language feature that solves one or more problems

Comments

@lrhn
Copy link
Member

lrhn commented Jul 16, 2024

A late variable with an initializer expression evaluates the initializer expression when first read and not initialized.
Augmentations allow you to augment both the initializer expression and the getter.

The default getter is the one which evaluates the initializer, but it needs to see the actual initializer of the variable, not the one at the base declaration.

That is most likely not a problem, but it needs to be specified precisely to have the desired behavior.
(That is: We should remember this when writing the specification and tests.)

Example:

late final String name = "banana";
late String inner; 
augment String get name => "[${inner = augmented}]";
augment late final String name = "($augmented)";
void main() {
  print(name); // Should be: [(banana)];
  print(inner); // Should be: (banana)
}

Seeing the fully augmented initializer expression is consistent with how we treat other augmenting declarations: An augmenting declaration can only see the augmented definition of the one thing it augments itself, anything else is always the fully augmented property, accessed by name. The difference here is that you can't access the initializer expression "by name", that would denote the getter, but we are treating the late variable getter as being able to access "the variable's initializer expression", which is what it does.
(Object initialization will have to similarly evaluate the fully augmented initializer expression while creating the object, but that doesn't feel as weird because we don't get to augment the code that triggers the initialization, which we do when augmenting the getter of a late/lazy variable.)

In pseudo-terms:

  • The declaration late final String name = "banana"; introduces a getter definition with a synthetic body which does the if (name::initialized) return name::get; return name:set(name::initializer); operation.
  • When the augmenting getter invokes augmented, it invokes that default implementation.
  • The default implementation refers to (pseudo-code) name::initializer which evaluates the initializer expression of the fully augmented variable definition. Even if the default getter was introduced before the final augmenting declaration for the initializer expression.

Even more, the behavior of the body of the default getter can be defined based on the properties of the fully augmented variable definition:

late String name; // No initializer, introduces getter.
augment String get name =>"'[$augmented]"; // Augments getter.
augment late String name = "banana"; // Changes what default getter does.

Here we could say that the default getter "body"/invocation behavior is defined based on the fully augmented declaration:

  • If the fully augmented variable definition has no initializer expression, the default body checks if the variable is initialized and returns its value if so, otherwise it throws.
  • Otherwise the default body checks if the variable is initialized and returns its value if so, otherwise it evaluates the initializer expression, if successful, stores that in the variable and returns the value.

Or we can say that the default getter body does that check at runtime (which it obviously doesn't, but then that's an optimization):

  • If the variable is initialized, return its value.
  • Otherwise if the fully augmented variable definition has an initializer expression, evaluate that expression to a value. If already initialized and final, throw. Otherwise write the value and return iut.
  • Otherwise throw a LateInitializationError.

Since the "does it has an initializer expression" is known at compile-time, we will expect all compilers to be smart.

(It's possible to make accessing a late variable never reach its default getter, to do initialization. It's weird, but nothing you couldn't already do by subclassing and overriding the getter.)

@lrhn lrhn added feature Proposed language feature that solves one or more problems augmentations Issues related to the augmentations proposal. labels Jul 16, 2024
@jakemac53
Copy link
Contributor

@lrhn can you tackle this one? I think you have the best handle on exactly what needs to be specified here.

@lrhn
Copy link
Member Author

lrhn commented Aug 5, 2024

I'll try. I may have to invent new terminology in the documentation

How do you feel about "declaration" being the syntax and "definition" being what it introduces, as used above?

Then a variable declaration introduces 2-3 definitions: a variable definition, a better definition and maybe a setter definition, which the class contains, and augmenting declarations create a new definition from an existing (augmented) definition.
And then we assign semantics to definitions, not declarations.

For this case, a late variable with an initializer introduces a late variable definition (which is identified by the declaring context and name, but does not itself introduce a public name), and a default getter declaration (and setter if not final).
An augmenting variable declaration applies to the variable definition.
An augmenting getter declaration applies to the getter definition.
When executing the default getter, it works on the fully augmented variable definition.

I'll try to write that up formally.

(I like that the variable declaration itself gets an entity, separate from the get and setter.)

@jakemac53
Copy link
Contributor

How do you feel about "declaration" being the syntax and "definition" being what it introduces, as used above?

I think that sounds pretty good.

Then a variable declaration introduces 2-3 definitions: a variable definition, a better definition and maybe a setter definition, which the class contains, and augmenting declarations create a new definition from an existing (augmented) definition.

...which replaces the previous definition (not quite correct, the previous also exists, but now all references to this definition should refer to the new one).

(I like that the variable declaration itself gets an entity, separate from the get and setter.)

Agreed. In general I think this makes sense.

@jakemac53 jakemac53 moved this from Todo to In Progress in Static Metaprogramming - design/prototype Aug 5, 2024
@munificent
Copy link
Member

With #4357, this isn't relevant any more. Variables (late or not) can't be augmented.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
augmentations Issues related to the augmentations proposal. feature Proposed language feature that solves one or more problems
Projects
Development

No branches or pull requests

3 participants