Constructor (non-redirecting generative) augmentation
class C {
var int x, y;
C(int x, [this.y = 0]) : x = ++x {
print((++x, ++y));
}
}
augment class C {
augment C(int z, [int w = 0]) : assert(z == 0) {
int x = 42;
print((z, w));
augmented();
print((z, w));
augmented();
print((z, w));
}
}
What should this do? Reasonably?!
Things to not support:
- Calling
augmented()
sees or changes local variables in the caller. - Calling
augmented()
changes parameter variables in the caller’s parameter list.
Let’s try this for a constructor invocation of a constructor defined by an augmentation stack and with and argument list args:
-
This invokes the constructor that is the result of augmentation.
-
First it evaluates all instance variable initializers in some order. (Traditionally source order inside a single declaration.)
- First recurse on the parent augmentation stack, if any.
- Then evaluate initializers in the current declaration.
-
Then bind actuals to formals.
-
If different parameter lists can have different names, and some may be mutable, we should have a binding per constructor in the chain. (It’s then an optimization to get rid of any that are unnecessary.)
-
So for each parameter list in the stack, depth first, bind the actual arguments to the parameters.
-
(We should have already made it an error if there are corresponding parameters in different lists where
- one is an initializing formal and the other an effective super-parameter.
- both are initializing formals, but for different variables.
so that we don’t need to worry about that.)
-
If at least one of the corresponding parameters is an initializing formal, then that variable is initialized once to the value.
-
(Don’t know what to say about
super.
parameters. Probably something.)
-
-
This introduces a parameter scope for each constructor declaration, and initializes some variables.
-
-
Evaluate the initializer lists of the constructors.
- Depth first again, evaluate the initializer lists in the corresponding constructor’s parameter scope.
- When back, all initializers (and
asserts
) have been evaluated, which can have changed some of the parameter scopes, if they contain mutable variables.
-
Invoke the super-constructor.
- Search back through the stack until finding a super-invocation. Update it with all
super
parameters from that declaration and forwards in the augmentation stack. (Overriding the super invocation also clears the meaning of all earliersuper.
parameters, making the ineffective, which is one of the things to say about super parameters.) - Invoke the super-constructor in that way.
- Search back through the stack until finding a super-invocation. Update it with all
-
When we come back, all superclass constructor bodies have been executed.
-
To execute the body of this class, we “execute the body of an augmentation stack” as follows:
- If the stack is empty, there is no body. Execution completes normally.
- Otherwise, let t be the top declaration of the stack and rest be the rest.
- If t has a body:
- Execute that body in its constructor declaration’s corresponding body scope (differs from parameter scope in that initializing formal and
super
parameters variables are not there). - If that body encounters an
augmented()
, execute the body of rest. The result has static typevoid
(and will benull
).
- Execute that body in its constructor declaration’s corresponding body scope (differs from parameter scope in that initializing formal and
- Otherwise, if the top had no body, execute the body of rest.
-
Let’s try that for C(0, 43)
.
-
First it creates an instance of
C
. -
Then it invokes the augmentation stack
C
andaugment C
. -
There are no field initializers.
-
Then we bind actuals to formals:
- For
C
: localvar x = 0
, initializing parameter initializes fieldy
and introduces local finaly = 43
- For
augment C
, localvar z = 0
and localvar w = 43
.
- For
-
Then evaluate initializer lists, depth first:
x = ++x
in scope ofC
, which initializesx
variable to1
and updates environment tovar x = 1, final y = 43
.assert(z == 0)
in scope ofaugment C
, which succeds.
-
Invoke super constructor. There are none, we end up calling
Object
, which does nothing, and we come right back. -
Execute body:
int x = 42; print((z, w)); augmented(); print((z, w)); augmented(); print((z, w));
Introduces body scope extending parameter scope of
augment C
withx = 42
.Prints
(0, 43)
.Executes the body of
C
in its own body scope (var x = 1;
). Prints(2, 44)
and updates variable of parameter/body scope tovar x = 2
, and instance variabley
to44
.Prints
(0, 43)
again.Executes the body of
C
in its own body scope (var x = 2;
). Prints(3, 45)
and updates variable of parameter/body scope tovar x = 3
, and instance variabley
to45
.Prints
(0, 43)
again.All changes to variables are local to the lexical scope where the local variable is declared.
The variables are consistent across the parameter list evaluation, initializer list evaluation and body execution for each declaration. They keep their names and values, which is important for mutable variables, and do not get mixed together with other variables.