spec clarification: type annotations on static nested types

Alex Buckley alex.buckley at oracle.com
Mon Jan 8 20:42:46 UTC 2018


A type annotation can apply to a type that's nested (i.e. deeper) in a 
*static* type though. Adapting an example from JLS 9.7.4:

---
import java.lang.annotation.ElementType;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.RUNTIME)
@interface Foo {}

class Test {
   static class C {
     class D {}
   }

   @Foo C . D   x;  // Legal. @Foo annotates the type which occurs in a 
field declaration.

   C . @Foo D   y;  // Legal. @Foo annotates the type of a non-static 
member of C.
}
---

In the second case, the nested type which is being annotated is "C . D". 
The D part denotes a non-static nested type, yes, but the first type 
that we hit when stepping through the path -- C -- is static. The 
storage of @Foo thus involves a type_path that descends "deeper in a 
nested type" -- mentioning anything about static here would be wrong. As 
a separate issue, the location where we ultimately find @Foo is, in 
fact, a non-static member type, so javap helpfully shows that location 
as INNER_TYPE:

   Test$C$D y;
     RuntimeVisibleTypeAnnotations:
       0: #12(): FIELD, location=[INNER_TYPE]

(More below)

On 1/8/2018 11:39 AM, Liam Miller-Cushon wrote:
> Hello, does anyone have thoughts on whether this is a javac issue, or a
> spec and core reflection issue?
>
> Thanks,
>
> On Thu, Nov 30, 2017 at 11:04 AM, Liam Miller-Cushon <cushon at google.com
> <mailto:cushon at google.com>> wrote:
>
>     JVMS 9 4.7.20.2-A says that a type_path_kind value of 1 is used to
>     indicate a type annotation "is deeper in a nested type". I believe
>     this should be "deeper in a *non-static* nested type". A comment in
>     JDK-8027263 confirms that type path entries are used for inner
>     classes, but not for static nested classes.
>
>     Example:
>
>     ```
>     import static java.lang.annotation.ElementType.TYPE_USE;
>     import static java.lang.annotation.RetentionPolicy.RUNTIME;
>
>     import java.lang.annotation.Retention;
>     import java.lang.annotation.Target;
>     import java.lang.reflect.AnnotatedType;
>     import java.util.Arrays;
>
>     class T {
>        @Retention(RUNTIME)
>        @Target(TYPE_USE)
>        @interface A {}
>
>        class One {}
>
>        static class Two {}
>
>        void f(@A One x) {}
>
>        void g(@A Two x) {}
>
>        public static void main(String[] args) throws Exception {
>          debug(T.class.getDeclaredMethod("f",
>     One.class).getAnnotatedParameterTypes()[0]);
>          debug(T.class.getDeclaredMethod("g",
>     Two.class).getAnnotatedParameterTypes()[0]);
>        }
>
>        static void debug(AnnotatedType type) {
>          System.err.println("type annotations: " +
>     Arrays.toString(type.getAnnotations()));
>          System.err.println(
>              "owner type annotations: "
>              +
>     Arrays.toString(type.getAnnotatedOwnerType().getAnnotations()));
>        }
>     }
>     ```
>
>     Using type_path_kind=1 for inner classes, but not for static member
>     classes, is consistent with current javac behaviour. In the example,
>     the type path for the annotation on `@A One` is `[INNER_TYPE]` (One
>     is an inner class), and the type path for the annotation on `@A Two`
>     is empty (Two is a static member class):
>
>     $ javap -v T
>     ...
>        void f(T$One);
>     ...
>          RuntimeVisibleTypeAnnotations:
>            0: #31(): METHOD_FORMAL_PARAMETER, param_index=0,
>     location=[INNER_TYPE]
>     ...
>        void g(T$Two);
>     ...
>          RuntimeVisibleTypeAnnotations:
>            0: #31(): METHOD_FORMAL_PARAMETER, param_index=0

I don't like how @A has been bounced out of the nested type "T . Two" so 
that it allegedly applies to the type of the formal parameter as a 
whole. @A actually applies to the type of the static member Two that is 
declared by T.

It's fine to annotate the type of a static member. What the 
admissibility rule in JLS 9.7.4 is trying to prevent is annotating that 
type from too far away:

   T. at A One yy;  // Legal
   T. at A Two zz;  // Legal

   @A T.One yyy; // Legal
   @A T.Two zzz; // Not legal. Because Two is static, T is deemed to be 
more of a scoping construct than a type, so type annotations shan't apply.

Alex

>     Using type_path_kind=1 for inner classes is not consistent with the
>     current behaviour of core reflection.
>     sun.reflect.annotation.AnnotatedTypeFactory adjusts type paths in
>     the implementation of getAnnotatedOwnerType() for AnnotatedType and
>     AnnotatedParameterizedType, and when handling ParameterizedTypes in
>     nestingForType, without considering whether the nested type is
>     static. This leads to apparently incorrect results for
>     getAnnotations(). Note that the type annotations on `@A Two` are
>     reported as being on both `Two` and its enclosing type `T`:
>
>     $ javac T.java && java T.
>     # `@A One`
>     type annotations: [@T$A()]
>     owner type annotations: []
>     # `@A Two`
>     type annotations: [@T$A()]
>     owner type annotations: [@T$A()]
>
>     My questions are:
>     * if my understanding of JVMS 9 4.7.20.2-A is correct, can the spec
>     be updated to clarify that type_path_kind=1 means "deeper in a
>     non-static nested type", not "deeper in a nested type"?
>     * is javac's output for this example correct, and if so is this a
>     bug in AnnotatedTypeFactory?
>
>     Thanks,
>
>


More information about the compiler-dev mailing list