8234101: Compilation error thrown when repeating annotation is used on record component
Reviewed-by: jlahoda
This commit is contained in:
parent
cbe604cf5d
commit
69c1729e26
@ -31,6 +31,7 @@ import com.sun.tools.javac.code.Kinds.Kind;
|
||||
import com.sun.tools.javac.util.Assert;
|
||||
import com.sun.tools.javac.util.List;
|
||||
import com.sun.tools.javac.util.ListBuffer;
|
||||
import com.sun.tools.javac.util.Pair;
|
||||
|
||||
/**
|
||||
* Container for all annotations (attributes in javac) on a Symbol.
|
||||
@ -285,6 +286,22 @@ public class SymbolMetadata {
|
||||
init_type_attributes = removeFromTypeCompoundList(init_type_attributes, (TypeCompound)compound);
|
||||
} else if (clinit_type_attributes.contains(compound)) {
|
||||
clinit_type_attributes = removeFromTypeCompoundList(clinit_type_attributes, (TypeCompound)compound);
|
||||
} else {
|
||||
// slow path, it could be that attributes list contains annotation containers, so we have to dig deeper
|
||||
for (Attribute.Compound attrCompound : attributes) {
|
||||
if (attrCompound.isSynthesized() && !attrCompound.values.isEmpty()) {
|
||||
Pair<Symbol.MethodSymbol, Attribute> val = attrCompound.values.get(0);
|
||||
if (val.fst.getSimpleName().contentEquals("value") &&
|
||||
val.snd instanceof Attribute.Array) {
|
||||
Attribute.Array arr = (Attribute.Array) val.snd;
|
||||
if (arr.values.length != 0
|
||||
&& arr.values[0] instanceof Attribute.Compound
|
||||
&& arr.values[0].type == compound.type) {
|
||||
attributes = removeFromCompoundList(attributes, attrCompound);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -835,7 +835,11 @@ public class Annotate {
|
||||
Attribute.Compound c = new Attribute.Compound(targetContainerType, List.of(p));
|
||||
JCAnnotation annoTree = m.Annotation(c);
|
||||
|
||||
if (!chk.annotationApplicable(annoTree, on)) {
|
||||
boolean isRecordMember = (on.flags_field & Flags.RECORD) != 0 || on.enclClass() != null && on.enclClass().isRecord();
|
||||
/* if it is a record member we will not issue the error now and wait until annotations on records are
|
||||
* checked at Check::validateAnnotation, which will issue it
|
||||
*/
|
||||
if (!chk.annotationApplicable(annoTree, on) && (!isRecordMember || isRecordMember && (on.flags_field & Flags.GENERATED_MEMBER) == 0)) {
|
||||
log.error(annoTree.pos(),
|
||||
Errors.InvalidRepeatableAnnotationNotApplicable(targetContainerType, on));
|
||||
}
|
||||
|
@ -2906,12 +2906,18 @@ public class Check {
|
||||
*/
|
||||
ClassSymbol recordClass = (ClassSymbol) s.owner;
|
||||
RecordComponent rc = recordClass.getRecordComponent((VarSymbol)s, false);
|
||||
rc.appendAttributes(s.getRawAttributes().stream().filter(anno ->
|
||||
Arrays.stream(getTargetNames(anno.type.tsym)).anyMatch(name -> name == names.RECORD_COMPONENT)
|
||||
).collect(List.collector()));
|
||||
rc.appendUniqueTypeAttributes(s.getRawTypeAttributes());
|
||||
// to get all the type annotations applied to the type
|
||||
rc.type = s.type;
|
||||
SymbolMetadata metadata = rc.getMetadata();
|
||||
if (metadata == null || metadata.isEmpty()) {
|
||||
/* if not is empty then we have already been here, which is the case if multiple annotations are applied
|
||||
* to the record component declaration
|
||||
*/
|
||||
rc.appendAttributes(s.getRawAttributes().stream().filter(anno ->
|
||||
Arrays.stream(getTargetNames(anno.type.tsym)).anyMatch(name -> name == names.RECORD_COMPONENT)
|
||||
).collect(List.collector()));
|
||||
rc.appendUniqueTypeAttributes(s.getRawTypeAttributes());
|
||||
// to get all the type annotations applied to the type
|
||||
rc.type = s.type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,250 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8234101
|
||||
* @summary Verify that repeating annotations and its processing works for records
|
||||
* @library /tools/lib /tools/javac/lib
|
||||
* @modules
|
||||
* jdk.compiler/com.sun.tools.javac.api
|
||||
* jdk.compiler/com.sun.tools.javac.main
|
||||
* jdk.compiler/com.sun.tools.javac.code
|
||||
* jdk.compiler/com.sun.tools.javac.util
|
||||
* @build toolbox.ToolBox toolbox.JavacTask
|
||||
* @build JavacTestingAbstractProcessor
|
||||
* @compile --enable-preview -source ${jdk.version} RepeatingAnnotationsOnRecords.java
|
||||
* @run main/othervm --enable-preview RepeatingAnnotationsOnRecords
|
||||
*/
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
|
||||
import javax.annotation.processing.*;
|
||||
import javax.lang.model.SourceVersion;
|
||||
import javax.lang.model.element.*;
|
||||
import javax.lang.model.type.TypeKind;
|
||||
import javax.lang.model.util.ElementFilter;
|
||||
import javax.lang.model.util.ElementScanner14;
|
||||
import javax.tools.Diagnostic.Kind;
|
||||
import javax.tools.*;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import com.sun.tools.javac.code.Symbol;
|
||||
import com.sun.tools.javac.code.Symbol.VarSymbol;
|
||||
|
||||
import com.sun.tools.javac.util.Assert;
|
||||
import com.sun.tools.javac.util.List;
|
||||
|
||||
import toolbox.JavacTask;
|
||||
import toolbox.Task;
|
||||
import toolbox.Task.Mode;
|
||||
import toolbox.Task.OutputKind;
|
||||
import toolbox.TestRunner;
|
||||
import toolbox.ToolBox;
|
||||
|
||||
public class RepeatingAnnotationsOnRecords extends TestRunner {
|
||||
protected ToolBox tb;
|
||||
|
||||
RepeatingAnnotationsOnRecords() {
|
||||
super(System.err);
|
||||
tb = new ToolBox();
|
||||
}
|
||||
|
||||
public static void main(String... args) throws Exception {
|
||||
new RepeatingAnnotationsOnRecords().runTests();
|
||||
}
|
||||
|
||||
protected void runTests() throws Exception {
|
||||
runTests(m -> new Object[] { Paths.get(m.getName()) });
|
||||
}
|
||||
|
||||
Path[] findJavaFiles(Path... paths) throws IOException {
|
||||
return tb.findJavaFiles(paths);
|
||||
}
|
||||
|
||||
static final String SOURCE =
|
||||
"""
|
||||
import java.lang.annotation.*;
|
||||
import java.util.*;
|
||||
import javax.annotation.processing.*;
|
||||
import javax.lang.model.element.*;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.PARAMETER })
|
||||
@interface ParameterContainer { Parameter[] value(); }
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.PARAMETER })
|
||||
@Repeatable(ParameterContainer.class)
|
||||
@interface Parameter {}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.METHOD })
|
||||
@interface MethodContainer { Method[] value(); }
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.METHOD })
|
||||
@Repeatable(MethodContainer.class)
|
||||
@interface Method {}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.FIELD })
|
||||
@interface FieldContainer { Field[] value(); }
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.FIELD })
|
||||
@Repeatable(FieldContainer.class)
|
||||
@interface Field {}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.RECORD_COMPONENT })
|
||||
@interface RecComponentContainer { RecComponent[] value(); }
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.RECORD_COMPONENT })
|
||||
@Repeatable(RecComponentContainer.class)
|
||||
@interface RecComponent {}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface AllContainer { All[] value(); }
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Repeatable(AllContainer.class)
|
||||
@interface All {}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.FIELD, ElementType.RECORD_COMPONENT })
|
||||
@interface RecComponentAndFieldContainer { RecComponentAndField[] value(); }
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.FIELD, ElementType.RECORD_COMPONENT })
|
||||
@Repeatable(RecComponentAndFieldContainer.class)
|
||||
@interface RecComponentAndField {}
|
||||
|
||||
record R1(@Parameter @Parameter int i) {}
|
||||
|
||||
record R2(@Method @Method int i) {}
|
||||
|
||||
record R3(@Field @Field int i) {}
|
||||
|
||||
record R4(@All @All int i) {}
|
||||
|
||||
record R5(@RecComponent @RecComponent int i) {}
|
||||
|
||||
record R6(@RecComponentAndField @RecComponentAndField int i) {}
|
||||
""";
|
||||
|
||||
@Test
|
||||
public void testAnnoProcessing(Path base) throws Exception {
|
||||
Path src = base.resolve("src");
|
||||
Path r = src.resolve("Records");
|
||||
|
||||
Path classes = base.resolve("classes");
|
||||
|
||||
Files.createDirectories(classes);
|
||||
|
||||
tb.writeJavaFiles(r, SOURCE);
|
||||
|
||||
for (Mode mode : new Mode[] {Mode.API}) {
|
||||
new JavacTask(tb, mode)
|
||||
.options("-nowarn",
|
||||
"-processor", Processor.class.getName(),
|
||||
"--enable-preview",
|
||||
"-source", Integer.toString(Runtime.version().feature()))
|
||||
.files(findJavaFiles(src))
|
||||
.outdir(classes)
|
||||
.run()
|
||||
.writeAll()
|
||||
.getOutputLines(Task.OutputKind.DIRECT);
|
||||
}
|
||||
}
|
||||
|
||||
@SupportedAnnotationTypes("*")
|
||||
public static final class Processor extends JavacTestingAbstractProcessor {
|
||||
public boolean process(Set<? extends TypeElement> tes, RoundEnvironment renv) {
|
||||
for (TypeElement te : tes) {
|
||||
switch (te.toString()) {
|
||||
case "ParameterContainer" :
|
||||
checkElements(te, renv, 1, Set.of(ElementKind.PARAMETER), "Parameter");
|
||||
break;
|
||||
case "MethodContainer":
|
||||
checkElements(te, renv, 1, Set.of(ElementKind.METHOD), "Method");
|
||||
break;
|
||||
case "FieldContainer":
|
||||
checkElements(te, renv, 1, Set.of(ElementKind.FIELD), "Field");
|
||||
break;
|
||||
case "AllContainer":
|
||||
checkElements(te, renv, 4,
|
||||
Set.of(ElementKind.FIELD,
|
||||
ElementKind.METHOD,
|
||||
ElementKind.PARAMETER,
|
||||
ElementKind.RECORD_COMPONENT), "All");
|
||||
break;
|
||||
case "RecComponentContainer":
|
||||
checkElements(te, renv, 1, Set.of(ElementKind.RECORD_COMPONENT), "RecComponent");
|
||||
break;
|
||||
case "RecComponentAndFieldContainer":
|
||||
checkElements(te, renv, 2, Set.of(ElementKind.RECORD_COMPONENT, ElementKind.FIELD),
|
||||
"RecComponentAndField");
|
||||
break;
|
||||
default:
|
||||
// ignore, just another annotation like Target, we don't care about
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void checkElements(TypeElement te,
|
||||
RoundEnvironment renv,
|
||||
int numberOfElementsAppliedTo,
|
||||
Set<ElementKind> kinds,
|
||||
String nameOfRepeatableAnno) {
|
||||
Set<? extends Element> annotatedElements = renv.getElementsAnnotatedWith(te);
|
||||
Assert.check(annotatedElements.size() == numberOfElementsAppliedTo, "for type element " + te + " expected = " + numberOfElementsAppliedTo + " found = " + annotatedElements.size());
|
||||
for (Element e : annotatedElements) {
|
||||
Symbol s = (Symbol) e;
|
||||
Assert.check(kinds.contains(s.getKind()));
|
||||
java.util.List<? extends AnnotationMirror> annoMirrors = e.getAnnotationMirrors();
|
||||
Assert.check(annoMirrors.size() == 1, "there must be only one annotation container");
|
||||
AnnotationMirror annotationMirror = annoMirrors.get(0);
|
||||
Map<? extends ExecutableElement, ? extends AnnotationValue> map = annotationMirror.getElementValues();
|
||||
// as we are dealing with a container that contains two annotations, its value is a list of
|
||||
// contained annotations
|
||||
List<? extends AnnotationMirror> containedAnnotations = (List<? extends AnnotationMirror>) map.values().iterator().next().getValue();
|
||||
Assert.check(containedAnnotations.size() == 2, "there can only be two repeated annotations");
|
||||
for (AnnotationMirror annoMirror : containedAnnotations) {
|
||||
Assert.check(annoMirror.getAnnotationType().toString().equals(nameOfRepeatableAnno),
|
||||
"incorrect declared type for contained annotation");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user