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