-
Bug
-
Resolution: Unresolved
-
P4
-
8
The rules for treatment of type annotations on method returns seem inconsistent, and aren't faithfully implemented by javac. This is definitely a problem for qualified type names, and I wonder if we want to reconsider how some other cases are specified.
Generally, a TYPE_USE annotation on a method applies to the return type of the method. This makes sense, because syntactically, it could have been intended as an annotation on the return type.
Some interesting cases outlined below.
Reasonably specified:
- `void` return: not a type, so can't be annotated. If the annotation can't be applied to the declaration, an error occurs. Fine.
- Array-typed return: spec says the annotation applies to the element type. Fine.
Spec is surprising:
- Generic method: spec note says that the annotation applies to the return type, even though the type parameters syntactically separate the annotation from the type. This is surprising, since there is no longer an ambiguity: if the user wanted to annotate the return type, they would have put the type annotation next to the return type.
- Multiple annotations: in this case, a type annotation may be followed by a non-type annotation. Again, there's no ambiguity: if the user wanted to annotate the return type, they would have put the type annotation next to the return type rather than in front of a declaration annotation. (The rule here would be somewhat complex, so I can see why it was avoided.)
Spec is surprising, and javac is inconsistent:
- Qualified type: spec note says the annotation applies to the first simple name, and if that's a package name, an error occurs. This is different than the treatment of `void` when the annotation can also be applied to the declaration: for `void`, that's fine; for package names, that's an error. It's reasonable to want to annotate the declaration of a method that returns a fully-qualified name. An error shouldn't occur just because the annotation incidentally also applies to TYPE_USE.
javac behavior is messy: for an annotation applicable to both declarations and types, it is applied to both the declaration and the *last* simple name; for an annotation applicable only to types, an error occurs.
----
In general, it seems like there are two competing models:
(A) If an annotation on a declaration could, via the syntax of types, also be interpreted as an annotation on a type, and doing so wouldn't introduce any errors, interpret it as an annotation on the type, too (or instead).
(B) Any annotation on the declaration can also be applied to a return type of the declaration, if the annotation supports it.
(A) is problematic, because it's hard to specify, even though it makes the most intuitive sense. (B) is problematic, because in the case of array types and qualified types, prefixed type annotations aren't supposed to apply to the whole type.
If (A) is what we want, we need an analysis, taking into account @Target and the sequence of annotations/modifiers, that determines which annotations can be applied to the declaration, which can be applied to the type, and which can be applied to both.
If (B) is what we want, I'd suggest starting with defining the "annotatable type" of a declaration; some declarations don't have one. In the case of arrays, this would be the element type. In the case of qualified names, this would be the first simple name, *if it is an annotatable type* (should exclude package names and also non-annotatable outer class names). Then we can have a consistent rule on top of that saying an error occurs if the annotation can be applied to neither the declaration (per @Target) nor the type (per @Target, or because there is no annotatable type).
----
Here's a test:
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;
public class TypeAnnotation {
@Target({ElementType.METHOD, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@interface Both {}
@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.RUNTIME)
@interface TypeOnly {}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface DeclOnly {}
// commented-out annotations trigger errors
@Both @TypeOnly @DeclOnly String m1() { return "hi"; }
@Both /*@TypeOnly*/ @DeclOnly void m2() { }
@Both /*@TypeOnly*/ @DeclOnly java.lang.String m3() { return "hi"; }
@Both @TypeOnly @DeclOnly <T> T m4() { return null; }
@Both @TypeOnly @DeclOnly String[] m5() { return null; }
public static void main(String... args) {
Arrays.stream(TypeAnnotation.class.getDeclaredMethods())
.sorted(Comparator.comparing(Method::getName))
.forEach(m -> {
List<Annotation> annots = Arrays.asList(m.getAnnotations());
List<Annotation> retAnnots = Arrays.asList(m.getAnnotatedReturnType().getAnnotations());
System.out.printf("Method: %s%n", m);
System.out.printf(" Annotations: %s%n", annots);
System.out.printf(" Return annotations: %s%n", retAnnots);
});
}
}
Generally, a TYPE_USE annotation on a method applies to the return type of the method. This makes sense, because syntactically, it could have been intended as an annotation on the return type.
Some interesting cases outlined below.
Reasonably specified:
- `void` return: not a type, so can't be annotated. If the annotation can't be applied to the declaration, an error occurs. Fine.
- Array-typed return: spec says the annotation applies to the element type. Fine.
Spec is surprising:
- Generic method: spec note says that the annotation applies to the return type, even though the type parameters syntactically separate the annotation from the type. This is surprising, since there is no longer an ambiguity: if the user wanted to annotate the return type, they would have put the type annotation next to the return type.
- Multiple annotations: in this case, a type annotation may be followed by a non-type annotation. Again, there's no ambiguity: if the user wanted to annotate the return type, they would have put the type annotation next to the return type rather than in front of a declaration annotation. (The rule here would be somewhat complex, so I can see why it was avoided.)
Spec is surprising, and javac is inconsistent:
- Qualified type: spec note says the annotation applies to the first simple name, and if that's a package name, an error occurs. This is different than the treatment of `void` when the annotation can also be applied to the declaration: for `void`, that's fine; for package names, that's an error. It's reasonable to want to annotate the declaration of a method that returns a fully-qualified name. An error shouldn't occur just because the annotation incidentally also applies to TYPE_USE.
javac behavior is messy: for an annotation applicable to both declarations and types, it is applied to both the declaration and the *last* simple name; for an annotation applicable only to types, an error occurs.
----
In general, it seems like there are two competing models:
(A) If an annotation on a declaration could, via the syntax of types, also be interpreted as an annotation on a type, and doing so wouldn't introduce any errors, interpret it as an annotation on the type, too (or instead).
(B) Any annotation on the declaration can also be applied to a return type of the declaration, if the annotation supports it.
(A) is problematic, because it's hard to specify, even though it makes the most intuitive sense. (B) is problematic, because in the case of array types and qualified types, prefixed type annotations aren't supposed to apply to the whole type.
If (A) is what we want, we need an analysis, taking into account @Target and the sequence of annotations/modifiers, that determines which annotations can be applied to the declaration, which can be applied to the type, and which can be applied to both.
If (B) is what we want, I'd suggest starting with defining the "annotatable type" of a declaration; some declarations don't have one. In the case of arrays, this would be the element type. In the case of qualified names, this would be the first simple name, *if it is an annotatable type* (should exclude package names and also non-annotatable outer class names). Then we can have a consistent rule on top of that saying an error occurs if the annotation can be applied to neither the declaration (per @Target) nor the type (per @Target, or because there is no annotatable type).
----
Here's a test:
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;
public class TypeAnnotation {
@Target({ElementType.METHOD, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@interface Both {}
@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.RUNTIME)
@interface TypeOnly {}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface DeclOnly {}
// commented-out annotations trigger errors
@Both @TypeOnly @DeclOnly String m1() { return "hi"; }
@Both /*@TypeOnly*/ @DeclOnly void m2() { }
@Both /*@TypeOnly*/ @DeclOnly java.lang.String m3() { return "hi"; }
@Both @TypeOnly @DeclOnly <T> T m4() { return null; }
@Both @TypeOnly @DeclOnly String[] m5() { return null; }
public static void main(String... args) {
Arrays.stream(TypeAnnotation.class.getDeclaredMethods())
.sorted(Comparator.comparing(Method::getName))
.forEach(m -> {
List<Annotation> annots = Arrays.asList(m.getAnnotations());
List<Annotation> retAnnots = Arrays.asList(m.getAnnotatedReturnType().getAnnotations());
System.out.printf("Method: %s%n", m);
System.out.printf(" Annotations: %s%n", annots);
System.out.printf(" Return annotations: %s%n", retAnnots);
});
}
}
- relates to
-
JDK-8239021 JLS contradiction for annotations with FIELD and TYPE_USE target
-
- Open
-