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

Reply via email to