JDK-8359213 : Inner classes of type parameters emitted as raw types in signatures
  • Type: CSR
  • Component: tools
  • Sub-Component: javac
  • Priority: P4
  • Status: Closed
  • Resolution: Approved
  • Fix Versions: 26
  • Submitted: 2025-06-11
  • Updated: 2025-07-16
  • Resolved: 2025-07-15
Related Reports
CSR :  
Relates :  
Relates :  
Description
Summary
-------

A code that names an inner class is erroneously classified as raw and as a result is erroneously rejected.

Problem
-------

In the example that follows, javac determines erroneously that `getter` has a raw type (`G.Getter`). The type of `getter` is deduced as the raw type `Getters<T>.Getter` which is clearly not, since `T`, the type parameter of `Getters<T>` is not omitted. javac thinks that `G` is raw and raises the `Object cannot be converted to T` message in the following example. 
In this example `Getter` is simply an inherited member from the supertype `Getters<T>`:

```
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();       // incompatible types: Object cannot be converted to T
    }
}
```

Not looking into the bounds of `G` is a compiler bug.

A symmetrical situation occurs when there is a type application either explicitly or implicitly qualified and the enclosing type is not fully recalculated, for example the compiler crashes in the following two snippets.

```
class A<T> {
  protected class B<V> {}

  public static <T, M extends A<T>> void f(Object g) {
    @SuppressWarnings("unchecked")
    M.B<?> mapping = (M.B<?>) g;   // explicitly qualified type application
  }
}
```

```
class A<T> {
    class B<W> {
        public T rett() { return null; }
    }
}

class C extends A<String> {
    static class D {
        {
            B<?> b = null;         // implicitly qualified type application
            String s = b.rett();
        }
    }
}
```

The last examples are related to JDK-8357472.

Solution
--------

javac has the necessary functionality to normalize qualified type paths. Taken from the comment in Attr:

```
// class Tree<A> { class Visitor { ... } }
// class PointTree extends Tree<Point> { ... }
// ...PointTree.Visitor...
//
// Then the type of the last expression above is
// Tree<Point>.Visitor.
```

The compiler performs the necessary traversal while calculating the qualifying type path of `PointTree.Visitor` with the type `Tree<Point>.Visitor`. In that case `PointTree` is a concrete class. The solution fixes the cases where the prefix of the path is not only a concrete class but a type parameter with a bound. In our case the correct type is: `Getters<T>.Getter`. The corresponding issue appears in the visitor method for parameterized types, in Attr, as well.

Specification
-------------

We believe there are no changes needed in the specification but you can find linked the JDK-8030746; a currently open spec bug around the same area -- 4.10: Define subtyping for inner classes of parameterized types.

Comments
RN added: https://bugs.openjdk.org/browse/JDK-8362269 Thanks Joe!
16-07-2025

Moving to Approved contingent on a release note being written.
15-07-2025

[~darcy] We linked this issue with JDK-8357472 as well. These two tickets are tightly related. One shows a bug about the type calculation of an identifier and the other about the type calculation of a type application. What is needed is the same kind of fixe in both places of the compiler (checkIdInternal and visitTypeTest). As a result this CSR covers both.
09-07-2025

In terms of core reflection, now the generic parameter type of `test` is `Getters<T>.Getter`. There is no way for a parameterized type to represent its outer type as a type variable in the signature format; though the `java.lang.reflect.ParameterizedType` API is forward compatible to this change, except users already expect the outer type to be one of `null`, a `Class`, or a `ParameterizedType`, and would encounter breakages. Previously, the type was the raw type `Getters.Getter`, where the outer type is incorrectly not parameterized; if the inner class has a type argument, the outer type loses its arguments and only the inner class has partial type arguments, which is a constructible bad type violating the JLS
13-06-2025

Moving to Provisional, not Approved. Hmm. Would the behavior of core reflection (and other reflective APIs) on the shape of declarations in question also change as a review of the proposed fix?
13-06-2025