Details
Description
ADDITIONAL SYSTEM INFORMATION :
Host: macOS 12.4 on Apple M1
JDK: OpenJDK Runtime Environment Temurin-18.0.1+10 (build 18.0.1+10)
A DESCRIPTION OF THE PROBLEM :
If a `@Repeatable` annotation is applied multiple times, when an annotation processor attempts to report a message against that annotation using Messager.printMessage, the message is instead located at the element it annotates, not the element itself.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Define a repeatable annotation `@X` and its container `@Xs`, and apply either `@Xs({@X, @X})` or `@X @X` to some declaration `Z`.
Define an annotation processor that produces messages against `@X` annotations by traversing any `@Xs` in each round.
Run this annotation processor against the declaration `Z` in the first step. Vary choice of explicit/implicit container to observe the difference in ability to locate messages to repeated annotations.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Expected the following output (contrast specifically the last two lines)
Z.java:3: error: [Xs] Container
@Xs({@X, @X})
^
Z.java:3: error: [Xs] Repetition
@Xs({@X, @X})
^
Z.java:3: error: [Xs] Repetition
@Xs({@X, @X})
^
Z.java:5: error: [Ys] Container
public enum Z() {}
^
Z.java:4: error: [Ys] Repetition
@Y @Y
^
Z.java:4: error: [Ys] Repetition
@Y @Y
^
ACTUAL -
Observed the following output (contrast specifically the last two lines)
Z.java:3: error: [Xs] Container
@Xs({@X, @X})
^
Z.java:3: error: [Xs] Repetition
@Xs({@X, @X})
^
Z.java:3: error: [Xs] Repetition
@Xs({@X, @X})
^
Z.java:5: error: [Ys] Container
public enum Z() {}
^
Z.java:5: error: [Ys] Repetition
public enum Z() {}
^
Z.java:5: error: [Ys] Repetition
public enum Z() {}
^
---------- BEGIN SOURCE ----------
There are two files below, `XYProcessor.java` and `Z.java`. When compiling, only compile `XYProcessor.java`. When executing `XYProcessor.main`, ensure that `Z.java` is a resource file on the classpath adjacent to `XYProcessor.java`.
/// XYProcessor.java
package anno;
import javax.annotation.processing.Completion;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import javax.tools.ToolProvider;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.annotation.Repeatable;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Set;
@Repeatable(Xs.class)
@interface X {}
@interface Xs { X[] value(); }
@Repeatable(Ys.class)
@interface Y {}
@interface Ys { Y[] value(); }
public final class XYProcessor implements Processor {
public static void main(final String[] args) throws URISyntaxException {
final var javac = ToolProvider.getSystemJavaCompiler();
final var fileManager = javac.getStandardFileManager(null, Locale.US, StandardCharsets.UTF_8);
final var err = new StringWriter();
final var task = javac.getTask(
Writer.nullWriter(),
fileManager,
diagnostic -> err.append(diagnostic.toString()).append("\n\n"),
List.of("-proc:only"),
List.of(),
fileManager.getJavaFileObjects(Path.of(XYProcessor.class.getResource("Z.java").toURI())));
task.setProcessors(List.of(new XYProcessor()));
task.call();
System.out.println(err);
}
private ProcessingEnvironment env;
@Override
public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment round) {
final var messager = env.getMessager();
for (final var element : round.getRootElements()) {
for (final var mirror : element.getAnnotationMirrors()) {
final var annotationType = (TypeElement) mirror.getAnnotationType().asElement();
@SuppressWarnings("unchecked")
final var repetitions = (List<? extends AnnotationMirror>)
mirror.getElementValues().values().iterator().next().getValue();
messager.printMessage(Diagnostic.Kind.ERROR, "[%s] Container".formatted(annotationType.getSimpleName()), element, mirror);
for (final var repetition : repetitions) {
messager.printMessage(Diagnostic.Kind.ERROR, "[%s] Repetition".formatted(annotationType.getSimpleName()), element, repetition);
}
}
}
return false;
}
@Override
public void init(final ProcessingEnvironment env) {
this.env = env;
}
@Override
public Set<String> getSupportedAnnotationTypes() {
return Set.of("*");
}
@Override
public Set<String> getSupportedOptions() {
return Collections.emptySet();
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public Iterable<? extends Completion> getCompletions(
final Element element,
final AnnotationMirror annotation,
final ExecutableElement member,
final String userText)
{
return Collections.emptySet();
}
}
/// Z.java
package anno;
@Xs({@X, @X})
@Y @Y
public enum Z {}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Repeated annotations must be explicitly given in their container in the source to be processed. That is, users must write `@Xs({@X, @X})` instead of `@X @X`.
FREQUENCY : always
Host: macOS 12.4 on Apple M1
JDK: OpenJDK Runtime Environment Temurin-18.0.1+10 (build 18.0.1+10)
A DESCRIPTION OF THE PROBLEM :
If a `@Repeatable` annotation is applied multiple times, when an annotation processor attempts to report a message against that annotation using Messager.printMessage, the message is instead located at the element it annotates, not the element itself.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Define a repeatable annotation `@X` and its container `@Xs`, and apply either `@Xs({@X, @X})` or `@X @X` to some declaration `Z`.
Define an annotation processor that produces messages against `@X` annotations by traversing any `@Xs` in each round.
Run this annotation processor against the declaration `Z` in the first step. Vary choice of explicit/implicit container to observe the difference in ability to locate messages to repeated annotations.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Expected the following output (contrast specifically the last two lines)
Z.java:3: error: [Xs] Container
@Xs({@X, @X})
^
Z.java:3: error: [Xs] Repetition
@Xs({@X, @X})
^
Z.java:3: error: [Xs] Repetition
@Xs({@X, @X})
^
Z.java:5: error: [Ys] Container
public enum Z() {}
^
Z.java:4: error: [Ys] Repetition
@Y @Y
^
Z.java:4: error: [Ys] Repetition
@Y @Y
^
ACTUAL -
Observed the following output (contrast specifically the last two lines)
Z.java:3: error: [Xs] Container
@Xs({@X, @X})
^
Z.java:3: error: [Xs] Repetition
@Xs({@X, @X})
^
Z.java:3: error: [Xs] Repetition
@Xs({@X, @X})
^
Z.java:5: error: [Ys] Container
public enum Z() {}
^
Z.java:5: error: [Ys] Repetition
public enum Z() {}
^
Z.java:5: error: [Ys] Repetition
public enum Z() {}
^
---------- BEGIN SOURCE ----------
There are two files below, `XYProcessor.java` and `Z.java`. When compiling, only compile `XYProcessor.java`. When executing `XYProcessor.main`, ensure that `Z.java` is a resource file on the classpath adjacent to `XYProcessor.java`.
/// XYProcessor.java
package anno;
import javax.annotation.processing.Completion;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import javax.tools.ToolProvider;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.annotation.Repeatable;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Set;
@Repeatable(Xs.class)
@interface X {}
@interface Xs { X[] value(); }
@Repeatable(Ys.class)
@interface Y {}
@interface Ys { Y[] value(); }
public final class XYProcessor implements Processor {
public static void main(final String[] args) throws URISyntaxException {
final var javac = ToolProvider.getSystemJavaCompiler();
final var fileManager = javac.getStandardFileManager(null, Locale.US, StandardCharsets.UTF_8);
final var err = new StringWriter();
final var task = javac.getTask(
Writer.nullWriter(),
fileManager,
diagnostic -> err.append(diagnostic.toString()).append("\n\n"),
List.of("-proc:only"),
List.of(),
fileManager.getJavaFileObjects(Path.of(XYProcessor.class.getResource("Z.java").toURI())));
task.setProcessors(List.of(new XYProcessor()));
task.call();
System.out.println(err);
}
private ProcessingEnvironment env;
@Override
public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment round) {
final var messager = env.getMessager();
for (final var element : round.getRootElements()) {
for (final var mirror : element.getAnnotationMirrors()) {
final var annotationType = (TypeElement) mirror.getAnnotationType().asElement();
@SuppressWarnings("unchecked")
final var repetitions = (List<? extends AnnotationMirror>)
mirror.getElementValues().values().iterator().next().getValue();
messager.printMessage(Diagnostic.Kind.ERROR, "[%s] Container".formatted(annotationType.getSimpleName()), element, mirror);
for (final var repetition : repetitions) {
messager.printMessage(Diagnostic.Kind.ERROR, "[%s] Repetition".formatted(annotationType.getSimpleName()), element, repetition);
}
}
}
return false;
}
@Override
public void init(final ProcessingEnvironment env) {
this.env = env;
}
@Override
public Set<String> getSupportedAnnotationTypes() {
return Set.of("*");
}
@Override
public Set<String> getSupportedOptions() {
return Collections.emptySet();
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public Iterable<? extends Completion> getCompletions(
final Element element,
final AnnotationMirror annotation,
final ExecutableElement member,
final String userText)
{
return Collections.emptySet();
}
}
/// Z.java
package anno;
@Xs({@X, @X})
@Y @Y
public enum Z {}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Repeated annotations must be explicitly given in their container in the source to be processed. That is, users must write `@Xs({@X, @X})` instead of `@X @X`.
FREQUENCY : always