Records and type annotations

Vicente Romero vicente.romero at oracle.com
Tue Dec 31 17:01:44 UTC 2019


Hi Michael,

Thanks for the detailed report, I have created [1] to track it,

Vicente

[1] https://bugs.openjdk.java.net/browse/JDK-8236597

On 12/22/19 12:32 PM, Michael Hixson wrote:
> Hello,
>
> I'm trying records with type annotations and I notice a couple of
> unexpected behaviors in terms of how the annotations are applied.  I'm
> using build 14-ea+28-1366.
>
> 1. If I explicitly declare the canonical constructor but omit the
> parameter list, the parameters of that constructor don't inherit the
> type annotations of the components.  I expected the type annotations
> to be inherited in the same way they would have been had I not
> declared the canonical constructor.
>
> 2. The type annotations of the components appear in the annotations of
> the accessor methods rather than in the annotated return types of
> those methods.  I expected the type annotations to be in the annotated
> return types because that's what happens when I write out a regular
> non-record class with type annotations.
>
> I've inlined the source code I'm using and its output at the end of
> this email.  The output for Record2's constructor shows issue #1 (its
> annotatedType is different than Record1's), and the output for the
> three classes' accessor methods shows issue #2 (the regular class's
> method is different from the two records').
>
> Issue #1 seems like the more problematic of the two.  It feels like my
> types are being mangled.    "Hey, who said you could change the type
> from `@Nullable String` to `String`, which is a different and
> incompatible type?  Not me."  Issue #2 is just something that caught
> my eye while investigating #1.
>
> -Michael
>
> -------------------------------
>
> package example;
>
> import java.lang.annotation.ElementType;
> import java.lang.annotation.Retention;
> import java.lang.annotation.RetentionPolicy;
> import java.lang.annotation.Target;
> import java.util.Arrays;
>
> public class RecordsTest {
>
>    @Retention(RetentionPolicy.RUNTIME)
>    @Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER })
>    @interface Nullable {}
>
>    static class RegularClass {
>      private final @Nullable String t;
>      public RegularClass(@Nullable String t) { this.t = t; }
>      public @Nullable String t() { return t; }
>    }
>
>    record Record1(@Nullable String t) {}
>
>    record Record2(@Nullable String t) {
>      public Record2 {}
>    }
>
>    public static void main(String[] args) {
>      describeClass(RegularClass.class);
>      describeClass(Record1.class);
>      describeClass(Record2.class);
>    }
>
>    static void describeClass(Class<?> clazz) {
>      System.out.println(clazz);
>
>      for (var field : clazz.getDeclaredFields()) {
>        System.out.println(
>            "  field " + field);
>        System.out.println(
>            "    annotatedType "
>                + field.getAnnotatedType());
>        System.out.println(
>            "    annotations "
>                + Arrays.toString(field.getAnnotations()));
>      }
>
>      for (var constructor : clazz.getConstructors()) {
>        System.out.println(
>            "  constructor " + constructor);
>        for (var parameter : constructor.getParameters()) {
>          System.out.println(
>              "    parameter " + parameter);
>          System.out.println(
>              "      annotatedType "
>                  + parameter.getAnnotatedType());
>          System.out.println(
>              "      annotations "
>                  + Arrays.toString(parameter.getAnnotations()));
>        }
>      }
>
>      for (var method : clazz.getDeclaredMethods()) {
>        var name = method.getName();
>        if (name.equals("equals")
>            || name.equals("hashCode")
>            || name.equals("toString"))
>          continue;
>
>        System.out.println(
>            "  method " + method);
>        System.out.println(
>            "    annotatedReturnType "
>                + method.getAnnotatedReturnType());
>        System.out.println(
>            "    annotations "
>                + Arrays.toString(method.getAnnotations()));
>      }
>
>      System.out.println();
>    }
> }
>
> -------------------------------
>
> class example.RecordsTest$RegularClass
>    field private final java.lang.String example.RecordsTest$RegularClass.t
>      annotatedType @example.RecordsTest$Nullable() java.lang.String
>      annotations []
>    constructor public example.RecordsTest$RegularClass(java.lang.String)
>      parameter java.lang.String arg0
>        annotatedType @example.RecordsTest$Nullable() java.lang.String
>        annotations []
>    method public java.lang.String example.RecordsTest$RegularClass.t()
>      annotatedReturnType @example.RecordsTest$Nullable() java.lang.String
>      annotations []
>
> class example.RecordsTest$Record1
>    field private final java.lang.String example.RecordsTest$Record1.t
>      annotatedType @example.RecordsTest$Nullable() java.lang.String
>      annotations []
>    constructor public example.RecordsTest$Record1(java.lang.String)
>      parameter java.lang.String t
>        annotatedType @example.RecordsTest$Nullable() java.lang.String
>        annotations []
>    method public java.lang.String example.RecordsTest$Record1.t()
>      annotatedReturnType java.lang.String
>      annotations [@example.RecordsTest$Nullable()]
>
> class example.RecordsTest$Record2
>    field private final java.lang.String example.RecordsTest$Record2.t
>      annotatedType @example.RecordsTest$Nullable() java.lang.String
>      annotations []
>    constructor public example.RecordsTest$Record2(java.lang.String)
>      parameter final java.lang.String t
>        annotatedType java.lang.String
>        annotations []
>    method public java.lang.String example.RecordsTest$Record2.t()
>      annotatedReturnType java.lang.String
>      annotations [@example.RecordsTest$Nullable()]



More information about the amber-dev mailing list