<div dir="ltr"><div>Hi Maurizio,</div><div><br></div><div>Thanks for looking into these issues. I agree this is definitely one of those "we need to rethink and refactor" opportunities. Please let me know if/how I can help. So far I've done some complaining but not much else :)</div><div><br></div><div>I'm trying to wrap my head around what's needed and have tried to write this out in detail mainly for my own understanding. Please tell me if the below sounds correct to you.<br></div><div><br></div><div>Observation: this issue doesn't really affect non-static member inner classes: they can never be declared in an early construction context of their directly enclosing class. So they always have an immediate outer instance, and it's always available and usable (or explicitly provided via qualified new). So all the complexity comes with local and anonymous classes, especially when they are declared inside early construction contexts with various levels of nesting. Fortunately, the JEP treats local and anonymous classes basically the same way.<br></div><div><br></div><div>Some terminology (just placeholders for now, I'm mainly trying to nail down the logic):<br></div><div><ol><li>Every class declaration C has zero or more lexically enclosing class declarations; let's call them E₁, E₂, E₃... ordered from the inside out.</li><li>For ease of terminology, define the degenerate case E₀ = C<br></li><li>For each Eᵢ, there is possibly an <b>outer instance</b> Oᵢ <b>defined</b> for C (see below)<br></li><li>For ease of terminology, define the degenerate case O₀ = C.this.</li><li>When is Oᵢ <b>defined</b> for C?</li><ol><li>O₀ is always defined</li><li>Let k ≥ 0 be the smallest value such that Eₖ is static (enum, record, declared in a static context, etc.)<br></li><li>For all i ≤ k, the outer instance Oᵢ is defined for C and it has type Eₖ</li><li>For all i > k there is no outer instance Oᵢ defined for C</li></ol><li>If Oᵢ is defined, it may also be the case that Oᵢ is <b>accessible</b> in C (in non-static contexts):</li><ol><li>Oᵢ is <b>accessible</b> in C if and only if C does not appear in an early construction context of Eᵢ</li></ol></ol></div><div>From the above, you can see that the outer classes for which a corresponding outer instance is defined includes C and is a contiguous range up until you hit the first static class/context. However, the set of outer classes for which the corresponding outer instance is both defined <i>and accessible</i> is an arbitrary subset of that contiguous range. In effect we create an "inaccessibility hole" at any class Eₖ for which C is inside an early construction context of Eₖ.</div><div><br></div><div>Observation: Suppose Oₖ of type Eₖ is defined for C. Then for any h > k, Oₕ is defined (accessible) for Eₖ if and only if it is defined (accessible) for C. In other words, if a class F encloses both C and Eₖ, then C and Eₖ agree on whether F has a defined and/or accessible outer instance. This is simply because C and Eₖ are lexically in the "same place" with respect to F.<br></div><div><br></div><div>Define the <b>compiler outer instance</b> for C to be that Oₖ for which k > 0, Oₖ is both defined and accessible, and where k is minimal (if such a thing exists).</div><br><div>OK now given the above what does the compiler need to do?</div><div><div><div><br></div><div>Part 0: The compiler needs to be able to calculate, given any class C, whether C has a compiler outer instance (let's call it Oₖ) and its type (let's call it Eₖ)</div><br></div></div><div>Part 1: When compiling some class C for which a compiler outer instance exists:</div><div><ol><li>Calculate the type Eₖ</li><li>Add a synthetic constructor parameter Oₖ of type Eₖ to each constructor of C<br></li><li>Add a synthetic field of type Eₖ in which to store Oₖ ("this$n")<br></li><li>Store Oₖ in that field in each constructor of C (very first thing)<br></li></ol><div>Part 2: Compiling the expression "Foo.this" in the context of some class C:<br></div><ol><li>If C has no compiler outer instance, then error unless Foo = C.</li><li>Otherwise let Oₖ with type Eₖ be the compiler outer instance for C<br></li><li>For expressions like "Eₕ.this" where 0 < h < k: generate an error ("no instance in scope")<br></li><li>For expressions like "Eₖ.this": evaluate to Oₖ as follows:</li><ol><li>If expression occurs in an early construction context of C, then read the synthetic constructor parameter of type Eₖ (we can do this because we are necessarily in a C constructor)</li><li>Otherwise. read the synthetic field "this$0"</li></ol><li>For expressions like "Eₕ.this" where h > k as follows:</li><ol><li>Evaluate "Eₖ.this" per previous steps to get Oₖ</li><li>Recurse, i.e., evaluate "Eₕ.this" in the context of class Eₖ. This is valid due to the earlier observation.<br></li></ol></ol>Part 3: When compiling "new C()" (or "C::new") where C is some class having a compiler outer instance:</div><div><ol><li>Calculate the type Eₖ - the type of the synthetic parameter Oₖ we need to prepend to the C() constructor invocation<br></li><li>Let's assume "new C()" is inside some method, constructor, or initializer of some class T</li><li>Evaluate "Eₖ.this" in the context of class T (see Part 2)<br></li><li>Proceed with construction, prepending the synthetic Oₖ parameter<br></li></ol></div><div>Hopefully this is close to capturing how it "should" work...?<br></div><div><br></div><div>-Archie<br></div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Wed, Jun 12, 2024 at 7:07 AM Maurizio Cimadamore <<a href="mailto:maurizio.cimadamore@oracle.com" target="_blank">maurizio.cimadamore@oracle.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">Hi,<br>
as we discussed through the details of the new JLS rules in JEP 482, we <br>
have found several issues where javac is emitting incorrect code (e.g. <br>
attempting to capture enclosing instances that are still under <br>
construction).<br>
<br>
In order to allow us to look at these issues more holistically, I've <br>
created a new JBS label, namely "javac-pre-capture". Here's a query to <br>
show them all:<br>
<br>
<a href="https://bugs.openjdk.org/issues/?jql=labels%20%3D%20javac-pre-capture" rel="noreferrer" target="_blank">https://bugs.openjdk.org/issues/?jql=labels%20%3D%20javac-pre-capture</a><br>
<br>
As stated above, this label is meant for issue related to issues that <br>
have to do broken translation strategy of enclosing instance references. <br>
Such issues can manifest them in different ways:<br>
<br>
* compiler crashes when compiling correct code (e.g. <br>
<a href="https://bugs.openjdk.org/browse/JDK-8334037" rel="noreferrer" target="_blank">https://bugs.openjdk.org/browse/JDK-8334037</a>)<br>
* "late" compiler error (e.g. in Lower) as the compiler can't resolve <br>
an enclosing instance (e.g. <a href="https://bugs.openjdk.org/browse/JDK-8334121" rel="noreferrer" target="_blank">https://bugs.openjdk.org/browse/JDK-8334121</a>)<br>
* compiler succeds when compiling a correct program, but the compiled <br>
program doesn't verify (we don't have an instance of this, yet, but it's <br>
something bound to occur)<br>
<br>
The scope of this label is somewhat (deliberately) narrow. As such, the <br>
label does NOT cover other issues pertaining to JEP 482, such as<br>
<br>
* compiler attempting to translate a bad program <br>
(<a href="https://bugs.openjdk.org/browse/JDK-8334043" rel="noreferrer" target="_blank">https://bugs.openjdk.org/browse/JDK-8334043</a>)<br>
* compiler generating invalid code for reasons that have nothing to do <br>
with capture (<a href="https://bugs.openjdk.org/browse/JDK-8332106" rel="noreferrer" target="_blank">https://bugs.openjdk.org/browse/JDK-8332106</a>)<br>
<br>
(if we think that a more general label would be useful to mark all <br>
issues that have to do with pre-construction context, we can do that too).<br>
<br>
Cheers<br>
Maurizio<br>
</blockquote></div><br clear="all"><br><span class="gmail_signature_prefix">-- </span><br><div dir="ltr" class="gmail_signature">Archie L. Cobbs<br></div>