ArchUnit icon indicating copy to clipboard operation
ArchUnit copied to clipboard

Annotations with target TYPE_USE are missing from JavaField instances

Open ivan-p92 opened this issue 1 year ago • 8 comments

Hi,

While working on some ArchUnit checks (I find the tool really useful so far!), I found that annotations with a target of @Target(TYPE_USE) are ignored (for example with @Nullable from Jspecify).

To reproduce, I created my own annotation:

@Target({TYPE_USE})
@Retention(RUNTIME)
public @interface Foo {}

Inspecting an ArchUnit JavaField resulting from importing a class with a field annotated with this annotation, no JavaAnnotations are present.

When adding or using FIELD as target, the JavaAnnotation is present on the JavaField instance.

@Target({TYPE_USE, FIELD})
@Retention(RUNTIME)
public @interface Foo {}

It would be great if these annotations were imported!

ivan-p92 avatar Nov 20 '24 16:11 ivan-p92

JSpecify is getting broad use in the wild and the lack of support for checking TYPE_USE annotations from Java 8 is getting in the way of adoption.

http://types.cs.washington.edu/jsr308/specification/java-annotation-design.pdf

Is this considered for an upcoming release of ArchUnit?

eskatos avatar Jan 29 '25 15:01 eskatos

Inspecting an ArchUnit JavaField resulting from importing a class with a field annotated with this annotation, no JavaAnnotations are present.

In this example, you would be annotating the field's type use, not the field itself. You cannot annotate the field itself because fields aren't declared as a valid target of the annotation. So ArchUnit's behavior is correct in this case. To see the annotation, you would have to query the field for its type, and then query the type for its annotations.

Note that type use annotations (allowed by TYPE_USE) are different from annotations present at the class definition that is used in the field's type. A type annotation would be something like List<@Foo String>. Also, since the type use is annotated, List<@Foo String> is different from List<@Bar String>, and neither class List nor class String knows of any of those annotations, since you aren't annotating the classes but the way in which their corresponding types are used. Nor is the field annotated here, because List<@Foo String> is different from @Foo List<String>.

The underlying problem, however, is that ArchUnit has JavaType which does not distinguish between type definition and type use, and seems to be unable to obtain the annotations for type uses, as @eskatos described.

janitza-mage avatar Feb 03 '25 09:02 janitza-mage

As a workaround one can use the reflect() functions from ArchUnit types to fallback to regular Java reflection in order to grab these annotations.

eskatos avatar Feb 03 '25 09:02 eskatos

@eskatos do you have an example perhaps? I tried the following, but it didn't yield any result:

class.reflect().getDeclaredField("myField").getType().getAnnotations()

ivan-p92 avatar Feb 03 '25 10:02 ivan-p92

I did not try on a field but for a method return type I used:

JavaMethod method = ... arch unit;
method.reflect().getAnnotatedReturnType().getAnnotation(Foo.class);

For fields I expect this to work:

JavaClass clazz = ... arch unit;
clazz.reflect().getDeclaredField("myField").getAnnotatedType().getAnnotation(Foo.class);

eskatos avatar Feb 03 '25 11:02 eskatos

Ah, great, thanks, getAnnotatedType() does the trick! 👌 So the following is indeed a valid workaround: clazz.reflect().getDeclaredField("myField").getAnnotatedType().getAnnotation(Nullable.class)

ivan-p92 avatar Feb 03 '25 11:02 ivan-p92