8236210: javac generates wrong annotation for fields generated from record components
Reviewed-by: mcimadamore
This commit is contained in:
parent
9e4830fc30
commit
0f98701e87
@ -29,6 +29,7 @@ import java.lang.annotation.Annotation;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
@ -58,6 +59,7 @@ import com.sun.tools.javac.comp.Env;
|
||||
import com.sun.tools.javac.jvm.*;
|
||||
import com.sun.tools.javac.jvm.PoolConstant;
|
||||
import com.sun.tools.javac.tree.JCTree;
|
||||
import com.sun.tools.javac.tree.JCTree.JCAnnotation;
|
||||
import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
|
||||
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
|
||||
import com.sun.tools.javac.tree.JCTree.Tag;
|
||||
@ -1274,6 +1276,9 @@ public abstract class Symbol extends AnnoConstruct implements PoolConstant, Elem
|
||||
/** the annotation metadata attached to this class */
|
||||
private AnnotationTypeMetadata annotationTypeMetadata;
|
||||
|
||||
/* the list of any of record components, only non empty if the class is a record
|
||||
* and it has at least one record component
|
||||
*/
|
||||
private List<RecordComponent> recordComponents = List.nil();
|
||||
|
||||
public ClassSymbol(long flags, Name name, Type type, Symbol owner) {
|
||||
@ -1471,15 +1476,24 @@ public abstract class Symbol extends AnnoConstruct implements PoolConstant, Elem
|
||||
return Flags.asModifierSet(flags & ~DEFAULT);
|
||||
}
|
||||
|
||||
public RecordComponent getRecordComponent(VarSymbol field, boolean addIfMissing) {
|
||||
public RecordComponent getRecordComponent(VarSymbol field) {
|
||||
for (RecordComponent rc : recordComponents) {
|
||||
if (rc.name == field.name) {
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public RecordComponent getRecordComponent(JCVariableDecl var, boolean addIfMissing) {
|
||||
for (RecordComponent rc : recordComponents) {
|
||||
if (rc.name == var.name) {
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
RecordComponent rc = null;
|
||||
if (addIfMissing) {
|
||||
recordComponents = recordComponents.append(rc = new RecordComponent(PUBLIC, field.name, field.type, field.owner));
|
||||
recordComponents = recordComponents.append(rc = new RecordComponent(var));
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
@ -1735,14 +1749,18 @@ public abstract class Symbol extends AnnoConstruct implements PoolConstant, Elem
|
||||
public static class RecordComponent extends VarSymbol implements RecordComponentElement {
|
||||
public MethodSymbol accessor;
|
||||
public JCTree.JCMethodDecl accessorMeth;
|
||||
private final List<JCAnnotation> originalAnnos;
|
||||
|
||||
/**
|
||||
* Construct a record component, given its flags, name, type and owner.
|
||||
*/
|
||||
public RecordComponent(long flags, Name name, Type type, Symbol owner) {
|
||||
super(flags, name, type, owner);
|
||||
public RecordComponent(JCVariableDecl fieldDecl) {
|
||||
super(PUBLIC, fieldDecl.sym.name, fieldDecl.sym.type, fieldDecl.sym.owner);
|
||||
this.originalAnnos = fieldDecl.mods.annotations;
|
||||
}
|
||||
|
||||
public List<JCAnnotation> getOriginalAnnos() { return originalAnnos; }
|
||||
|
||||
@Override @DefinedBy(Api.LANGUAGE_MODEL)
|
||||
@SuppressWarnings("preview")
|
||||
public ElementKind getKind() {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2012, 2020, 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
|
||||
@ -267,25 +267,9 @@ public class SymbolMetadata {
|
||||
return lb.toList();
|
||||
}
|
||||
|
||||
private List<Attribute.TypeCompound> removeFromTypeCompoundList(List<Attribute.TypeCompound> l, Attribute.TypeCompound compound) {
|
||||
ListBuffer<Attribute.TypeCompound> lb = new ListBuffer<>();
|
||||
for (Attribute.TypeCompound c : l) {
|
||||
if (c != compound) {
|
||||
lb.add(c);
|
||||
}
|
||||
}
|
||||
return lb.toList();
|
||||
}
|
||||
|
||||
public void remove(Attribute.Compound compound) {
|
||||
public void removeDeclarationMetadata(Attribute.Compound compound) {
|
||||
if (attributes.contains(compound)) {
|
||||
attributes = removeFromCompoundList(attributes, compound);
|
||||
} else if (type_attributes.contains(compound)) {
|
||||
type_attributes = removeFromTypeCompoundList(type_attributes, (TypeCompound)compound);
|
||||
} else if (init_type_attributes.contains(compound)) {
|
||||
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) {
|
||||
|
@ -2908,7 +2908,7 @@ public class Check {
|
||||
* the corresponding record component
|
||||
*/
|
||||
ClassSymbol recordClass = (ClassSymbol) s.owner;
|
||||
RecordComponent rc = recordClass.getRecordComponent((VarSymbol)s, false);
|
||||
RecordComponent rc = recordClass.getRecordComponent((VarSymbol)s);
|
||||
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
|
||||
@ -2917,34 +2917,93 @@ public class Check {
|
||||
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());
|
||||
rc.setTypeAttributes(s.getRawTypeAttributes());
|
||||
// to get all the type annotations applied to the type
|
||||
rc.type = s.type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (a.type.tsym.isAnnotationType() && !annotationApplicable(a, s)) {
|
||||
if (isRecordMember && (s.flags_field & Flags.GENERATED_MEMBER) != 0) {
|
||||
/* so we have found an annotation that is not applicable to a record member that was generated by the
|
||||
* compiler. This was intentionally done at TypeEnter, now is the moment strip away the annotations
|
||||
* that are not applicable to the given record member
|
||||
*/
|
||||
JCModifiers modifiers = TreeInfo.getModifiers(declarationTree);
|
||||
// lets first remove the annotation from the modifier
|
||||
if (modifiers != null) {
|
||||
ListBuffer<JCAnnotation> newAnnotations = new ListBuffer<>();
|
||||
for (JCAnnotation anno : modifiers.annotations) {
|
||||
if (anno != a) {
|
||||
newAnnotations.add(anno);
|
||||
/* the section below is tricky. Annotations applied to record components are propagated to the corresponding
|
||||
* record member so if an annotation has target: FIELD, it is propagated to the corresponding FIELD, if it has
|
||||
* target METHOD, it is propagated to the accessor and so on. But at the moment when method members are generated
|
||||
* there is no enough information to propagate only the right annotations. So all the annotations are propagated
|
||||
* to all the possible locations.
|
||||
*
|
||||
* At this point we need to remove all the annotations that are not in place before going on with the annotation
|
||||
* party. On top of the above there is the issue that there is no AST representing record components, just symbols
|
||||
* so the corresponding field has been holding all the annotations and it's metadata has been modified as if it
|
||||
* was both a field and a record component.
|
||||
*
|
||||
* So there are two places where we need to trim annotations from: the metadata of the symbol and / or the modifiers
|
||||
* in the AST. Whatever is in the metadata will be written to the class file, whatever is in the modifiers could
|
||||
* be see by annotation processors.
|
||||
*
|
||||
* The metadata contains both type annotations and declaration annotations. At this point of the game we don't
|
||||
* need to care about type annotations, they are all in the right place. But we could need to remove declaration
|
||||
* annotations. So for declaration annotations if they are not applicable to the record member, excluding type
|
||||
* annotations which are already correct, then we will remove it. For the AST modifiers if the annotation is not
|
||||
* applicable either as type annotation and or declaration annotation, only in that case it will be removed.
|
||||
*
|
||||
* So it could be that annotation is removed as a declaration annotation but it is kept in the AST modifier for
|
||||
* further inspection by annotation processors.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* import java.lang.annotation.*;
|
||||
*
|
||||
* @Target({ElementType.TYPE_USE, ElementType.RECORD_COMPONENT})
|
||||
* @Retention(RetentionPolicy.RUNTIME)
|
||||
* @interface Anno { }
|
||||
*
|
||||
* record R(@Anno String s) {}
|
||||
*
|
||||
* at this point we will have for the case of the generated field:
|
||||
* - @Anno in the modifier
|
||||
* - @Anno as a type annotation
|
||||
* - @Anno as a declaration annotation
|
||||
*
|
||||
* the last one should be removed because the annotation has not FIELD as target but it was applied as a
|
||||
* declaration annotation because the field was being treated both as a field and as a record component
|
||||
* as we have already copied the annotations to the record component, now the field doesn't need to hold
|
||||
* annotations that are not intended for it anymore. Still @Anno has to be kept in the AST's modifiers as it
|
||||
* is applicable as a type annotation to the type of the field.
|
||||
*/
|
||||
|
||||
if (a.type.tsym.isAnnotationType()) {
|
||||
Optional<Set<Name>> applicableTargetsOp = getApplicableTargets(a, s);
|
||||
if (!applicableTargetsOp.isEmpty()) {
|
||||
Set<Name> applicableTargets = applicableTargetsOp.get();
|
||||
boolean notApplicableOrIsTypeUseOnly = applicableTargets.isEmpty() ||
|
||||
applicableTargets.size() == 1 && applicableTargets.contains(names.TYPE_USE);
|
||||
boolean isRecordMemberWithNonApplicableDeclAnno =
|
||||
isRecordMember && (s.flags_field & Flags.GENERATED_MEMBER) != 0 && notApplicableOrIsTypeUseOnly;
|
||||
|
||||
if (applicableTargets.isEmpty() || isRecordMemberWithNonApplicableDeclAnno) {
|
||||
if (isRecordMemberWithNonApplicableDeclAnno) {
|
||||
/* so we have found an annotation that is not applicable to a record member that was generated by the
|
||||
* compiler. This was intentionally done at TypeEnter, now is the moment strip away the annotations
|
||||
* that are not applicable to the given record member
|
||||
*/
|
||||
JCModifiers modifiers = TreeInfo.getModifiers(declarationTree);
|
||||
/* lets first remove the annotation from the modifier if it is not applicable, we have to check again as
|
||||
* it could be a type annotation
|
||||
*/
|
||||
if (modifiers != null && applicableTargets.isEmpty()) {
|
||||
ListBuffer<JCAnnotation> newAnnotations = new ListBuffer<>();
|
||||
for (JCAnnotation anno : modifiers.annotations) {
|
||||
if (anno != a) {
|
||||
newAnnotations.add(anno);
|
||||
}
|
||||
}
|
||||
modifiers.annotations = newAnnotations.toList();
|
||||
}
|
||||
// now lets remove it from the symbol
|
||||
s.getMetadata().removeDeclarationMetadata(a.attribute);
|
||||
} else {
|
||||
log.error(a.pos(), Errors.AnnotationTypeNotApplicable);
|
||||
}
|
||||
modifiers.annotations = newAnnotations.toList();
|
||||
}
|
||||
// now lets remove it from the symbol
|
||||
s.getMetadata().remove(a.attribute);
|
||||
} else {
|
||||
log.error(a.pos(), Errors.AnnotationTypeNotApplicable);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3221,10 +3280,20 @@ public class Check {
|
||||
return targets;
|
||||
}
|
||||
|
||||
@SuppressWarnings("preview")
|
||||
boolean annotationApplicable(JCAnnotation a, Symbol s) {
|
||||
Optional<Set<Name>> targets = getApplicableTargets(a, s);
|
||||
/* the optional could be emtpy if the annotation is unknown in that case
|
||||
* we return that it is applicable and if it is erroneous that should imply
|
||||
* an error at the declaration site
|
||||
*/
|
||||
return targets.isEmpty() || targets.isPresent() && !targets.get().isEmpty();
|
||||
}
|
||||
|
||||
@SuppressWarnings("preview")
|
||||
Optional<Set<Name>> getApplicableTargets(JCAnnotation a, Symbol s) {
|
||||
Attribute.Array arr = getAttributeTargetAttribute(a.annotationType.type.tsym);
|
||||
Name[] targets;
|
||||
Set<Name> applicableTargets = new HashSet<>();
|
||||
|
||||
if (arr == null) {
|
||||
targets = defaultTargetMetaInfo();
|
||||
@ -3234,7 +3303,8 @@ public class Check {
|
||||
for (int i=0; i<arr.values.length; ++i) {
|
||||
Attribute app = arr.values[i];
|
||||
if (!(app instanceof Attribute.Enum)) {
|
||||
return true; // recovery
|
||||
// recovery
|
||||
return Optional.empty();
|
||||
}
|
||||
Attribute.Enum e = (Attribute.Enum) app;
|
||||
targets[i] = e.value.name;
|
||||
@ -3243,55 +3313,55 @@ public class Check {
|
||||
for (Name target : targets) {
|
||||
if (target == names.TYPE) {
|
||||
if (s.kind == TYP)
|
||||
return true;
|
||||
applicableTargets.add(names.TYPE);
|
||||
} else if (target == names.FIELD) {
|
||||
if (s.kind == VAR && s.owner.kind != MTH)
|
||||
return true;
|
||||
applicableTargets.add(names.FIELD);
|
||||
} else if (target == names.RECORD_COMPONENT) {
|
||||
if (s.getKind() == ElementKind.RECORD_COMPONENT) {
|
||||
return true;
|
||||
applicableTargets.add(names.RECORD_COMPONENT);
|
||||
}
|
||||
} else if (target == names.METHOD) {
|
||||
if (s.kind == MTH && !s.isConstructor())
|
||||
return true;
|
||||
applicableTargets.add(names.METHOD);
|
||||
} else if (target == names.PARAMETER) {
|
||||
if (s.kind == VAR &&
|
||||
(s.owner.kind == MTH && (s.flags() & PARAMETER) != 0)) {
|
||||
return true;
|
||||
applicableTargets.add(names.PARAMETER);
|
||||
}
|
||||
} else if (target == names.CONSTRUCTOR) {
|
||||
if (s.kind == MTH && s.isConstructor())
|
||||
return true;
|
||||
applicableTargets.add(names.CONSTRUCTOR);
|
||||
} else if (target == names.LOCAL_VARIABLE) {
|
||||
if (s.kind == VAR && s.owner.kind == MTH &&
|
||||
(s.flags() & PARAMETER) == 0) {
|
||||
return true;
|
||||
applicableTargets.add(names.LOCAL_VARIABLE);
|
||||
}
|
||||
} else if (target == names.ANNOTATION_TYPE) {
|
||||
if (s.kind == TYP && (s.flags() & ANNOTATION) != 0) {
|
||||
return true;
|
||||
applicableTargets.add(names.ANNOTATION_TYPE);
|
||||
}
|
||||
} else if (target == names.PACKAGE) {
|
||||
if (s.kind == PCK)
|
||||
return true;
|
||||
applicableTargets.add(names.PACKAGE);
|
||||
} else if (target == names.TYPE_USE) {
|
||||
if (s.kind == VAR && s.owner.kind == MTH && s.type.hasTag(NONE)) {
|
||||
//cannot type annotate implicitly typed locals
|
||||
return false;
|
||||
continue;
|
||||
} else if (s.kind == TYP || s.kind == VAR ||
|
||||
(s.kind == MTH && !s.isConstructor() &&
|
||||
!s.type.getReturnType().hasTag(VOID)) ||
|
||||
(s.kind == MTH && s.isConstructor())) {
|
||||
return true;
|
||||
applicableTargets.add(names.TYPE_USE);
|
||||
}
|
||||
} else if (target == names.TYPE_PARAMETER) {
|
||||
if (s.kind == TYP && s.type.hasTag(TYPEVAR))
|
||||
return true;
|
||||
applicableTargets.add(names.TYPE_PARAMETER);
|
||||
} else
|
||||
return true; // Unknown ElementType. This should be an error at declaration site,
|
||||
// assume applicable.
|
||||
return Optional.empty(); // Unknown ElementType. This should be an error at declaration site,
|
||||
// assume applicable.
|
||||
}
|
||||
return false;
|
||||
return Optional.of(applicableTargets);
|
||||
}
|
||||
|
||||
Attribute.Array getAttributeTargetAttribute(TypeSymbol s) {
|
||||
|
@ -918,7 +918,7 @@ public class TypeEnter implements Completer {
|
||||
List<JCVariableDecl> fields = TreeInfo.recordFields(tree);
|
||||
memberEnter.memberEnter(fields, env);
|
||||
for (JCVariableDecl field : fields) {
|
||||
sym.getRecordComponent(field.sym, true);
|
||||
sym.getRecordComponent(field, true);
|
||||
}
|
||||
|
||||
enterThisAndSuper(sym, env);
|
||||
@ -1034,15 +1034,16 @@ public class TypeEnter implements Completer {
|
||||
|
||||
private void addAccessor(JCVariableDecl tree, Env<AttrContext> env) {
|
||||
MethodSymbol implSym = lookupMethod(env.enclClass.sym, tree.sym.name, List.nil());
|
||||
RecordComponent rec = ((ClassSymbol) tree.sym.owner).getRecordComponent(tree.sym, false);
|
||||
RecordComponent rec = ((ClassSymbol) tree.sym.owner).getRecordComponent(tree.sym);
|
||||
if (implSym == null || (implSym.flags_field & GENERATED_MEMBER) != 0) {
|
||||
/* here we are pushing the annotations present in the corresponding field down to the accessor
|
||||
* it could be that some of those annotations are not applicable to the accessor, they will be striped
|
||||
* away later at Check::validateAnnotation
|
||||
*/
|
||||
List<JCAnnotation> originalAnnos = rec.getOriginalAnnos();
|
||||
JCMethodDecl getter = make.at(tree.pos).
|
||||
MethodDef(
|
||||
make.Modifiers(Flags.PUBLIC | Flags.GENERATED_MEMBER, tree.mods.annotations),
|
||||
make.Modifiers(Flags.PUBLIC | Flags.GENERATED_MEMBER, originalAnnos),
|
||||
tree.sym.name,
|
||||
/* we need to special case for the case when the user declared the type as an ident
|
||||
* if we don't do that then we can have issues if type annotations are applied to the
|
||||
@ -1349,7 +1350,8 @@ public class TypeEnter implements Completer {
|
||||
/* at this point we are passing all the annotations in the field to the corresponding
|
||||
* parameter in the constructor.
|
||||
*/
|
||||
arg.mods.annotations = tmpRecordFieldDecls.head.mods.annotations;
|
||||
RecordComponent rc = ((ClassSymbol) owner).getRecordComponent(arg.sym);
|
||||
arg.mods.annotations = rc.getOriginalAnnos();
|
||||
arg.vartype = tmpRecordFieldDecls.head.vartype;
|
||||
tmpRecordFieldDecls = tmpRecordFieldDecls.tail;
|
||||
}
|
||||
|
@ -28,10 +28,12 @@
|
||||
*
|
||||
* @test
|
||||
* @summary Negative compilation tests, and positive compilation (smoke) tests for records
|
||||
* @library /lib/combo
|
||||
* @library /lib/combo /tools/lib /tools/javac/lib
|
||||
* @modules
|
||||
* jdk.compiler/com.sun.tools.javac.code
|
||||
* jdk.compiler/com.sun.tools.javac.util
|
||||
* jdk.jdeps/com.sun.tools.classfile
|
||||
* @build JavacTestingAbstractProcessor
|
||||
* @compile --enable-preview -source ${jdk.version} RecordCompilationTests.java
|
||||
* @run testng/othervm --enable-preview RecordCompilationTests
|
||||
*/
|
||||
@ -39,17 +41,54 @@
|
||||
import java.io.File;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumMap;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
|
||||
import com.sun.tools.javac.util.Assert;
|
||||
|
||||
import javax.annotation.processing.RoundEnvironment;
|
||||
import javax.annotation.processing.SupportedAnnotationTypes;
|
||||
|
||||
import javax.lang.model.element.AnnotationMirror;
|
||||
import javax.lang.model.element.AnnotationValue;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ElementKind;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.RecordComponentElement;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.element.VariableElement;
|
||||
|
||||
import javax.lang.model.type.ArrayType;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
|
||||
import com.sun.tools.classfile.Annotation;
|
||||
import com.sun.tools.classfile.Attribute;
|
||||
import com.sun.tools.classfile.Attributes;
|
||||
import com.sun.tools.classfile.ClassFile;
|
||||
import com.sun.tools.classfile.ConstantPool;
|
||||
import com.sun.tools.classfile.ConstantPool.CPInfo;
|
||||
import com.sun.tools.classfile.Field;
|
||||
import com.sun.tools.classfile.Method;
|
||||
import com.sun.tools.classfile.Record_attribute;
|
||||
import com.sun.tools.classfile.Record_attribute.ComponentInfo;
|
||||
import com.sun.tools.classfile.RuntimeAnnotations_attribute;
|
||||
import com.sun.tools.classfile.RuntimeTypeAnnotations_attribute;
|
||||
import com.sun.tools.classfile.RuntimeVisibleAnnotations_attribute;
|
||||
import com.sun.tools.classfile.RuntimeVisibleParameterAnnotations_attribute;
|
||||
import com.sun.tools.classfile.RuntimeVisibleTypeAnnotations_attribute;
|
||||
import com.sun.tools.classfile.TypeAnnotation;
|
||||
|
||||
import com.sun.tools.javac.code.Attribute.TypeCompound;
|
||||
import com.sun.tools.javac.code.Symbol;
|
||||
import com.sun.tools.javac.code.Symbol.VarSymbol;
|
||||
|
||||
import org.testng.annotations.Test;
|
||||
import tools.javac.combo.CompilationTestCase;
|
||||
@ -585,4 +624,332 @@ public class RecordCompilationTests extends CompilationTestCase {
|
||||
}
|
||||
Assert.check(numberOfFieldRefs == 1);
|
||||
}
|
||||
|
||||
public void testAnnos() throws Exception {
|
||||
String srcTemplate =
|
||||
"""
|
||||
import java.lang.annotation.*;
|
||||
@Target({#TARGET})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface Anno { }
|
||||
|
||||
record R(@Anno String s) {}
|
||||
""";
|
||||
|
||||
// testing several combinations, adding even more combinations won't add too much value
|
||||
List<String> annoApplicableTargets = List.of(
|
||||
"ElementType.FIELD",
|
||||
"ElementType.METHOD",
|
||||
"ElementType.PARAMETER",
|
||||
"ElementType.RECORD_COMPONENT",
|
||||
"ElementType.TYPE_USE",
|
||||
"ElementType.TYPE_USE,ElementType.FIELD",
|
||||
"ElementType.TYPE_USE,ElementType.METHOD",
|
||||
"ElementType.TYPE_USE,ElementType.PARAMETER",
|
||||
"ElementType.TYPE_USE,ElementType.RECORD_COMPONENT",
|
||||
"ElementType.TYPE_USE,ElementType.FIELD,ElementType.METHOD",
|
||||
"ElementType.TYPE_USE,ElementType.FIELD,ElementType.PARAMETER",
|
||||
"ElementType.TYPE_USE,ElementType.FIELD,ElementType.RECORD_COMPONENT",
|
||||
"ElementType.FIELD,ElementType.TYPE_USE",
|
||||
"ElementType.METHOD,ElementType.TYPE_USE",
|
||||
"ElementType.PARAMETER,ElementType.TYPE_USE",
|
||||
"ElementType.RECORD_COMPONENT,ElementType.TYPE_USE",
|
||||
"ElementType.FIELD,ElementType.METHOD,ElementType.TYPE_USE",
|
||||
"ElementType.FIELD,ElementType.PARAMETER,ElementType.TYPE_USE",
|
||||
"ElementType.FIELD,ElementType.RECORD_COMPONENT,ElementType.TYPE_USE"
|
||||
);
|
||||
|
||||
String[] generalOptions = {
|
||||
"--enable-preview",
|
||||
"-source", Integer.toString(Runtime.version().feature()),
|
||||
"-processor", Processor.class.getName(),
|
||||
"-Atargets="
|
||||
};
|
||||
|
||||
for (String target : annoApplicableTargets) {
|
||||
String code = srcTemplate.replaceFirst("#TARGET", target);
|
||||
String[] testOptions = generalOptions.clone();
|
||||
testOptions[testOptions.length - 1] = testOptions[testOptions.length - 1] + target;
|
||||
setCompileOptions(testOptions);
|
||||
|
||||
File dir = assertOK(true, code);
|
||||
|
||||
ClassFile classFile = ClassFile.read(findClassFileOrFail(dir, "R.class"));
|
||||
|
||||
// field first
|
||||
Assert.check(classFile.fields.length == 1);
|
||||
Field field = classFile.fields[0];
|
||||
/* if FIELD is one of the targets then there must be a declaration annotation applied to the field, apart from
|
||||
* the type annotation
|
||||
*/
|
||||
if (target.contains("FIELD")) {
|
||||
checkAnno(classFile,
|
||||
(RuntimeAnnotations_attribute)findAttributeOrFail(
|
||||
field.attributes,
|
||||
RuntimeVisibleAnnotations_attribute.class),
|
||||
"Anno");
|
||||
} else {
|
||||
assertAttributeNotPresent(field.attributes, RuntimeVisibleAnnotations_attribute.class);
|
||||
}
|
||||
|
||||
// lets check now for the type annotation
|
||||
if (target.contains("TYPE_USE")) {
|
||||
checkTypeAnno(
|
||||
classFile,
|
||||
(RuntimeVisibleTypeAnnotations_attribute)findAttributeOrFail(field.attributes, RuntimeVisibleTypeAnnotations_attribute.class),
|
||||
"FIELD",
|
||||
"Anno");
|
||||
} else {
|
||||
assertAttributeNotPresent(field.attributes, RuntimeVisibleTypeAnnotations_attribute.class);
|
||||
}
|
||||
|
||||
// checking for the annotation on the corresponding parameter of the canonical constructor
|
||||
Method init = findMethodOrFail(classFile, "<init>");
|
||||
/* if PARAMETER is one of the targets then there must be a declaration annotation applied to the parameter, apart from
|
||||
* the type annotation
|
||||
*/
|
||||
if (target.contains("PARAMETER")) {
|
||||
checkParameterAnno(classFile,
|
||||
(RuntimeVisibleParameterAnnotations_attribute)findAttributeOrFail(
|
||||
init.attributes,
|
||||
RuntimeVisibleParameterAnnotations_attribute.class),
|
||||
"Anno");
|
||||
} else {
|
||||
assertAttributeNotPresent(init.attributes, RuntimeVisibleAnnotations_attribute.class);
|
||||
}
|
||||
// let's check now for the type annotation
|
||||
if (target.contains("TYPE_USE")) {
|
||||
checkTypeAnno(
|
||||
classFile,
|
||||
(RuntimeVisibleTypeAnnotations_attribute) findAttributeOrFail(init.attributes, RuntimeVisibleTypeAnnotations_attribute.class),
|
||||
"METHOD_FORMAL_PARAMETER", "Anno");
|
||||
} else {
|
||||
assertAttributeNotPresent(init.attributes, RuntimeVisibleTypeAnnotations_attribute.class);
|
||||
}
|
||||
|
||||
// checking for the annotation in the accessor
|
||||
Method accessor = findMethodOrFail(classFile, "s");
|
||||
/* if METHOD is one of the targets then there must be a declaration annotation applied to the accessor, apart from
|
||||
* the type annotation
|
||||
*/
|
||||
if (target.contains("METHOD")) {
|
||||
checkAnno(classFile,
|
||||
(RuntimeAnnotations_attribute)findAttributeOrFail(
|
||||
accessor.attributes,
|
||||
RuntimeVisibleAnnotations_attribute.class),
|
||||
"Anno");
|
||||
} else {
|
||||
assertAttributeNotPresent(accessor.attributes, RuntimeVisibleAnnotations_attribute.class);
|
||||
}
|
||||
// let's check now for the type annotation
|
||||
if (target.contains("TYPE_USE")) {
|
||||
checkTypeAnno(
|
||||
classFile,
|
||||
(RuntimeVisibleTypeAnnotations_attribute)findAttributeOrFail(accessor.attributes, RuntimeVisibleTypeAnnotations_attribute.class),
|
||||
"METHOD_RETURN", "Anno");
|
||||
} else {
|
||||
assertAttributeNotPresent(accessor.attributes, RuntimeVisibleTypeAnnotations_attribute.class);
|
||||
}
|
||||
|
||||
// checking for the annotation in the Record attribute
|
||||
Record_attribute record = (Record_attribute)findAttributeOrFail(classFile.attributes, Record_attribute.class);
|
||||
Assert.check(record.component_count == 1);
|
||||
/* if RECORD_COMPONENT is one of the targets then there must be a declaration annotation applied to the
|
||||
* field, apart from the type annotation
|
||||
*/
|
||||
if (target.contains("RECORD_COMPONENT")) {
|
||||
checkAnno(classFile,
|
||||
(RuntimeAnnotations_attribute)findAttributeOrFail(
|
||||
record.component_info_arr[0].attributes,
|
||||
RuntimeVisibleAnnotations_attribute.class),
|
||||
"Anno");
|
||||
} else {
|
||||
assertAttributeNotPresent(record.component_info_arr[0].attributes, RuntimeVisibleAnnotations_attribute.class);
|
||||
}
|
||||
// lets check now for the type annotation
|
||||
if (target.contains("TYPE_USE")) {
|
||||
checkTypeAnno(
|
||||
classFile,
|
||||
(RuntimeVisibleTypeAnnotations_attribute)findAttributeOrFail(
|
||||
record.component_info_arr[0].attributes,
|
||||
RuntimeVisibleTypeAnnotations_attribute.class),
|
||||
"FIELD", "Anno");
|
||||
} else {
|
||||
assertAttributeNotPresent(record.component_info_arr[0].attributes, RuntimeVisibleTypeAnnotations_attribute.class);
|
||||
}
|
||||
}
|
||||
|
||||
// let's reset the default compiler options for other tests
|
||||
setCompileOptions(PREVIEW_OPTIONS);
|
||||
}
|
||||
|
||||
private void checkTypeAnno(ClassFile classFile,
|
||||
RuntimeTypeAnnotations_attribute rtAnnos,
|
||||
String positionType,
|
||||
String annoName) throws Exception {
|
||||
// containing only one type annotation
|
||||
Assert.check(rtAnnos.annotations.length == 1);
|
||||
TypeAnnotation tAnno = (TypeAnnotation)rtAnnos.annotations[0];
|
||||
Assert.check(tAnno.position.type.toString().equals(positionType));
|
||||
String annotationName = classFile.constant_pool.getUTF8Value(tAnno.annotation.type_index).toString().substring(1);
|
||||
Assert.check(annotationName.startsWith(annoName));
|
||||
}
|
||||
|
||||
private void checkAnno(ClassFile classFile,
|
||||
RuntimeAnnotations_attribute rAnnos,
|
||||
String annoName) throws Exception {
|
||||
// containing only one type annotation
|
||||
Assert.check(rAnnos.annotations.length == 1);
|
||||
Annotation anno = (Annotation)rAnnos.annotations[0];
|
||||
String annotationName = classFile.constant_pool.getUTF8Value(anno.type_index).toString().substring(1);
|
||||
Assert.check(annotationName.startsWith(annoName));
|
||||
}
|
||||
|
||||
// special case for parameter annotations
|
||||
private void checkParameterAnno(ClassFile classFile,
|
||||
RuntimeVisibleParameterAnnotations_attribute rAnnos,
|
||||
String annoName) throws Exception {
|
||||
// containing only one type annotation
|
||||
Assert.check(rAnnos.parameter_annotations.length == 1);
|
||||
Assert.check(rAnnos.parameter_annotations[0].length == 1);
|
||||
Annotation anno = (Annotation)rAnnos.parameter_annotations[0][0];
|
||||
String annotationName = classFile.constant_pool.getUTF8Value(anno.type_index).toString().substring(1);
|
||||
Assert.check(annotationName.startsWith(annoName));
|
||||
}
|
||||
|
||||
private File findClassFileOrFail(File dir, String name) {
|
||||
for (final File fileEntry : dir.listFiles()) {
|
||||
if (fileEntry.getName().equals("R.class")) {
|
||||
return fileEntry;
|
||||
}
|
||||
}
|
||||
throw new AssertionError("file not found");
|
||||
}
|
||||
|
||||
private Method findMethodOrFail(ClassFile classFile, String name) throws Exception {
|
||||
for (Method method : classFile.methods) {
|
||||
if (method.getName(classFile.constant_pool).equals(name)) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
throw new AssertionError("method not found");
|
||||
}
|
||||
|
||||
private Attribute findAttributeOrFail(Attributes attributes, Class<? extends Attribute> attrClass) {
|
||||
for (Attribute attribute : attributes) {
|
||||
if (attribute.getClass() == attrClass) {
|
||||
return attribute;
|
||||
}
|
||||
}
|
||||
throw new AssertionError("attribute not found");
|
||||
}
|
||||
|
||||
private void assertAttributeNotPresent(Attributes attributes, Class<? extends Attribute> attrClass) {
|
||||
for (Attribute attribute : attributes) {
|
||||
if (attribute.getClass() == attrClass) {
|
||||
throw new AssertionError("attribute not expected");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SupportedAnnotationTypes("*")
|
||||
public static final class Processor extends JavacTestingAbstractProcessor {
|
||||
|
||||
String targets;
|
||||
int numberOfTypeAnnotations;
|
||||
|
||||
@Override
|
||||
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
|
||||
targets = processingEnv.getOptions().get("targets");
|
||||
System.out.println("targets------------------------------------------------- " + targets);
|
||||
for (TypeElement te : annotations) {
|
||||
if (te.toString().equals("Anno")) {
|
||||
checkElements(te, roundEnv, targets);
|
||||
if (targets.contains("TYPE_USE")) {
|
||||
Element element = processingEnv.getElementUtils().getTypeElement("R");
|
||||
numberOfTypeAnnotations = 0;
|
||||
System.out.println("element found --------------------------------- " + element);
|
||||
checkTypeAnnotations(element);
|
||||
Assert.check(numberOfTypeAnnotations == 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void checkElements(TypeElement te, RoundEnvironment renv, String targets) {
|
||||
Set<? extends Element> annoElements = renv.getElementsAnnotatedWith(te);
|
||||
Set<String> targetSet = new HashSet<>(Arrays.asList(targets.split(",")));
|
||||
// we will check for type annotation in another method
|
||||
targetSet.remove("ElementType.TYPE_USE");
|
||||
for (Element e : annoElements) {
|
||||
Symbol s = (Symbol) e;
|
||||
switch (s.getKind()) {
|
||||
case FIELD:
|
||||
Assert.check(targetSet.contains("ElementType.FIELD"));
|
||||
targetSet.remove("ElementType.FIELD");
|
||||
break;
|
||||
case METHOD:
|
||||
Assert.check(targetSet.contains("ElementType.METHOD"));
|
||||
targetSet.remove("ElementType.METHOD");
|
||||
break;
|
||||
case PARAMETER:
|
||||
Assert.check(targetSet.contains("ElementType.PARAMETER"));
|
||||
targetSet.remove("ElementType.PARAMETER");
|
||||
break;
|
||||
case RECORD_COMPONENT:
|
||||
Assert.check(targetSet.contains("ElementType.RECORD_COMPONENT"));
|
||||
targetSet.remove("ElementType.RECORD_COMPONENT");
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("unexpected element kind");
|
||||
}
|
||||
}
|
||||
Assert.check(targetSet.isEmpty(), targetSet.toString());
|
||||
}
|
||||
|
||||
private void checkTypeAnnotations(Element rootElement) {
|
||||
new ElementScanner<Void, Void>() {
|
||||
@Override public Void visitVariable(VariableElement e, Void p) {
|
||||
Symbol s = (Symbol) e;
|
||||
if (s.getKind() == ElementKind.FIELD ||
|
||||
s.getKind() == ElementKind.PARAMETER &&
|
||||
s.name.toString().equals("s")) {
|
||||
int currentTAs = numberOfTypeAnnotations;
|
||||
verifyTypeAnnotations(e.asType().getAnnotationMirrors());
|
||||
Assert.check(currentTAs + 1 == numberOfTypeAnnotations);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@Override
|
||||
public Void visitExecutable(ExecutableElement e, Void p) {
|
||||
Symbol s = (Symbol) e;
|
||||
if (s.getKind() == ElementKind.METHOD &&
|
||||
s.name.toString().equals("s")) {
|
||||
int currentTAs = numberOfTypeAnnotations;
|
||||
verifyTypeAnnotations(e.getReturnType().getAnnotationMirrors());
|
||||
Assert.check(currentTAs + 1 == numberOfTypeAnnotations);
|
||||
}
|
||||
scan(e.getParameters(), p);
|
||||
return null;
|
||||
}
|
||||
@Override public Void visitRecordComponent(RecordComponentElement e, Void p) {
|
||||
int currentTAs = numberOfTypeAnnotations;
|
||||
verifyTypeAnnotations(e.asType().getAnnotationMirrors());
|
||||
Assert.check(currentTAs + 1 == numberOfTypeAnnotations);
|
||||
return null;
|
||||
}
|
||||
}.scan(rootElement, null);
|
||||
}
|
||||
|
||||
private void verifyTypeAnnotations(Iterable<? extends AnnotationMirror> annotations) {
|
||||
for (AnnotationMirror mirror : annotations) {
|
||||
Assert.check(mirror.toString().startsWith("@Anno"));
|
||||
if (mirror instanceof TypeCompound) {
|
||||
numberOfTypeAnnotations++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user