Records and type annotations
Brian Goetz
brian.goetz at oracle.com
Sun Dec 22 23:26:33 UTC 2019
> 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.
This seems a reasonable expectation. We don't attempt to infer the
annotations when there is an explicit parameter list, but with an
implicit parameter list, it seems reasonable that the annotations would
be pushed down. Good catch!
This should be the case for both declaration and type annotations.
> 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.
Declaration annotations on the record components, if applicable to a
type of METHOD, should be pushed down to the accessor methods. But, for
type annotations, they should be pushed down to type annotations on the
return type, as you expect.
> 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.
Both good catches. Thanks for the excellent testing and clear bug report!
>
> -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