RFR: 8194743: Compiler implementation for Statements before super() [v14]
Alex Buckley
alex.buckley at oracle.com
Mon Sep 25 16:35:25 UTC 2023
Hi Archie,
Anything concerning the science of when nested classes have enclosing
instances is pretty scary, so I'd like to make sure we're all heading in
the same direction.
On 9/23/2023 8:19 AM, Archie Cobbs wrote:
> public class Test {
> public Test(int x) {
> }
>
> public Test() {
> this(switch (0) {
> default -> {
> class Local {
> }
> yield 0;
> }
> });
> }
> }
>
> According to JLS 21 §15.9.2, class `Local` does not have an enclosing instance.
>
> Yet here is what's actually emitted by the compiler:
>
> $ javap -classpath classes -c Test$1Local
> Compiled from "Test.java"
> class Test$1Local {
> final Test this$0;
>
> Test$1Local(Test);
> Code:
> 0: aload_0
> 1: aload_1
> 2: putfield #1 // Field this$0:LTest;
> 5: aload_0
> 6: invokespecial #7 // Method java/lang/Object."<init>":()V
> 9: return
> }
JLS 15.9.2 says what happens when an inner local class is instantiated,
but does not determine whether Local is declared as inner in the first
place. JLS 8.1.3 is where we find that Local is specified to be inner
(because it is not implicitly static). So, javac's emission of
Test$1Local is in line with the JLS -- great!
> An explicit goal of JEP 447 is **any existing program must compile to
> the same bytecode**. So if we adhere to that goal, local classes
> declared in pre-construction contexts must have outer instances.
I don't see this explicit goal stated in JEP 447. There's a sentence
about compilation in Testing -- "We will compile all JDK classes using
the previous and new versions of the compiler and verify that the
resulting bytecode is identical." -- but that sounds merely like a
nice-to-have property, and one which holds only for JDK classes.
If 100% class file compatibility for all existing source code is a goal,
then please say that in the Goals and give an example in the
Description. ("compiles to same bytecode" is actually a higher bar than
binary compatibility, which is why I didn't say "100% binary
compatibility".)
> I think this is the most natural behavior anyway. Anonymous classes,
> with their "immediate enclosing instance with respect to superclass S",
> and their declare-and-instantiate-all-at-once property, are the
> oddballs. It's appropriate for them to have a special rule where their
> implicit outer instance "disappears" in a pre-construction context
> because it would never be possible to provide one.
I'm not, per se, disagreeing with specifying that local classes declared
in a pre-construction context are inner. It's certainly attractive from
a compatibility POV to be able to move a local class declaration around
within a constructor body (maybe before super(..)/this(..), maybe after)
and observe no class file change because the local class is always inner
(unless it's a local enum class or local record class of course). On the
other hand, we have to guarantee that no instantiation of the local
class is possible before super(..)/this(..) has completed normally.
Here's a program that tries to instantiate a Local which observes x with
a value other than 5. What does the JLS say about the program? What does
javac do?
public class Test {
public int x = 5;
public Test(int i) {}
public Test() {
class Local {
{ System.out.println(x); }
}
try {
this(switch (0) { default -> throw new Exception(); });
} finally {
new Local(); // Legal or illegal?
}
}
}
Don't worry about JEP 401 until JEP 447 has thoroughly clarified the
matter of whether such local classes are inner or not.
Alex
> So my suggestion is to alter this wording in JEP 401:
>
>> Local and anonymous classes may be declared, but (as in a static context) they have no enclosing instance. Inner classes may refer to enclosing instances or captured enclosing variables from their own regulated constructors without error.
>>
>> These rules coincide with the restrictions imposed in a pre-construction context, as described by [JEP 447](https://openjdk.org/jeps/447), except that they allow for writes to instance fields.
>
> To this:
>
>> Local classes may be declared, but since they require an enclosing instance, they can't be instantiated. Anonymous classes may be declared, but (as in a static context) they have no enclosing instance. Inner classes may refer to enclosing instances or captured enclosing variables from their own regulated constructors without error.
>>
>> These rules coincide with the restrictions imposed in a pre-construction context, as described by [JEP 447](https://openjdk.org/jeps/447), except that they allow for writes to instance fields.
>
> Also:
>
>> I think there are already similar rules implemented for anonymous classes before this JEP, where the anonymous `Cat$1` (printing "Running") does not require an implicit `Cat` for its constructor while `Cat$2` (printing "Meow") does after compilation:
>
> As remarked above, anonymous classes are different. It's not possible to declare them in one place and instantiate them in another place, so they indeed will not have enclosing instances when declared in a pre-construction context but will have them otherwise, and this behavior is not changing.
>
> -------------
>
> PR Comment: https://git.openjdk.org/jdk/pull/13656#issuecomment-1732340240
More information about the compiler-dev
mailing list