org.checkerframework.common.basetype.BaseTypeValidator Maven / Gradle / Ivy
Show all versions of checker Show documentation
package org.checkerframework.common.basetype;
import com.sun.source.tree.AnnotatedTypeTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.ParameterizedTypeTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.TypeParameterTree;
import com.sun.source.tree.VariableTree;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.tools.Diagnostic.Kind;
import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.framework.source.DiagMessage;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedIntersectionType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
import org.checkerframework.framework.type.AnnotatedTypeParameterBounds;
import org.checkerframework.framework.type.QualifierHierarchy;
import org.checkerframework.framework.type.visitor.AnnotatedTypeScanner;
import org.checkerframework.framework.type.visitor.SimpleAnnotatedTypeScanner;
import org.checkerframework.framework.util.AnnotatedTypes;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.Pair;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypeAnnotationUtils;
import org.checkerframework.javacutil.TypesUtils;
/**
* A visitor to validate the types in a tree.
*
* Note: A TypeValidator (this class and its subclasses) cannot tell whether an annotation was
* written by a programmer or defaulted/inferred/computed by the Checker Framework, because the
* AnnotatedTypeMirror does not make distinctions about which annotations in an AnnotatedTypeMirror
* were explicitly written and which were added by a checker. To issue a warning/error only when a
* programmer writes an annotation, override {@link BaseTypeVisitor#visitAnnotatedType} and {@link
* BaseTypeVisitor#visitVariable}.
*/
public class BaseTypeValidator extends AnnotatedTypeScanner implements TypeValidator {
/** Is the type valid? This is side-effected by the visitor, and read at the end of visiting. */
protected boolean isValid = true;
/** Should the primary annotation on the top level type be checked? */
protected boolean checkTopLevelDeclaredOrPrimitiveType = true;
/** BaseTypeChecker. */
protected final BaseTypeChecker checker;
/** BaseTypeVisitor. */
protected final BaseTypeVisitor visitor;
/** AnnotatedTypeFactory. */
protected final AnnotatedTypeFactory atypeFactory;
// TODO: clean up coupling between components
public BaseTypeValidator(
BaseTypeChecker checker, BaseTypeVisitor visitor, AnnotatedTypeFactory atypeFactory) {
this.checker = checker;
this.visitor = visitor;
this.atypeFactory = atypeFactory;
}
/**
* Validate the type against the given tree. This method both issues error messages and also
* returns a boolean value.
*
* This is the entry point to the type validator. Neither this method nor visit should be
* called directly by a visitor, only use {@link BaseTypeVisitor#validateTypeOf(Tree)}.
*
*
This method is only called on top-level types, but it validates the entire type including
* components of a compound type. Subclasses should override this only if there is special-case
* behavior that should be performed only on top-level types.
*
* @param type the type to validate
* @param tree the tree from which the type originated. If the tree is a method tree, {@code type}
* is its return type. If the tree is a variable tree, {@code type} is the variable's type.
* @return true if the type is valid
*/
@Override
public boolean isValid(AnnotatedTypeMirror type, Tree tree) {
List diagMessages =
isValidStructurally(atypeFactory.getQualifierHierarchy(), type);
if (!diagMessages.isEmpty()) {
for (DiagMessage d : diagMessages) {
checker.report(tree, d);
}
return false;
}
this.isValid = true;
this.checkTopLevelDeclaredOrPrimitiveType =
shouldCheckTopLevelDeclaredOrPrimitiveType(type, tree);
visit(type, tree);
return this.isValid;
}
/**
* Should the top-level declared or primitive type be checked?
*
* If {@code type} is not a declared or primitive type, then this method returns true.
*
*
Top-level type is not checked if tree is a local variable or an expression tree.
*
* @param type AnnotatedTypeMirror being validated
* @param tree a Tree whose type is {@code type}
* @return whether or not the top-level type should be checked, if {@code type} is a declared or
* primitive type.
*/
protected boolean shouldCheckTopLevelDeclaredOrPrimitiveType(
AnnotatedTypeMirror type, Tree tree) {
if (type.getKind() != TypeKind.DECLARED && !type.getKind().isPrimitive()) {
return true;
}
return !TreeUtils.isLocalVariable(tree)
&& (!TreeUtils.isExpressionTree(tree) || TreeUtils.isTypeTree(tree));
}
/**
* Performs some well-formedness checks on the given {@link AnnotatedTypeMirror}. Returns a list
* of failures. If successful, returns an empty list. The method will never return failures for a
* valid type, but might not catch all invalid types.
*
*
This method ensures that the type is structurally or lexically well-formed, but it does not
* check whether the annotations are semantically sensible. Subclasses should generally override
* visit methods such as {@link #visitDeclared} rather than this method.
*
*
Currently, this implementation checks the following (subclasses can extend this behavior):
*
*
* - There should not be multiple annotations from the same qualifier hierarchy.
*
- There should not be more annotations than the width of the QualifierHierarchy.
*
- If the type is not a type variable, then the number of annotations should be the same as
* the width of the QualifierHierarchy.
*
- These properties should also hold recursively for component types of arrays and for
* bounds of type variables and wildcards.
*
*
* @param qualifierHierarchy the QualifierHierarchy
* @param type the type to test
* @return list of reasons the type is invalid, or empty list if the type is valid
*/
protected List isValidStructurally(
QualifierHierarchy qualifierHierarchy, AnnotatedTypeMirror type) {
SimpleAnnotatedTypeScanner, QualifierHierarchy> scanner =
new SimpleAnnotatedTypeScanner<>(
(atm, q) -> isTopLevelValidType(q, atm),
DiagMessage::mergeLists,
Collections.emptyList());
return scanner.visit(type, qualifierHierarchy);
}
/**
* Checks every property listed in {@link #isValidStructurally}, but only for the top level type.
* If successful, returns an empty list. If not successful, returns diagnostics.
*
* @param qualifierHierarchy the QualifierHierarchy
* @param type the type to be checked
* @return the diagnostics indicating failure, or an empty list if successful
*/
// This method returns a singleton or empyty list. Its return type is List rather than
// DiagMessage (with null indicting success) because its caller, isValidStructurally(), expects
// a list.
protected List isTopLevelValidType(
QualifierHierarchy qualifierHierarchy, AnnotatedTypeMirror type) {
// multiple annotations from the same hierarchy
Set annotations = type.getAnnotations();
Set seenTops = AnnotationUtils.createAnnotationSet();
for (AnnotationMirror anno : annotations) {
AnnotationMirror top = qualifierHierarchy.getTopAnnotation(anno);
if (AnnotationUtils.containsSame(seenTops, top)) {
return Collections.singletonList(
new DiagMessage(Kind.ERROR, "conflicting.annos", annotations, type));
}
seenTops.add(top);
}
boolean canHaveEmptyAnnotationSet = QualifierHierarchy.canHaveEmptyAnnotationSet(type);
// wrong number of annotations
if (!canHaveEmptyAnnotationSet && seenTops.size() < qualifierHierarchy.getWidth()) {
return Collections.singletonList(
new DiagMessage(Kind.ERROR, "too.few.annotations", annotations, type));
}
// success
return Collections.emptyList();
}
protected void reportValidityResult(
final @CompilerMessageKey String errorType, final AnnotatedTypeMirror type, final Tree p) {
checker.reportError(p, errorType, type.getAnnotations(), type.toString());
isValid = false;
}
/**
* Like {@link #reportValidityResult}, but the type is printed in the error message without
* annotations. This method would print "annotation @NonNull is not permitted on type int",
* whereas {@link #reportValidityResult} would print "annotation @NonNull is not permitted on
* type @NonNull int". In addition, when the underlying type is a compound type such as
* {@code @Bad List}, the erased type will be used, i.e., "{@code List}" will print
* instead of "{@code @Bad List}".
*/
protected void reportValidityResultOnUnannotatedType(
final @CompilerMessageKey String errorType, final AnnotatedTypeMirror type, final Tree p) {
TypeMirror underlying =
TypeAnnotationUtils.unannotatedType(type.getErased().getUnderlyingType());
checker.reportError(p, errorType, type.getAnnotations(), underlying.toString());
isValid = false;
}
/**
* Most errors reported by this class are of the form type.invalid. This method reports when the
* bounds of a wildcard or type variable don't make sense. Bounds make sense when the effective
* annotations on the upper bound are supertypes of those on the lower bounds for all hierarchies.
* To ensure that this subtlety is not lost on users, we report "bound" and print the bounds along
* with the invalid type rather than a "type.invalid".
*
* @param type the type with invalid bounds
* @param tree where to report the error
*/
protected void reportInvalidBounds(final AnnotatedTypeMirror type, final Tree tree) {
final String label;
final AnnotatedTypeMirror upperBound;
final AnnotatedTypeMirror lowerBound;
switch (type.getKind()) {
case TYPEVAR:
label = "type parameter";
upperBound = ((AnnotatedTypeVariable) type).getUpperBound();
lowerBound = ((AnnotatedTypeVariable) type).getLowerBound();
break;
case WILDCARD:
label = "wildcard";
upperBound = ((AnnotatedWildcardType) type).getExtendsBound();
lowerBound = ((AnnotatedWildcardType) type).getSuperBound();
break;
default:
throw new BugInCF("Type is not bounded.%ntype=%s%ntree=%s", type, tree);
}
checker.reportError(
tree,
"bound",
label,
type.toString(),
upperBound.toString(true),
lowerBound.toString(true));
isValid = false;
}
protected void reportInvalidType(final AnnotatedTypeMirror type, final Tree p) {
reportValidityResult("type.invalid", type, p);
}
/**
* Report an "annotations.on.use" error for the given type and tree.
*
* @param type the type with invalid annotations
* @param p the tree where to report the error
*/
protected void reportInvalidAnnotationsOnUse(final AnnotatedTypeMirror type, final Tree p) {
reportValidityResultOnUnannotatedType("annotations.on.use", type, p);
}
@Override
public Void visitDeclared(AnnotatedDeclaredType type, Tree tree) {
if (visitedNodes.containsKey(type)) {
return visitedNodes.get(type);
}
final boolean skipChecks = checker.shouldSkipUses(type.getUnderlyingType().asElement());
if (checkTopLevelDeclaredOrPrimitiveType && !skipChecks) {
// Ensure that type use is a subtype of the element type
// isValidUse determines the erasure of the types.
Set bounds =
atypeFactory.getTypeDeclarationBounds(type.getUnderlyingType());
AnnotatedDeclaredType elemType = type.deepCopy();
elemType.clearPrimaryAnnotations();
elemType.addAnnotations(bounds);
if (!visitor.isValidUse(elemType, type, tree)) {
reportInvalidAnnotationsOnUse(type, tree);
}
}
// Set checkTopLevelDeclaredType to true, because the next time visitDeclared is called,
// the type isn't the top level, so always do the check.
checkTopLevelDeclaredOrPrimitiveType = true;
if (TreeUtils.isClassTree(tree)) {
visitedNodes.put(type, null);
visitClassTypeParameters(type, (ClassTree) tree);
return null;
}
/*
* Try to reconstruct the ParameterizedTypeTree from the given tree.
* TODO: there has to be a nicer way to do this...
*/
Pair p = extractParameterizedTypeTree(tree, type);
ParameterizedTypeTree typeArgTree = p.first;
type = p.second;
if (typeArgTree == null) {
return super.visitDeclared(type, tree);
} // else
// We put this here because we don't want to put it in visitedNodes before calling
// super (in the else branch) because that would cause the super implementation
// to detect that we've already visited type and to immediately return.
visitedNodes.put(type, null);
// We have a ParameterizedTypeTree -> visit it.
visitParameterizedType(type, typeArgTree);
/*
* Instead of calling super with the unchanged "tree", adapt the
* second argument to be the corresponding type argument tree. This
* ensures that the first and second parameter to this method always
* correspond. visitDeclared is the only method that had this
* problem.
*/
List tatypes = type.getTypeArguments();
if (tatypes == null) {
return null;
}
// May be zero for a "diamond" (inferred type args in constructor invocation).
int numTypeArgs = typeArgTree.getTypeArguments().size();
if (numTypeArgs != 0) {
// TODO: this should be an equality, but in
// http://buffalo.cs.washington.edu:8080/job/jdk6-daikon-typecheck/2061/console
// it failed with:
// daikon/Debug.java; message: size mismatch for type arguments:
// @NonNull Object and Class
// but I didn't manage to reduce it to a test case.
assert tatypes.size() <= numTypeArgs || skipChecks
: "size mismatch for type arguments: " + type + " and " + typeArgTree;
for (int i = 0; i < tatypes.size(); ++i) {
scan(tatypes.get(i), typeArgTree.getTypeArguments().get(i));
}
}
// Don't call the super version, because it creates a mismatch
// between the first and second parameters.
// return super.visitDeclared(type, tree);
return null;
}
/**
* Visits the type parameters of a class tree.
*
* @param type type of {@code tree}
* @param tree a class tree
*/
protected void visitClassTypeParameters(AnnotatedDeclaredType type, ClassTree tree) {
for (int i = 0, size = type.getTypeArguments().size(); i < size; i++) {
AnnotatedTypeVariable typeParameter = (AnnotatedTypeVariable) type.getTypeArguments().get(i);
TypeParameterTree typeParameterTree = tree.getTypeParameters().get(i);
scan(typeParameter, typeParameterTree);
}
}
/**
* Visits type parameter bounds.
*
* @param typeParameter type of {@code typeParameterTree}
* @param typeParameterTree a type parameter tree
*/
protected void visitTypeParameterBounds(
AnnotatedTypeVariable typeParameter, TypeParameterTree typeParameterTree) {
List boundTrees = typeParameterTree.getBounds();
if (boundTrees.size() == 1) {
scan(typeParameter.getUpperBound(), boundTrees.get(0));
} else if (boundTrees.size() == 0) {
// The upper bound is implicitly Object
scan(typeParameter.getUpperBound(), typeParameterTree);
} else {
AnnotatedIntersectionType intersectionType =
(AnnotatedIntersectionType) typeParameter.getUpperBound();
for (int j = 0; j < intersectionType.getBounds().size(); j++) {
scan(intersectionType.getBounds().get(j), boundTrees.get(j));
}
}
}
/**
* If {@code tree} has a {@link ParameterizedTypeTree}, then the tree and its type is returned.
* Otherwise null and {@code type} are returned.
*
* @param tree tree to search
* @param type type to return if no {@code ParameterizedTypeTree} is found
* @return if {@code tree} has a {@code ParameterizedTypeTree}, then returns the tree and its
* type. Otherwise, returns null and {@code type}.
*/
private Pair<@Nullable ParameterizedTypeTree, AnnotatedDeclaredType> extractParameterizedTypeTree(
Tree tree, AnnotatedDeclaredType type) {
ParameterizedTypeTree typeargtree = null;
switch (tree.getKind()) {
case VARIABLE:
Tree lt = ((VariableTree) tree).getType();
if (lt instanceof ParameterizedTypeTree) {
typeargtree = (ParameterizedTypeTree) lt;
} else {
// System.out.println("Found a: " + lt);
}
break;
case PARAMETERIZED_TYPE:
typeargtree = (ParameterizedTypeTree) tree;
break;
case NEW_CLASS:
NewClassTree nct = (NewClassTree) tree;
ExpressionTree nctid = nct.getIdentifier();
if (nctid.getKind() == Tree.Kind.PARAMETERIZED_TYPE) {
typeargtree = (ParameterizedTypeTree) nctid;
/*
* This is quite tricky... for anonymous class instantiations,
* the type at this point has no type arguments. By doing the
* following, we get the type arguments again.
*/
type = (AnnotatedDeclaredType) atypeFactory.getAnnotatedType(typeargtree);
}
break;
case ANNOTATED_TYPE:
AnnotatedTypeTree tr = (AnnotatedTypeTree) tree;
ExpressionTree undtr = tr.getUnderlyingType();
if (undtr instanceof ParameterizedTypeTree) {
typeargtree = (ParameterizedTypeTree) undtr;
} else if (undtr instanceof IdentifierTree) {
// @Something D -> Nothing to do
} else {
// TODO: add more test cases to ensure that nested types are
// handled correctly,
// e.g. @Nullable() List<@Nullable Object>[][]
Pair p =
extractParameterizedTypeTree(undtr, type);
typeargtree = p.first;
type = p.second;
}
break;
case IDENTIFIER:
case ARRAY_TYPE:
case NEW_ARRAY:
case MEMBER_SELECT:
case UNBOUNDED_WILDCARD:
case EXTENDS_WILDCARD:
case SUPER_WILDCARD:
case TYPE_PARAMETER:
// Nothing to do.
break;
case METHOD:
// If a MethodTree is passed, it's just the return type that is validated.
// See BaseTypeVisitor#validateTypeOf.
MethodTree methodTree = (MethodTree) tree;
if (methodTree.getReturnType() instanceof ParameterizedTypeTree) {
typeargtree = (ParameterizedTypeTree) methodTree.getReturnType();
}
break;
default:
// The parameterized type is the result of some expression tree.
// No need to do anything further.
break;
}
return Pair.of(typeargtree, type);
}
@Override
@SuppressWarnings("signature:argument") // PrimitiveType.toString(): @PrimitiveType
public Void visitPrimitive(AnnotatedPrimitiveType type, Tree tree) {
if (!checkTopLevelDeclaredOrPrimitiveType
|| checker.shouldSkipUses(type.getUnderlyingType().toString())) {
return super.visitPrimitive(type, tree);
}
if (!visitor.isValidUse(type, tree)) {
reportInvalidAnnotationsOnUse(type, tree);
}
return super.visitPrimitive(type, tree);
}
@Override
public Void visitArray(AnnotatedArrayType type, Tree tree) {
// TODO: is there already or add a helper method
// to determine the non-array component type
AnnotatedTypeMirror comp = type;
do {
comp = ((AnnotatedArrayType) comp).getComponentType();
} while (comp.getKind() == TypeKind.ARRAY);
if (comp.getKind() == TypeKind.DECLARED
&& checker.shouldSkipUses(((AnnotatedDeclaredType) comp).getUnderlyingType().asElement())) {
return super.visitArray(type, tree);
}
if (!visitor.isValidUse(type, tree)) {
reportInvalidAnnotationsOnUse(type, tree);
}
return super.visitArray(type, tree);
}
/**
* Checks that the annotations on the type arguments supplied to a type or a method invocation are
* within the bounds of the type variables as declared, and issues the "type.argument" error if
* they are not.
*
* @param type the type to check
* @param tree the type's tree
*/
protected Void visitParameterizedType(AnnotatedDeclaredType type, ParameterizedTypeTree tree) {
// System.out.printf("TypeValidator.visitParameterizedType: type: %s, tree: %s%n", type, tree);
if (TreeUtils.isDiamondTree(tree)) {
return null;
}
final TypeElement element = (TypeElement) type.getUnderlyingType().asElement();
if (checker.shouldSkipUses(element)) {
return null;
}
AnnotatedDeclaredType capturedType =
(AnnotatedDeclaredType) atypeFactory.applyCaptureConversion(type);
List bounds =
atypeFactory.typeVariablesFromUse(capturedType, element);
visitor.checkTypeArguments(
tree,
bounds,
capturedType.getTypeArguments(),
tree.getTypeArguments(),
element.getSimpleName(),
element.getTypeParameters());
@SuppressWarnings(
"interning:not.interned") // applyCaptureConversion returns the passed type if type does not
// have wildcards.
boolean hasCapturedTypeVariables = capturedType != type;
if (hasCapturedTypeVariables) {
// Check that the extends bound of the captured type variable is a subtype of the extends
// bound of the wildcard.
int numTypeArgs = capturedType.getTypeArguments().size();
// First create a mapping from captured type variable to its wildcard.
Map typeVarToWildcard = new HashMap<>(numTypeArgs);
for (int i = 0; i < numTypeArgs; i++) {
AnnotatedTypeMirror captureTypeArg = capturedType.getTypeArguments().get(i);
if (TypesUtils.isCapturedTypeVariable(captureTypeArg.getUnderlyingType())
&& type.getTypeArguments().get(i).getKind() == TypeKind.WILDCARD) {
AnnotatedTypeVariable capturedTypeVar = (AnnotatedTypeVariable) captureTypeArg;
AnnotatedWildcardType wildcard = (AnnotatedWildcardType) type.getTypeArguments().get(i);
typeVarToWildcard.put(capturedTypeVar.getUnderlyingType(), wildcard);
}
}
for (int i = 0; i < numTypeArgs; i++) {
AnnotatedTypeMirror captureTypeArg = capturedType.getTypeArguments().get(i);
if (type.getTypeArguments().get(i).getKind() == TypeKind.WILDCARD) {
AnnotatedWildcardType wildcard = (AnnotatedWildcardType) type.getTypeArguments().get(i);
if (TypesUtils.isCapturedTypeVariable(captureTypeArg.getUnderlyingType())) {
AnnotatedTypeVariable capturedTypeVar = (AnnotatedTypeVariable) captureTypeArg;
// Substitute the captured type variables with their wildcards. Without this, the
// isSubtype check crashes because wildcards aren't comparable with type variables.
AnnotatedTypeMirror catpureTypeVarUB =
atypeFactory
.getTypeVarSubstitutor()
.substituteWithoutCopyingTypeArguments(
typeVarToWildcard, capturedTypeVar.getUpperBound());
if (!atypeFactory
.getTypeHierarchy()
.isSubtype(catpureTypeVarUB, wildcard.getExtendsBound())) {
checker.reportError(
tree.getTypeArguments().get(i),
"type.argument",
element.getTypeParameters().get(i),
element.getSimpleName(),
wildcard.getExtendsBound(),
capturedTypeVar.getUpperBound());
}
} else if (AnnotatedTypes.isExplicitlySuperBounded(wildcard)) {
// If the super bound of the wildcard is the same as the upper bound of the type
// parameter, then javac uses the bound rather than creating a fresh type variable.
// (See https://bugs.openjdk.java.net/browse/JDK-8054309.)
// In this case, the Checker Framework uses the annotations on the super bound of the
// wildcard and ignores the annotations on the extends bound. So, issue a warning if
// the annotations on the extends bound are not the same as the annotations on the super
// bound.
if (!(atypeFactory
.getQualifierHierarchy()
.isSubtype(
wildcard.getSuperBound().getEffectiveAnnotations(),
wildcard.getExtendsBound().getAnnotations())
&& atypeFactory
.getQualifierHierarchy()
.isSubtype(
wildcard.getExtendsBound().getAnnotations(),
wildcard.getSuperBound().getEffectiveAnnotations()))) {
checker.reportError(
tree.getTypeArguments().get(i),
"super.wildcard",
wildcard.getExtendsBound(),
wildcard.getSuperBound());
}
}
}
}
}
return null;
}
@Override
public Void visitTypeVariable(AnnotatedTypeVariable type, Tree tree) {
if (visitedNodes.containsKey(type)) {
return visitedNodes.get(type);
}
if (type.isDeclaration() && !areBoundsValid(type.getUpperBound(), type.getLowerBound())) {
reportInvalidBounds(type, tree);
}
AnnotatedTypeVariable useOfTypeVar = type.asUse();
if (tree instanceof TypeParameterTree) {
TypeParameterTree typeParameterTree = (TypeParameterTree) tree;
visitedNodes.put(useOfTypeVar, defaultResult);
visitTypeParameterBounds(useOfTypeVar, typeParameterTree);
visitedNodes.put(useOfTypeVar, defaultResult);
return null;
}
return super.visitTypeVariable(useOfTypeVar, tree);
}
@Override
public Void visitWildcard(AnnotatedWildcardType type, Tree tree) {
if (visitedNodes.containsKey(type)) {
return visitedNodes.get(type);
}
if (!areBoundsValid(type.getExtendsBound(), type.getSuperBound())) {
reportInvalidBounds(type, tree);
}
return super.visitWildcard(type, tree);
}
/**
* Returns true if the effective annotations on the upperBound are above those on the lowerBound.
*
* @return true if the effective annotations on the upperBound are above those on the lowerBound
*/
public boolean areBoundsValid(
final AnnotatedTypeMirror upperBound, final AnnotatedTypeMirror lowerBound) {
final QualifierHierarchy qualifierHierarchy = atypeFactory.getQualifierHierarchy();
final Set upperBoundAnnos =
AnnotatedTypes.findEffectiveAnnotations(qualifierHierarchy, upperBound);
final Set lowerBoundAnnos =
AnnotatedTypes.findEffectiveAnnotations(qualifierHierarchy, lowerBound);
if (upperBoundAnnos.size() == lowerBoundAnnos.size()) {
return qualifierHierarchy.isSubtype(lowerBoundAnnos, upperBoundAnnos);
} // else
// When upperBoundAnnos.size() != lowerBoundAnnos.size() one of the two bound types will
// be reported as invalid. Therefore, we do not do any other comparisons nor do we report
// a bound
return true;
}
}