RFR: 8349754: Invalid "early reference" error when class extends an outer class [v2]
Maurizio Cimadamore
mcimadamore at openjdk.org
Mon Feb 17 17:17:09 UTC 2025
On Fri, 14 Feb 2025 23:34:53 GMT, Archie Cobbs <acobbs at openjdk.org> wrote:
>>> So... N.this is always used to denote an enclosing instance...
>>
>> I think that's not always true... for example, this compiles just fine (with or without the explicit `super()`):
>>
>> class Myself {
>> int x;
>> Myself() {
>> Myself.this.x = 42;
>> super();
>> }
>> }
>
> Hmm, re-reading that I think maybe I misinterpreted your statement... For clarity, let's just enumerate the possibilities.
>
> Suppose we are inside a class `C` and there is some instance field `x` accessible to us.
>
> Then any such `x` must be declared in one (_or both_) of these two "hierarchies":
> * **Inner hierarchy** - `x` is declared in `C` or (non-privately) in a superclass of `C`
> * **Outer hierarchy** - `x` is declared in a some outer class `O` containing `C`
>
> In the case `x` is declared in both hierarchies, it may _also_ be the case that `O` is a superclass of `C`, but for our purposes that's "just a coincidence" - it doesn't affect how many `x` field's are accessible to us (i.e., two).
>
> Now what can the various expressions mean depending on which of the two hierarchies `x` is declared in?
>
> | Inner? | Outer? | `C` extends `O` | `x` | `C.this.x` | `O.this.x` | Comments |
> | :---: | :---: | :---: | :---: | :---: | :---: | :--- |
> | Yes | No | N/A | Inner | Inner | _N/A_ | Simple inner case |
> | No | Yes | No | Outer | _N/A_ | Outer | Simple outer case |
> | No | Yes | Yes | Outer | _N/A_ | Outer | `x` must be private | |
> | Yes | Yes | No | Inner | Inner | Outer | |
> | Yes | Yes | Yes | Inner | Inner | Outer | |
>
> So it looks like we can say this about an (presumably valid) expression of the form `N.this.x`:
> * If `N` = `C`, then we are always referring to the "inner" `x` - therefore, it's an early reference to `C` if and only if we are in an early construction context for `C`
> * If `N` ≠ `C`, then we are always referring to the "outer" `x` - therefore, an early reference if and only if we are in an early construction context for `N`
>
> So according to that logic, you are correct in saying, with regards to expressions like `N.this.x`, that "the only thing that matters is whether `N` is in early construction context".
>
> Please check my work...
Yes, the spec is clear in saying:
> A class or interface O is the zeroth lexically enclosing class or interface declaration of itself.
And
> Let n be an integer such that TypeName denotes the n'th lexically enclosing class or interface declaration of the class or interface whose declaration immediately encloses the qualified this expression. The value of a qualified this expression TypeName.this is the n'th lexically enclosing instance of this.
So, if we are inside N and we see N.this, we have to assume it's the 0-th enclosing instance we're talking about (e.g. the class being declared).
Your table looks correct - when you say `x must be private`, I'd add that this is true only for the implicit case where access is just `x`. E.g. if `x` is not inherited, then it's resolved to the Outer, otherwise it's resolved to the class being declared (which might create early construction issues).
This situation is not dissimilar from the one we have encountered here:
https://git.openjdk.org/jdk/pull/19904
Where my initial prototype was "skipping" over early references if non-early enclosing references were available. While in that case it was deemed too subtle (for good reasons), I think in this case we're bound by the existing meaning of `x` -- which depends on whether `x` is, or is not a member of the current class.
-------------
PR Review Comment: https://git.openjdk.org/jdk/pull/23545#discussion_r1958571432
More information about the compiler-dev
mailing list