<div dir="ltr"><div dir="ltr">On Mon, Jun 10, 2024 at 4:24 AM Maurizio Cimadamore <<a href="mailto:maurizio.cimadamore@oracle.com">maurizio.cimadamore@oracle.com</a>> wrote:</div><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><u></u>

  
  <div>We basically have classes which have no enclosing instances, but
      where enclosing members can be accessed (via other means).
    </div></blockquote><div><br></div><div>Hah, you've already got me in a screeching halt on the first sentence here, because I'm inferring a different meaning of "enclosing instance" than what I knew (and you also throw in "enclosing members", phew :)<br></div><div><br></div><div>Just so we're on the same page - with respect to the anonymous Runnable class below, how would you describe "ClassA.this" and "ClassB.this"?<br></div><div><br></div><div style="margin-left:40px"><span style="font-family:monospace">class ClassA {<br>    class ClassB {<br>        ClassB() {<br>            this(new Runnable() {<br>                public void run() {<br>                    ClassA.this.hashCode();<br>                }<br>            });<br>        }<br>        ClassB(Runnable r) {<br>        }<br>    }<br>}</span><br></div><div><br></div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div>Historically, the JLS used to treat this/super calls as a static
      context (8.8.7.1).</div></blockquote><div><br></div><div>That's what the JLS said, but of course that's not what the compiler ever implemented (JDK-8301649).</div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div>And then JLS also used to say that inner
      classes defined in a static context had no enclosing instance
      (8.1.3).
    <p>So, for this particular JEP, I think there's a choice in front of
      us:</p>
    <p>* do we keep JLS as is (and fix javac to do what the spec has
      always said the behavior was) ?<br>
      * or, do we keep javac as is and then tweak the JLS to model what
      the implemented behavior is?</p>
    <p>The latter path seems to have been chosen. Do we have a feeling
      that local classes in pre-construction context will be radically
      more common than they are today? Or are there other reasons behind
      this decision?<br></p></div></blockquote><div>Here is my own understanding of the reasoning behind choosing option #2 (others may differ).</div><div><br></div><div>First of all, the question of how to treat classes in early construction contexts is not new - it's just become a lot more obvious because of the new flexibility offered in the JEP. <br>The "early construction context" already exists in constructor parameters, although of course more shoe-horning required doing it the old way.</div><div><br></div><div>For example this code compiles fine in JDK 17:<br></div><div><br></div><div style="margin-left:40px"><span style="font-family:monospace">class EarlyLocal {<br>    int x;<br>    class Inner {<br>        Inner(int x) {<br>            this(switch (x) {<br>                default -> {<br>                    class Local { { EarlyLocal.this.hashCode(); } }<br>                    yield 0.0;<br>                }<br>            });<br>        }<br>        Inner(double x) {<br>        }<br>    }<br>}</span></div><div><br></div></div><div class="gmail_quote">So that means there is some amount of code out there (unknown how much) which is, for example, referencing 2nd outer instances from anonymous classes that are created inside constructor parameters (a more likely example of that is the one given earlier in this email thread (May 30th, "class Outer")).</div><div class="gmail_quote"><br></div><div class="gmail_quote">A second reason is that access to 2nd & further outer instances is something developers understand and expect, and denying it to them for no particularly good reason (my opinion) in one place (early construction) but not anywhere else will strike them as arbitrarily inconsistent and annoying. Putting this another way, developers already have an intuitive understanding that you can't access 'this' prior to superclass construction, so that exception is already baked into their lives. If we make early construction contexts truly static (option #1), then we're creating yet another exception they will have to learn and work around. Contrast with option #2 which just extends everything they already know and expect in a natural way.<br></div><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div><p>
    </p>
    <p>Perhaps the tacit understanding was that "javac got this right",
      but looking at the increasing numbers of bugs filed recently in
      this area, and after looking more at how the code works, it seems
      that javac doesn't have a very principled way to get there, and it
      is in fact rather easy to come up with examples which defeats
      javac's translation strategy.</p></div></blockquote><div>I agree that the increasing number of bugs is alarming. But I attribute this to poor internal documentation and years of early construction fixes. If you look back at Jira historically you'll see a lot of bugs that relate specifically to early construction context issues (in constructor parameters, obviously). The NOOUTERTHIS flag is an example of how these got fixed with (arguably) a quick hack rather than a more principled refactoring. IMHO, saying that these are reasons to downgrade the spec is the tail wagging the dog.<br></div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div>I agree this is confusing and we need better terminology. But I'm
      having trouble with the model that A is an enclosing instance of
      B, but A's members are not accessible in B. I understand why the
      spec does this - but it would be vastly simpler (at least from an
      implementation perspective) then to say that A is not in fact an
      enclosing instance of B, but that B still has some other enclosing
      instance (some enclosing class of A). A model such as this would
      inform the translation strategy quite clearly. In fact, I'm
      starting to think if we really want to go down the path of
      non-static local classes in pre-construction contexts, then javac
      should do things this way regardless of how things are specified,
      as that will make the translation strategy more uniform and less
      buggy than the one we have today.
    </div></blockquote><div><br></div><div>This makes plenty of sense to me and I'd be happy with it.</div><div><br></div><div>Frankly I don't care what the terminology is, as long as it is precise/unambiguous and universally agreed-to. We all need to be speaking the same language (and that includes internal compiler method names, variable names, and comments - with the caveat that this part is an openjdk/javac discussion probably more appropriate for compiler-dev).</div></div><div><br></div><div>-Archie<br></div><div><br></div><span class="gmail_signature_prefix">-- </span><br><div dir="ltr" class="gmail_signature">Archie L. Cobbs<br></div></div>