JDK-8357653 : Inner classes of type parameters emitted as raw types in signatures
  • Type: Bug
  • Component: tools
  • Sub-Component: javac
  • Affected Version: 21,25
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • OS: generic
  • CPU: generic
  • Submitted: 2025-05-23
  • Updated: 2025-07-22
  • Resolved: 2025-07-16
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 26
26 b07Fixed
Related Reports
CSR :  
Relates :  
Relates :  
Relates :  
Sub Tasks
JDK-8362269 :  
Description
A DESCRIPTION OF THE PROBLEM :
The test method fails to compile, as javac believes that getter.get() returns just Object:

class Scratch {
    static abstract class Getters<T> {
        abstract class Getter {
            abstract T get();
        }
    }

    static class Usage<T, G extends Getters<T>> {
        public T test(G.Getter getter) {
            return getter.get();
        }
    }
}


FREQUENCY : always

If we merely address the issue in allparams() (as in the new backed-off 8343580), the test method compiles, but the emitted signature is as if it was T test(Scratch.Getters.Getter getter), where the Getter inner class appears as a raw type.

For the attached test on 25 mainline JDK as of 2025 May 23, this fails compilation with such a message:

TypeVarInners.java:11: error: incompatible types: TypeVarInners.Base cannot be converted to S
	return handler.owner();
	                    ^
  where S is a type-variable:
    S extends TypeVarInners.Base<S> declared in method <S>getOwner(TypeVarInners.Base.Handler)
1 error
error: compilation failed

Note the method's signature is already wrong in the error message: it should be <S>getOwner(S.Handler)

Note that assembleClassSig independently examines outer to detect whether it is raw or not:

if (outer.allparams().nonEmpty()) { 
   // rawOuter detection
   // code that emits generic encl type signature
} else {
   append(externalize(c.flatname));
}
Comments
Changeset: 5e4a2ead Branch: master Author: Aggelos Biboudis <abimpoudis@openjdk.org> Date: 2025-07-16 10:52:26 +0000 URL: https://git.openjdk.org/jdk/commit/5e4a2ead714814cb4eb90ca88debc226f9c75864
16-07-2025

The underlying cause of all these issues is some complex logic in the javac compiler that is used to normalize the qualifier of an inner type. This logic is used in two cases: 1. there is a reference to an inner type `I`, but there's no enclosing type in the source 2. there is a reference to an inner type `O.I`, and the enclosing type of `I` is a generic type, but `O` is not In (1), javac attempts to determine the the enclosing type of `I` by looking at the enclosing scope. For instance, if the current class is nested in a class `F` such that `F` is a (possibly generic) subclass of `E`, where `E` is the class declaring `I`, then we return `F<T>.I`. In (2) javac attempts to "lift" the enclosing type `O` to a type `F<T>` where `F` is a subclass of `E` -- the class declaring `I`. This normalization logic should occur in all code paths, but this is currently not the case. A first problem is that normalization is not applied to type-variable receivers (the original example in this bug report). That is easy to fix. A second (and deeper) issue is that Javac applies different kinds of normalization when `I` is a parameterized vs. non-parameterized type. In the first case, javac applies the normalization in `Attr.visitTypeApply`, and the normalization is carried out using (incorrectly, see below) `Types::asOuterSuper`. In the second case, the normalization is applied in `Attr.checkIdInternal`, and the function used is `Types::asEnclosingSuper`. These two normalization functions are vaguely related: they both take an input type T and a target symbol S. These functions attempt to find a concrete supertype of T that matches the symbol S -- e.g a supertype U such that U.tsym == S. The way in which this is done is slightly different in the two functions: * `asOuterSuper` will keep recursing on the enclosing type of the input type T -- e.g. if T has no supertype U such that U.tsym == S, then we compute `T = T.getEnclosingType` and we repeat (until there's no more enclosing types) * `asEnclosingSuper` does something similar -- e.g. it will keep recursing until it finds a match. However, when T is updated, it is updated with either `T.getEnclosingType` (if that exists) or, `T.tsym.owner.enclClass().type` (otherwise) Ignoring accidental complexity, the two functions really serve two very different use cases: A. a member m is accessed on a type T, so we need to compute the type of the access expression T::m B. an inner type is referenced from a context where no qualifier is present, so we need to infer a qualifier from the current class (and all its enclosing classes) Note that the normalization steps described here really refer to use case (B). This means that (a) both `checkIdInternal` and `visitTypeApply` should use `asEnclosingSuper`, and (b) the implementation of `asEnclosingSuper` should never try to use `getEnclosingType`.
07-07-2025

Another bug ``` class A<T> { class B<W> { public T rett() { return null; } } } class C extends A<String> { static class D { { B<?> b = null; String s = b.rett(); } } } ``` This fails with: Test.java:10: error: improperly formed type, type arguments given on a raw type B<?> b = null; ^ Test.java:11: error: incompatible types: T cannot be converted to String String s = b.rett(); ^ However, if class B is made non-generic, then the program compiles with no issues.
07-07-2025

A pull request was submitted for review. Branch: master URL: https://git.openjdk.org/jdk/pull/25451 Date: 2025-05-26 15:51:06 +0000
26-05-2025