[type-annos-observers] annotated type of array-typed varargs parameter

Markus Keller markus_keller at ch.ibm.com
Fri Apr 4 14:14:26 UTC 2014


We've found a problem with the definition of the varargs syntax on array 
types. The methods "var" and "arr" below should intuitively have the same 
formal parameter type "Object @A [] @B []", but that clashes with the 
order of annotations on array dimensions:

interface Test {
        void var(Object @A [] @B ... pVar);
        void arr(Object @A [] @B [] pArr);
}
@Target(ElementType.TYPE_USE) @interface A { }
@Target(ElementType.TYPE_USE) @interface B { }

The clarification for 8.4.1 in 
https://bugs.openjdk.java.net/browse/JDK-8038881 says: "If the formal 
parameter is a variable arity parameter, then the declared type is an 
array type whose component type is UnannType."

In "var", the UnannType is "Object @A []", so the type of the parameter is 
actually "Object @B [] @A []", according to the annotation order given in 
9.7.4.

In JDK 1.8.0-b132, reflection uses the intuitive order. The snippet below 
prints:

var:   class java.lang.Object @A [] @B []
arr:   class java.lang.Object @A [] @B []

That's incorrect according to JLS8. java-annotation-design.html doesn't 
address this problem (just says "The varargs syntax “...” is treated 
analogously to array brackets").

I don't see a good solution.
a) Say JLS8 is right. => We lose the simple transformation that turns a 
vararg into a normal array dimension by changing ... into [].
b) Say the above transformation must be valid. => The annotation in front 
of the ... no longer applies to the outermost array type, which is also 
strange, because that's the array that is constructed by the compiler for 
a variable arity method invocation.



import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.stream.*;

@Target(ElementType.TYPE_USE) @Retention(RetentionPolicy.RUNTIME) 
@interface A {}
@Target(ElementType.TYPE_USE) @Retention(RetentionPolicy.RUNTIME) 
@interface B {}

public class Snippet {
    void var(Object @A [] @B ... pVar) { }
    void arr(Object @A [] @B [] pArr) { }

    public static void main(String[] args) throws Exception {
        printParamType(Snippet.class.getDeclaredMethod("var",
                Object[][].class));
        printParamType(Snippet.class.getDeclaredMethod("arr",
                Object[][].class));
    }

    private static void printParamType(Method method) {
        System.out.printf("%-5s %s\n", method.getName() + ":", 
                toString(method.getAnnotatedParameterTypes()[0]));
    }

    private static String toString(AnnotatedType annotatedType) {
        StringBuilder sb = new StringBuilder();
        while (annotatedType instanceof AnnotatedArrayType) {
            sb.append(" ")
                    .append(toString(annotatedType.getAnnotations()))
                    .append("[]");
            annotatedType = ((AnnotatedArrayType) annotatedType)
                    .getAnnotatedGenericComponentType();
        }
        Type type = annotatedType.getType();
        sb.insert(0, type.toString());
        sb.insert(0, toString(annotatedType.getAnnotations()));
        return sb.toString();
    }

    private static String toString(Annotation[] annotations) {
        return Stream.of(annotations).map(Snippet::toSimpleString).
                collect(Collectors.joining(" ", "", " "));
    }

    public static String toSimpleString(Annotation a) {
        return "@" + a.annotationType().getSimpleName();
    }
}

Markus


More information about the type-annotations-spec-observers mailing list