On 8 Nov 2024, at 13:49, Dan Smith wrote: > On Nov 8, 2024, at 11:59 AM, Dan Smith <daniel.sm...@oracle.com> wrote: > > 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. > > Ugh, never mind, spoke too soon. > > We have rules that say: > - All blank final instance fields must be DU before a 'this()' invocation > occurs > - All blank strict instance fields must be DA before a 'super()' invocation > occurs > > These rules rely on a more nuanced analysis: the 'this()' rule assumes no > initializers have run, while the 'super()' rule assumes early initializers > have run. You only get those outcomes by modeling the assignments at the > proper place where they will actually occur at run time, and excluding any > initializers that won't run until after the invocation. > > Plus the rules about assignments in the initializers and the prologue need to > somehow pass information between each other to prevent multiple assignments. > > So I think we'll have to stick with the early vs. late DA analysis, even > though it works differently than the simpler left-to-right forward reference > restriction. And this means DATest should be allowed.
Yes. It’s ugly, though. (CLARIFICATION: The fields in any value class can be redundantly marked final, as you have here, BUT ALSO they should be viewed as marked strict, even if we don’t have a syntax for that. I’m going to pretend we have a __Strict keyword.) Here’s what you wrote, with __Strict added: value class DATest { __Strict final String s1; { System.out.println(s1); } __Strict final String s2 = (s1 = "abc"); } It’s legal because the s2-init in pre-super, and so precedes the instance-init, which is post-super. Here is how that class SHOULD be written, to keep it from being so puzzling to read: value class DATest_b { __Strict final String s1; __Strict final String s2 = (s1 = "abc"); { System.out.println(s1); } } Let’s just pretend for a second that we could annotate the instance-init with __Strict as well, meaning the code gets moved to pre-super. Then we could try moving the instance-init to pre-super: value class DATest_c { __Strict final String s1; __Strict { System.out.println(s1); } __Strict final String s2 = (s1 = "abc"); } Gross. And also useless. As I think you observed, s1 cannot be read in a pre-super context. I am happy to acknowledge that there is no call for marking an instance-init with __Strict or any other marker that would change its ordering. DATest_c is just wrong, and the original DATest is a puzzler because it reads like it could be DATest_c. OK, so given all that, I see that it would be nice to employ the left-to-right rule, somehow, to encourage (or even force) programmers to write DATest_b to avoid appearances of disorder. I have an idea here, which I think might pan out: Amend the left-to-right order rule (for non-statics only) to allow only the nicely readable DATest_b. We keep unchanged the existing constraints from Java 1.1, of DA/DU conditions, and left-to-right name def-to-use order, among non-statics. (Same story independently for statics, of course.) THEN, for non-statics only (because there is a distinction between pre-super and post-super inits), we require that all pre-super inits are to the left of all post-super inits. We have five cases of declarations which interact with init-order (either as use or def of fields). A. a strict field with an initializer (pre-super, has-init) B. a strict field with no initializer (pre-super, no-init) C. an instance initialization block (post-super, has-init) D. a non-strict field with an initializer (post-super, has-init) E. a non-strict field with no initializer (post-super?, no-init) The idea I’m trying out here is that any of the five cases that executes init code (A, C, or D) must have an extra left-to-right constraint that places it either with all of the pre-super codes (all A cases together) or all of the post-super codes (all C and D cases together). It boils down to a constraint that all strict initializers (case A) must precede (in left-to-right order) all non-strict initializers (cases C and D). There would be no extra constraint on the no-init cases (B, E), but the existing rule (from Java 1) would keep them before any references. Is case E uniquely strange? Because, different constructors might initialize E either before or after the super. non-value class TwoCons { __NonStrict int caseE; TwoCons(int x) { caseE = x; super(); } TwoCons() { super(); caseE = 42; } } I don’t think any of the other cases can have this ambiguity. I also think it would be harmless to let case E go anywhere in the left-to-right order. He doesn’t have an init, and he can’t be read during the pre-super phase anyway. The same reasoning applies to B. Both B and E fields still must be declared in the left-to-right order before any init that uses the B or E field, per the old (Java 1) rule. Boiling it down: Classify init actions (field initializers or instance initializers) as pre-super or post-super; only strict field initializers are pre-super. Then, require that a post-super init action must never precede (in left to right order) a pre-super init action (i.e., a strict field init). That would remove one kind of paradoxical appearance of disorder. Is it worth it? Maybe. — John