Issue
Eclipse shows this as a Java problem:
Annotation types that do not specify explicit target element types cannot be applied here.
This only happened when I upgraded from Eclipse 2022-09 to 2023-09.
This is an example when this happens:
public <T> @Nullable Set<T> getSomeSet(T something) {
// ...
}
The annotation is the javax.annotation.Nullable
from the jsr305 library.
I'm using Adoptium OpenJDK 17.0.8.101-hotspot.
- This didn't happen with Eclipse 2022-09.
- This doesn't happen with IntelliJ.
- This doesn't happen when I compile via
gradlew compileJava
. - This doesn't happen with
public @Nullable Set<?> getSomeSet() { ... }
- This doesn't happen with
public @Nullable <T> Set<T> getSomeSet(T something)
I don't understand why is that happening in this specific circumstance now (and not in all other cases) and what can I do to make that error disappear from my Eclipse IDE. I didn't find any option to disable this kind of error in the preferences.
EDIT: Simplified the minimal reproduction case and added "doesn't happen" case #4.
EDIT2: Added "doesn't happen" case #5.
Solution
It may seem as if the declarations
public <T> @Nullable Set<T> getSomeSet(T something)
public @Nullable <T> Set<T> getSomeSet(T something)
are syntactically different, i.e. that in the first variant, the annotation is bound to the return type. Apparently, Eclipse follows this idea.
The grammar of the specification, however, shows both as part of the method declaration:
MethodDeclaration: {MethodModifier} MethodHeader MethodBody MethodHeader: Result MethodDeclarator [Throws] TypeParameters {Annotation} Result MethodDeclarator [Throws] MethodDeclarator: Identifier ( [ReceiverParameter ,] [FormalParameterList] ) [Dims]
MethodModifier: (one of) Annotation public protected private abstract static final synchronized native strictfp
Note that {Annotation}
between TypeParameters
and Result
is part of the MethodHeader
.
Then the Java Language Specification, §9.7.4 says:
It is possible for an annotation to appear at a syntactic location in a program where it could plausibly apply to a declaration, or a type, or both.
[...]
The grammar of the Java programming language unambiguously treats annotations at these locations as modifiers for a declaration (§8.3), but that is purely a syntactic matter. Whether an annotation applies to the declaration or to the type of the declared entity - and thus, whether the annotation is a declaration annotation or a type annotation - depends on the applicability of the annotation's interface […]
The way I read this, is that the syntactic location shouldn’t matter, but only the applicability of annotation, given by the @Target
annotation or its absence. That’s also, how javac
handles this, unlike Eclipse:
public class AnnotationExample {
public static void main(String[] args) throws NoSuchMethodException {
Method m = AnnotationExample.class.getMethod("methodName");
System.out.println("method: " + Arrays.toString(m.getAnnotations()));
System.out.println("return type: "
+ Arrays.toString(m.getAnnotatedReturnType().getAnnotations()));
}
@Retention(RetentionPolicy.RUNTIME) @Target({TYPE_USE, METHOD}) @interface A {}
@Retention(RetentionPolicy.RUNTIME) @Target({TYPE_USE, METHOD}) @interface B {}
public static @A <T extends Enum<T>> @B Set<T> methodName() {
return null;
}
}
When compiled with javac
, it will print
method: [@AnnotationExample$A(), @AnnotationExample$B()]
return type: [@AnnotationExample$A(), @AnnotationExample$B()]
but when compiled with Eclipse, it prints
method: [@AnnotationExample.A()]
return type: [@AnnotationExample.A(), @AnnotationExample.B()]
indicating that Eclipse treats the annotation behind the type parameter declaration as an annotation of the return type only. Consequently, changing B
’s declaration to @Target(METHOD)
produces a compile error in Eclipse.
This behavior interacts with the targets assumed to be valid when no @Target
annotation has been given:
From Java 8 to Java 13, the specification looked like
If an annotation of type
java.lang.annotation.Target
is not present on the declaration of an annotation typeT
, thenT
is applicable in all declaration contexts except type parameter declarations, and in no type contexts.
Starting with Java 17, the specification looks like
If an annotation of type
java.lang.annotation.Target
is not present on the declaration of an annotation interfaceA
, thenA
is applicable in all declaration contexts and in no type contexts.
So, this looks like a straight-forward change, now also permitting type parameter declarations as target when no @Target
has been given, which does not affect our scenario.
If there weren’t the in-between versions, 14 to 16:
If an annotation of type java.lang.annotation.Target is not present on the declaration of an annotation interface A, then A is applicable in all nine declaration contexts and in all 16 type contexts.
(in Java 16, there are ten declaration contexts and 17 type contexts)
This directly affects compilers assuming that the annotation following the type parameter declaration are type contexts only. And since this is more permissive than the subsequent version, it’s understandable if a compiler vendor gets surprised by this and has to deliver the more restrictive behavior in an update.
(Note that javac
dodged this by not implementing these changes in the first place, JDK 21 is the first to implement the behavior specified since Java 17, see JDK-8309743)
It’s important to keep in mind that while the compiler error shouldn’t happen, the specification changes will still affect what is reported to be annotated when inspecting the code. When you want to be able to annotate types, rather than the method, you need an explicit @Target
including TYPE_USE
.
Answered By - Holger
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.