Forward references in initializers
Dan Smith
daniel.smith at oracle.com
Fri Nov 8 19:59:42 UTC 2024
> On Nov 7, 2024, at 12:36 PM, Dan Smith <daniel.smith at oracle.com> wrote:
>
> Reflecting on this, I think all of ValueTest, NullTest, and StaticTest should be errors.
>
> I like that, under the existing left-to-right rule, a reader can make sense of the initialization code, *as if* it were all run at once, left to right. In the rare cases that timing/side effects matter, a more sophisticated reader would need to understand that some initializers run early, and others run late. But because of the restrictions on early construction code (no field reads), we can leave the left-to-right reading as "good enough" for most developers, and stick with language rules that reinforce this.
What does this mean for definite assignment?
The current JEP 401 rules model definite assignment according to the actual flow of execution. So something like this would be allowed:
value class DATest {
final String s1;
{ System.out.println(s1); }
final String s2 = (s1 = "abc");
}
But under the left-to-right design principle, we'd say this is an error: it *looks like* s1 is not yet DA in the initializer. It's some weird code that we're rejecting, so no great loss. (Just reorder your initializers.)
What if we reverse both the left-to-right order and the run time order?
value class DATest2 {
final String s1;
{ s1 = "abc"; }
final String s2 = s1;
}
This seems like a problem that DA needs to catch, but fortunately the reference to s1 is illegal anyway—we're in an early construction context. (You can think of early execution sort of like an optimization, and one that is allowed because we've already proven that this code doesn't depend on the state of the object being initialized.)
What about definite unassignment?
value class DUTest {
final String s1;
{ s1 = "abc"; }
final String s2 = (s1 = "abc");
}
The left-to-right analysis will *allow* the assignment to s1 in the block initializer, even though it will have already been assigned by the early initializer of s2. Fortunately, we'll catch the problem when we check the s2 initializer, which is now assigning to a field that is not DU. You could claim that the error is "in the wrong place", but the error occurs nonetheless.
Can we rely on this outcome in general? That the set of programs that satisfy the DU rules under a left-to-right interpretation is the same as the set of programs that satisfy the DU rules under an early-then-late interpretation? I *think* so: every field initializer, block initializer, and constructor prologue that contains an assignment to a field is required to consider the field DU beforehand, and non-DU afterwards. However we order those pieces, they always run in a linear sequence, so it's never allowed to do the transition twice.
Conclusion: I think I'm happy with a DA/DU analysis that treats initializers as if they run in left-to-right order, before the start of the constructor. It's not really true, but it detects the errors we need to detect with less complexity.
More information about the valhalla-spec-experts
mailing list