Skip to content

Ordering of augmenting initializers and getters on late variables #3987

Closed
@lrhn

Description

@lrhn

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.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    augmentationsIssues related to the augmentations proposal.featureProposed language feature that solves one or more problems

    Type

    No type

    Projects

    Status

    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions