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.lang.annotation.Inherited;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.Callable;
|
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.*;
|
||||||
import com.sun.tools.javac.jvm.PoolConstant;
|
import com.sun.tools.javac.jvm.PoolConstant;
|
||||||
import com.sun.tools.javac.tree.JCTree;
|
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.JCFieldAccess;
|
||||||
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
|
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
|
||||||
import com.sun.tools.javac.tree.JCTree.Tag;
|
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 */
|
/** the annotation metadata attached to this class */
|
||||||
private AnnotationTypeMetadata annotationTypeMetadata;
|
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();
|
private List<RecordComponent> recordComponents = List.nil();
|
||||||
|
|
||||||
public ClassSymbol(long flags, Name name, Type type, Symbol owner) {
|
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);
|
return Flags.asModifierSet(flags & ~DEFAULT);
|
||||||
}
|
}
|
||||||
|
|
||||||
public RecordComponent getRecordComponent(VarSymbol field, boolean addIfMissing) {
|
public RecordComponent getRecordComponent(VarSymbol field) {
|
||||||
for (RecordComponent rc : recordComponents) {
|
for (RecordComponent rc : recordComponents) {
|
||||||
if (rc.name == field.name) {
|
if (rc.name == field.name) {
|
||||||
return rc;
|
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;
|
RecordComponent rc = null;
|
||||||
if (addIfMissing) {
|
if (addIfMissing) {
|
||||||
recordComponents = recordComponents.append(rc = new RecordComponent(PUBLIC, field.name, field.type, field.owner));
|
recordComponents = recordComponents.append(rc = new RecordComponent(var));
|
||||||
}
|
}
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
@ -1735,14 +1749,18 @@ public abstract class Symbol extends AnnoConstruct implements PoolConstant, Elem
|
|||||||
public static class RecordComponent extends VarSymbol implements RecordComponentElement {
|
public static class RecordComponent extends VarSymbol implements RecordComponentElement {
|
||||||
public MethodSymbol accessor;
|
public MethodSymbol accessor;
|
||||||
public JCTree.JCMethodDecl accessorMeth;
|
public JCTree.JCMethodDecl accessorMeth;
|
||||||
|
private final List<JCAnnotation> originalAnnos;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a record component, given its flags, name, type and owner.
|
* Construct a record component, given its flags, name, type and owner.
|
||||||
*/
|
*/
|
||||||
public RecordComponent(long flags, Name name, Type type, Symbol owner) {
|
public RecordComponent(JCVariableDecl fieldDecl) {
|
||||||
super(flags, name, type, owner);
|
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)
|
@Override @DefinedBy(Api.LANGUAGE_MODEL)
|
||||||
@SuppressWarnings("preview")
|
@SuppressWarnings("preview")
|
||||||
public ElementKind getKind() {
|
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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -267,25 +267,9 @@ public class SymbolMetadata {
|
|||||||
return lb.toList();
|
return lb.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Attribute.TypeCompound> removeFromTypeCompoundList(List<Attribute.TypeCompound> l, Attribute.TypeCompound compound) {
|
public void removeDeclarationMetadata(Attribute.Compound 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) {
|
|
||||||
if (attributes.contains(compound)) {
|
if (attributes.contains(compound)) {
|
||||||
attributes = removeFromCompoundList(attributes, 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 {
|
} else {
|
||||||
// slow path, it could be that attributes list contains annotation containers, so we have to dig deeper
|
// slow path, it could be that attributes list contains annotation containers, so we have to dig deeper
|
||||||
for (Attribute.Compound attrCompound : attributes) {
|
for (Attribute.Compound attrCompound : attributes) {
|
||||||
|
@ -2908,7 +2908,7 @@ public class Check {
|
|||||||
* the corresponding record component
|
* the corresponding record component
|
||||||
*/
|
*/
|
||||||
ClassSymbol recordClass = (ClassSymbol) s.owner;
|
ClassSymbol recordClass = (ClassSymbol) s.owner;
|
||||||
RecordComponent rc = recordClass.getRecordComponent((VarSymbol)s, false);
|
RecordComponent rc = recordClass.getRecordComponent((VarSymbol)s);
|
||||||
SymbolMetadata metadata = rc.getMetadata();
|
SymbolMetadata metadata = rc.getMetadata();
|
||||||
if (metadata == null || metadata.isEmpty()) {
|
if (metadata == null || metadata.isEmpty()) {
|
||||||
/* if not is empty then we have already been here, which is the case if multiple annotations are applied
|
/* if not is empty then we have already been here, which is the case if multiple annotations are applied
|
||||||
@ -2917,22 +2917,79 @@ public class Check {
|
|||||||
rc.appendAttributes(s.getRawAttributes().stream().filter(anno ->
|
rc.appendAttributes(s.getRawAttributes().stream().filter(anno ->
|
||||||
Arrays.stream(getTargetNames(anno.type.tsym)).anyMatch(name -> name == names.RECORD_COMPONENT)
|
Arrays.stream(getTargetNames(anno.type.tsym)).anyMatch(name -> name == names.RECORD_COMPONENT)
|
||||||
).collect(List.collector()));
|
).collect(List.collector()));
|
||||||
rc.appendUniqueTypeAttributes(s.getRawTypeAttributes());
|
rc.setTypeAttributes(s.getRawTypeAttributes());
|
||||||
// to get all the type annotations applied to the type
|
// to get all the type annotations applied to the type
|
||||||
rc.type = s.type;
|
rc.type = s.type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.type.tsym.isAnnotationType() && !annotationApplicable(a, s)) {
|
/* the section below is tricky. Annotations applied to record components are propagated to the corresponding
|
||||||
if (isRecordMember && (s.flags_field & Flags.GENERATED_MEMBER) != 0) {
|
* 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
|
/* 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
|
* compiler. This was intentionally done at TypeEnter, now is the moment strip away the annotations
|
||||||
* that are not applicable to the given record member
|
* that are not applicable to the given record member
|
||||||
*/
|
*/
|
||||||
JCModifiers modifiers = TreeInfo.getModifiers(declarationTree);
|
JCModifiers modifiers = TreeInfo.getModifiers(declarationTree);
|
||||||
// lets first remove the annotation from the modifier
|
/* lets first remove the annotation from the modifier if it is not applicable, we have to check again as
|
||||||
if (modifiers != null) {
|
* it could be a type annotation
|
||||||
|
*/
|
||||||
|
if (modifiers != null && applicableTargets.isEmpty()) {
|
||||||
ListBuffer<JCAnnotation> newAnnotations = new ListBuffer<>();
|
ListBuffer<JCAnnotation> newAnnotations = new ListBuffer<>();
|
||||||
for (JCAnnotation anno : modifiers.annotations) {
|
for (JCAnnotation anno : modifiers.annotations) {
|
||||||
if (anno != a) {
|
if (anno != a) {
|
||||||
@ -2942,11 +2999,13 @@ public class Check {
|
|||||||
modifiers.annotations = newAnnotations.toList();
|
modifiers.annotations = newAnnotations.toList();
|
||||||
}
|
}
|
||||||
// now lets remove it from the symbol
|
// now lets remove it from the symbol
|
||||||
s.getMetadata().remove(a.attribute);
|
s.getMetadata().removeDeclarationMetadata(a.attribute);
|
||||||
} else {
|
} else {
|
||||||
log.error(a.pos(), Errors.AnnotationTypeNotApplicable);
|
log.error(a.pos(), Errors.AnnotationTypeNotApplicable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (a.annotationType.type.tsym == syms.functionalInterfaceType.tsym) {
|
if (a.annotationType.type.tsym == syms.functionalInterfaceType.tsym) {
|
||||||
if (s.kind != TYP) {
|
if (s.kind != TYP) {
|
||||||
@ -3221,10 +3280,20 @@ public class Check {
|
|||||||
return targets;
|
return targets;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("preview")
|
|
||||||
boolean annotationApplicable(JCAnnotation a, Symbol s) {
|
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);
|
Attribute.Array arr = getAttributeTargetAttribute(a.annotationType.type.tsym);
|
||||||
Name[] targets;
|
Name[] targets;
|
||||||
|
Set<Name> applicableTargets = new HashSet<>();
|
||||||
|
|
||||||
if (arr == null) {
|
if (arr == null) {
|
||||||
targets = defaultTargetMetaInfo();
|
targets = defaultTargetMetaInfo();
|
||||||
@ -3234,7 +3303,8 @@ public class Check {
|
|||||||
for (int i=0; i<arr.values.length; ++i) {
|
for (int i=0; i<arr.values.length; ++i) {
|
||||||
Attribute app = arr.values[i];
|
Attribute app = arr.values[i];
|
||||||
if (!(app instanceof Attribute.Enum)) {
|
if (!(app instanceof Attribute.Enum)) {
|
||||||
return true; // recovery
|
// recovery
|
||||||
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
Attribute.Enum e = (Attribute.Enum) app;
|
Attribute.Enum e = (Attribute.Enum) app;
|
||||||
targets[i] = e.value.name;
|
targets[i] = e.value.name;
|
||||||
@ -3243,55 +3313,55 @@ public class Check {
|
|||||||
for (Name target : targets) {
|
for (Name target : targets) {
|
||||||
if (target == names.TYPE) {
|
if (target == names.TYPE) {
|
||||||
if (s.kind == TYP)
|
if (s.kind == TYP)
|
||||||
return true;
|
applicableTargets.add(names.TYPE);
|
||||||
} else if (target == names.FIELD) {
|
} else if (target == names.FIELD) {
|
||||||
if (s.kind == VAR && s.owner.kind != MTH)
|
if (s.kind == VAR && s.owner.kind != MTH)
|
||||||
return true;
|
applicableTargets.add(names.FIELD);
|
||||||
} else if (target == names.RECORD_COMPONENT) {
|
} else if (target == names.RECORD_COMPONENT) {
|
||||||
if (s.getKind() == ElementKind.RECORD_COMPONENT) {
|
if (s.getKind() == ElementKind.RECORD_COMPONENT) {
|
||||||
return true;
|
applicableTargets.add(names.RECORD_COMPONENT);
|
||||||
}
|
}
|
||||||
} else if (target == names.METHOD) {
|
} else if (target == names.METHOD) {
|
||||||
if (s.kind == MTH && !s.isConstructor())
|
if (s.kind == MTH && !s.isConstructor())
|
||||||
return true;
|
applicableTargets.add(names.METHOD);
|
||||||
} else if (target == names.PARAMETER) {
|
} else if (target == names.PARAMETER) {
|
||||||
if (s.kind == VAR &&
|
if (s.kind == VAR &&
|
||||||
(s.owner.kind == MTH && (s.flags() & PARAMETER) != 0)) {
|
(s.owner.kind == MTH && (s.flags() & PARAMETER) != 0)) {
|
||||||
return true;
|
applicableTargets.add(names.PARAMETER);
|
||||||
}
|
}
|
||||||
} else if (target == names.CONSTRUCTOR) {
|
} else if (target == names.CONSTRUCTOR) {
|
||||||
if (s.kind == MTH && s.isConstructor())
|
if (s.kind == MTH && s.isConstructor())
|
||||||
return true;
|
applicableTargets.add(names.CONSTRUCTOR);
|
||||||
} else if (target == names.LOCAL_VARIABLE) {
|
} else if (target == names.LOCAL_VARIABLE) {
|
||||||
if (s.kind == VAR && s.owner.kind == MTH &&
|
if (s.kind == VAR && s.owner.kind == MTH &&
|
||||||
(s.flags() & PARAMETER) == 0) {
|
(s.flags() & PARAMETER) == 0) {
|
||||||
return true;
|
applicableTargets.add(names.LOCAL_VARIABLE);
|
||||||
}
|
}
|
||||||
} else if (target == names.ANNOTATION_TYPE) {
|
} else if (target == names.ANNOTATION_TYPE) {
|
||||||
if (s.kind == TYP && (s.flags() & ANNOTATION) != 0) {
|
if (s.kind == TYP && (s.flags() & ANNOTATION) != 0) {
|
||||||
return true;
|
applicableTargets.add(names.ANNOTATION_TYPE);
|
||||||
}
|
}
|
||||||
} else if (target == names.PACKAGE) {
|
} else if (target == names.PACKAGE) {
|
||||||
if (s.kind == PCK)
|
if (s.kind == PCK)
|
||||||
return true;
|
applicableTargets.add(names.PACKAGE);
|
||||||
} else if (target == names.TYPE_USE) {
|
} else if (target == names.TYPE_USE) {
|
||||||
if (s.kind == VAR && s.owner.kind == MTH && s.type.hasTag(NONE)) {
|
if (s.kind == VAR && s.owner.kind == MTH && s.type.hasTag(NONE)) {
|
||||||
//cannot type annotate implicitly typed locals
|
//cannot type annotate implicitly typed locals
|
||||||
return false;
|
continue;
|
||||||
} else if (s.kind == TYP || s.kind == VAR ||
|
} else if (s.kind == TYP || s.kind == VAR ||
|
||||||
(s.kind == MTH && !s.isConstructor() &&
|
(s.kind == MTH && !s.isConstructor() &&
|
||||||
!s.type.getReturnType().hasTag(VOID)) ||
|
!s.type.getReturnType().hasTag(VOID)) ||
|
||||||
(s.kind == MTH && s.isConstructor())) {
|
(s.kind == MTH && s.isConstructor())) {
|
||||||
return true;
|
applicableTargets.add(names.TYPE_USE);
|
||||||
}
|
}
|
||||||
} else if (target == names.TYPE_PARAMETER) {
|
} else if (target == names.TYPE_PARAMETER) {
|
||||||
if (s.kind == TYP && s.type.hasTag(TYPEVAR))
|
if (s.kind == TYP && s.type.hasTag(TYPEVAR))
|
||||||
return true;
|
applicableTargets.add(names.TYPE_PARAMETER);
|
||||||
} else
|
} else
|
||||||
return true; // Unknown ElementType. This should be an error at declaration site,
|
return Optional.empty(); // Unknown ElementType. This should be an error at declaration site,
|
||||||
// assume applicable.
|
// assume applicable.
|
||||||
}
|
}
|
||||||
return false;
|
return Optional.of(applicableTargets);
|
||||||
}
|
}
|
||||||
|
|
||||||
Attribute.Array getAttributeTargetAttribute(TypeSymbol s) {
|
Attribute.Array getAttributeTargetAttribute(TypeSymbol s) {
|
||||||
|
@ -918,7 +918,7 @@ public class TypeEnter implements Completer {
|
|||||||
List<JCVariableDecl> fields = TreeInfo.recordFields(tree);
|
List<JCVariableDecl> fields = TreeInfo.recordFields(tree);
|
||||||
memberEnter.memberEnter(fields, env);
|
memberEnter.memberEnter(fields, env);
|
||||||
for (JCVariableDecl field : fields) {
|
for (JCVariableDecl field : fields) {
|
||||||
sym.getRecordComponent(field.sym, true);
|
sym.getRecordComponent(field, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
enterThisAndSuper(sym, env);
|
enterThisAndSuper(sym, env);
|
||||||
@ -1034,15 +1034,16 @@ public class TypeEnter implements Completer {
|
|||||||
|
|
||||||
private void addAccessor(JCVariableDecl tree, Env<AttrContext> env) {
|
private void addAccessor(JCVariableDecl tree, Env<AttrContext> env) {
|
||||||
MethodSymbol implSym = lookupMethod(env.enclClass.sym, tree.sym.name, List.nil());
|
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) {
|
if (implSym == null || (implSym.flags_field & GENERATED_MEMBER) != 0) {
|
||||||
/* here we are pushing the annotations present in the corresponding field down to the accessor
|
/* 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
|
* it could be that some of those annotations are not applicable to the accessor, they will be striped
|
||||||
* away later at Check::validateAnnotation
|
* away later at Check::validateAnnotation
|
||||||
*/
|
*/
|
||||||
|
List<JCAnnotation> originalAnnos = rec.getOriginalAnnos();
|
||||||
JCMethodDecl getter = make.at(tree.pos).
|
JCMethodDecl getter = make.at(tree.pos).
|
||||||
MethodDef(
|
MethodDef(
|
||||||
make.Modifiers(Flags.PUBLIC | Flags.GENERATED_MEMBER, tree.mods.annotations),
|
make.Modifiers(Flags.PUBLIC | Flags.GENERATED_MEMBER, originalAnnos),
|
||||||
tree.sym.name,
|
tree.sym.name,
|
||||||
/* we need to special case for the case when the user declared the type as an ident
|
/* 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
|
* 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
|
/* at this point we are passing all the annotations in the field to the corresponding
|
||||||
* parameter in the constructor.
|
* 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;
|
arg.vartype = tmpRecordFieldDecls.head.vartype;
|
||||||
tmpRecordFieldDecls = tmpRecordFieldDecls.tail;
|
tmpRecordFieldDecls = tmpRecordFieldDecls.tail;
|
||||||
}
|
}
|
||||||
|
@ -28,10 +28,12 @@
|
|||||||
*
|
*
|
||||||
* @test
|
* @test
|
||||||
* @summary Negative compilation tests, and positive compilation (smoke) tests for records
|
* @summary Negative compilation tests, and positive compilation (smoke) tests for records
|
||||||
* @library /lib/combo
|
* @library /lib/combo /tools/lib /tools/javac/lib
|
||||||
* @modules
|
* @modules
|
||||||
|
* jdk.compiler/com.sun.tools.javac.code
|
||||||
* jdk.compiler/com.sun.tools.javac.util
|
* jdk.compiler/com.sun.tools.javac.util
|
||||||
* jdk.jdeps/com.sun.tools.classfile
|
* jdk.jdeps/com.sun.tools.classfile
|
||||||
|
* @build JavacTestingAbstractProcessor
|
||||||
* @compile --enable-preview -source ${jdk.version} RecordCompilationTests.java
|
* @compile --enable-preview -source ${jdk.version} RecordCompilationTests.java
|
||||||
* @run testng/othervm --enable-preview RecordCompilationTests
|
* @run testng/othervm --enable-preview RecordCompilationTests
|
||||||
*/
|
*/
|
||||||
@ -39,17 +41,54 @@
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
|
||||||
import com.sun.tools.javac.util.Assert;
|
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.ClassFile;
|
||||||
import com.sun.tools.classfile.ConstantPool;
|
import com.sun.tools.classfile.ConstantPool;
|
||||||
import com.sun.tools.classfile.ConstantPool.CPInfo;
|
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 org.testng.annotations.Test;
|
||||||
import tools.javac.combo.CompilationTestCase;
|
import tools.javac.combo.CompilationTestCase;
|
||||||
@ -585,4 +624,332 @@ public class RecordCompilationTests extends CompilationTestCase {
|
|||||||
}
|
}
|
||||||
Assert.check(numberOfFieldRefs == 1);
|
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