spec clarification: type annotations on static nested types

classic Classic list List threaded Threaded
5 messages Options
Reply | Threaded
Open this post in threaded view
|

spec clarification: type annotations on static nested types

Liam Miller-Cushon
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

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,
Reply | Threaded
Open this post in threaded view
|

Re: spec clarification: type annotations on static nested types

Liam Miller-Cushon
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 <[hidden email]> 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

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,

Reply | Threaded
Open this post in threaded view
|

Re: spec clarification: type annotations on static nested types

Alex Buckley-3
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 <[hidden email]
> <mailto:[hidden email]>> 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.@A One yy;  // Legal
   T.@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,
>
>
Reply | Threaded
Open this post in threaded view
|

Re: spec clarification: type annotations on static nested types

Liam Miller-Cushon
Thanks! I have some follow-up questions inline -

On Mon, Jan 8, 2018 at 12:42 PM, Alex Buckley <[hidden email]> wrote:
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:
... 
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:

I didn't express what I was thinking, sorry. I agree we're not interested in the static-ness of the outer type.

I think javac currently emits type_path_kind=1 steps only when descending in to a member type that is non-static, and that the "location=[INNER_TYPE]" bit of javap's output corresponds directly to the type_path structure in the class file.

Is that accurate, and if so is it to spec?

My understanding of the `@Foo C . D x;` example was that no type_path is necessary even though C is a member of Test, because C is a static member and Test is 'scoping construct' that is not a valid type annotation target.

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.

Similar to the other example, isn't "@A actually applies to the type of the static member Two" the only interpretation for a type annotation attribute that applies to `T . Two`, since T is a scoping construct and is not a valid type annotation target?
Reply | Threaded
Open this post in threaded view
|

Re: spec clarification: type annotations on static nested types

Liam Miller-Cushon
I double-checked that the type paths in the class file javac emits are:

  void f(@A One x) {}
...
type_path_length 1
type path entry [0]: type_path_kind: 1, type_path_index: 0

  void g(@A Two x) {}
...
type_path_length 0

My understanding is still that javac uses type_path_kind=1 to step in to (non-static) inner classes, and reflection interprets type_path_kind=1 as stepping in to member classes (both non-static and static).
Is this a javac bug, or a reflection bug, or am I confused?

Thanks,

On Mon, Jan 8, 2018 at 2:25 PM, Liam Miller-Cushon <[hidden email]> wrote:
Thanks! I have some follow-up questions inline -

On Mon, Jan 8, 2018 at 12:42 PM, Alex Buckley <[hidden email]> wrote:
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:
... 
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:

I didn't express what I was thinking, sorry. I agree we're not interested in the static-ness of the outer type.

I think javac currently emits type_path_kind=1 steps only when descending in to a member type that is non-static, and that the "location=[INNER_TYPE]" bit of javap's output corresponds directly to the type_path structure in the class file.

Is that accurate, and if so is it to spec?

My understanding of the `@Foo C . D x;` example was that no type_path is necessary even though C is a member of Test, because C is a static member and Test is 'scoping construct' that is not a valid type annotation target.

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.

Similar to the other example, isn't "@A actually applies to the type of the static member Two" the only interpretation for a type annotation attribute that applies to `T . Two`, since T is a scoping construct and is not a valid type annotation target?