Records and type annotations

Michael Hixson michael.hixson at gmail.com
Sun Dec 22 17:32:58 UTC 2019


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