blob: a0fc9b2b4007f84081760b4ef50475002759b8df [file] [log] [blame]
package org.checkerframework.framework.type;
import java.util.List;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
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.lang.model.util.Types;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.framework.qual.Covariant;
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.AnnotatedNullType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
import org.checkerframework.framework.type.visitor.AbstractAtmComboVisitor;
import org.checkerframework.framework.util.AnnotatedTypes;
import org.checkerframework.framework.util.AtmCombo;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypesUtils;
/**
* Default implementation of TypeHierarchy that implements the JLS specification with minor
* deviations as outlined by the Checker Framework manual. Changes to the JLS include forbidding
* covariant array types, raw types, and allowing covariant type arguments depending on various
* options passed to DefaultTypeHierarchy.
*
* <p>Subtyping rules of the JLS can be found in <a
* href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-4.html#jls-4.10">section 4.10,
* "Subtyping"</a>.
*
* <p>Note: The visit methods of this class must be public but it is intended to be used through a
* TypeHierarchy interface reference which will only allow isSubtype to be called. Clients should
* not call the visit methods.
*/
public class DefaultTypeHierarchy extends AbstractAtmComboVisitor<Boolean, Void>
implements TypeHierarchy {
// used for processingEnvironment when needed
protected final BaseTypeChecker checker;
protected final QualifierHierarchy qualifierHierarchy;
protected final StructuralEqualityComparer equalityComparer;
protected final boolean ignoreRawTypes;
protected final boolean invariantArrayComponents;
// TODO: Incorporate feedback from David/Suzanne
// IMPORTANT_NOTE:
//
// For MultigraphQualifierHierarchies, we check the subtyping relationship of each annotation
// hierarchy individually. This is done because when comparing a pair of type variables,
// sometimes you need to traverse and compare the bounds of two type variables. Other times it
// is incorrect to compare the bounds. These two cases can occur simultaneously when comparing
// two hierarchies at once. In this case, comparing both hierarchies simultaneously will lead
// to an error. More detail is given below.
//
// Recall, type variables may or may not have a primary annotation for each individual
// hierarchy. When comparing
// two type variables for a specific hierarchy we have five possible cases:
// case 1: only one of the type variables has a primary annotation
// case 2a: both type variables have primary annotations and they are uses of the same type
// parameter
// case 2b: both type variables have primary annotations and they are uses of different
// type parameters
// case 3a: neither type variable has a primary annotation and they are uses of the same
// type parameter
// case 3b: neither type variable has a primary annotation and they are uses of different
// type parameters
//
// Case 1, 2b, and 3b require us to traverse both type variables bounds to ensure that the
// subtype's upper bound is a subtype of the supertype's lower bound. Cases 2a requires only
// that we check that the primary annotation on the subtype is a subtype of the primary
// annotation on the supertype. In case 3a, we can just return true, since two
// non-primary-annotated uses of the same type parameter are equivalent. In this case it would
// be an error to check the bounds because the check would only return true when the bounds are
// exact but it should always return true.
//
// A problem occurs when, one hierarchy matches cases 1, 2b, or 3b and the other matches 3a. In
// the first set of cases we MUST check the type variables' bounds. In case 3a we MUST NOT
// check the bounds. e.g.
//
// Suppose I have a hierarchy with two tops @A1 and @B1. Let @A0 <: @A1 and @B0 <: @B1.
// @A1 T t1; T t2;
// t1 = t2;
//
// To typecheck "t1 = t2;" in the hierarchy topped by @A1, we need to descend into the bounds of
// t1 and t2 (where t1's bounds will be overridden by @A1). However, for hierarchy B we need
// only recognize that since neither variable has a primary annotation, the types are equivalent
// and no traversal is needed. If we tried to do these two actions simultaneously, in every
// visit and isSubtype call, we would have to check to see that the @B hierarchy has been
// handled and ignore those annotations.
//
// Solutions:
// We could handle this problem by keeping track of which hierarchies have already been taken
// care of. We could then check each hierarchy before making comparisons. But this would lead
// to complicated plumbing that would be hard to understand.
// The chosen solution is to only check one hierarchy at a time. One trade-off to this approach
// is that we have to re-traverse the types for each hierarchy being checked.
//
// The field currentTop identifies the hierarchy for which the types are currently being checked.
// Final note: all annotation comparisons are done via isPrimarySubtype, isBottom, and
// isAnnoSubtype in order to ensure that we first get the annotations in the hierarchy of
// currentTop before passing annotations to qualifierHierarchy.
/** The top annotation of the hierarchy currently being checked. */
protected AnnotationMirror currentTop;
/** Stores the result of isSubtype, if that result is true. */
protected final SubtypeVisitHistory isSubtypeVisitHistory;
/**
* Stores the result of {@link #areEqualInHierarchy(AnnotatedTypeMirror, AnnotatedTypeMirror)} for
* type arguments. Prevents infinite recursion on types that refer to themselves. (Stores both
* true and false results.)
*/
protected final StructuralEqualityVisitHistory areEqualVisitHistory;
/** The Covariant.value field/element. */
final ExecutableElement covariantValueElement;
/**
* Creates a DefaultTypeHierarchy.
*
* @param checker the type-checker that is associated with this
* @param qualifierHierarchy the qualiifer hierarchy that is associated with this
* @param ignoreRawTypes whether to ignore raw types
* @param invariantArrayComponents whether to make array subtyping invariant with respect to array
* component types
*/
public DefaultTypeHierarchy(
final BaseTypeChecker checker,
final QualifierHierarchy qualifierHierarchy,
boolean ignoreRawTypes,
boolean invariantArrayComponents) {
this.checker = checker;
this.qualifierHierarchy = qualifierHierarchy;
this.isSubtypeVisitHistory = new SubtypeVisitHistory();
this.areEqualVisitHistory = new StructuralEqualityVisitHistory();
this.equalityComparer = createEqualityComparer();
this.ignoreRawTypes = ignoreRawTypes;
this.invariantArrayComponents = invariantArrayComponents;
covariantValueElement =
TreeUtils.getMethod(Covariant.class, "value", 0, checker.getProcessingEnvironment());
}
/**
* Create the equality comparer.
*
* @return the equality comparer
*/
protected StructuralEqualityComparer createEqualityComparer() {
return new StructuralEqualityComparer(areEqualVisitHistory);
}
/**
* Returns true if subtype {@literal <:} supertype.
*
* <p>This implementation iterates over all top annotations and invokes {@link
* #isSubtype(AnnotatedTypeMirror, AnnotatedTypeMirror, AnnotationMirror)}. Most type systems
* should not override this method, but instead override {@link #isSubtype(AnnotatedTypeMirror,
* AnnotatedTypeMirror, AnnotationMirror)} or some of the {@code visitXXX} methods.
*
* @param subtype expected subtype
* @param supertype expected supertype
* @return true if subtype is a subtype of supertype or equal to it
*/
@Override
public boolean isSubtype(final AnnotatedTypeMirror subtype, final AnnotatedTypeMirror supertype) {
for (final AnnotationMirror top : qualifierHierarchy.getTopAnnotations()) {
if (!isSubtype(subtype, supertype, top)) {
return false;
}
}
return true;
}
/**
* Returns true if {@code subtype <: supertype}, but only for the hierarchy of which {@code top}
* is the top.
*
* @param subtype expected subtype
* @param supertype expected supertype
* @param top the top of the hierarchy for which we want to make a comparison
* @return true if {@code subtype} is a subtype of, or equal to, {@code supertype} in the
* qualifier hierarchy whose top is {@code top}
*/
protected boolean isSubtype(
final AnnotatedTypeMirror subtype,
final AnnotatedTypeMirror supertype,
final AnnotationMirror top) {
assert top != null;
currentTop = top;
return AtmCombo.accept(subtype, supertype, null, this);
}
/**
* Returns error message for the case when two types shouldn't be compared.
*
* @return error message for the case when two types shouldn't be compared
*/
@Override
protected String defaultErrorMessage(
final AnnotatedTypeMirror subtype, final AnnotatedTypeMirror supertype, final Void p) {
return "Incomparable types ("
+ subtype
+ ", "
+ supertype
+ ") visitHistory = "
+ isSubtypeVisitHistory;
}
/**
* Compare the primary annotations of {@code subtype} and {@code supertype}. Neither type can be
* missing annotations.
*
* @param subtype a type that might be a subtype (with respect to primary annotations)
* @param supertype a type that might be a supertype (with respect to primary annotations)
* @return true if the primary annotation on subtype {@literal <:} primary annotation on supertype
* for the current top.
*/
protected boolean isPrimarySubtype(AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype) {
final AnnotationMirror subtypeAnno = subtype.getAnnotationInHierarchy(currentTop);
final AnnotationMirror supertypeAnno = supertype.getAnnotationInHierarchy(currentTop);
if (checker.getTypeFactory().hasQualifierParameterInHierarchy(supertype, currentTop)
&& checker.getTypeFactory().hasQualifierParameterInHierarchy(subtype, currentTop)) {
// If the types have a class qualifier parameter, the qualifiers must be equivalent.
return qualifierHierarchy.isSubtype(subtypeAnno, supertypeAnno)
&& qualifierHierarchy.isSubtype(supertypeAnno, subtypeAnno);
}
return qualifierHierarchy.isSubtype(subtypeAnno, supertypeAnno);
}
/**
* Like {@link #isSubtype(AnnotatedTypeMirror, AnnotatedTypeMirror)}, but uses a cache to prevent
* infinite recursion on recursive types.
*
* @param subtype a type that may be a subtype
* @param supertype a type that may be a supertype
* @return true if subtype {@literal <:} supertype
*/
protected boolean isSubtypeCaching(
final AnnotatedTypeMirror subtype, final AnnotatedTypeMirror supertype) {
if (isSubtypeVisitHistory.contains(subtype, supertype, currentTop)) {
// visitHistory only contains pairs in a subtype relationship.
return true;
}
boolean result = isSubtype(subtype, supertype, currentTop);
// The call to put has no effect if result is false.
isSubtypeVisitHistory.put(subtype, supertype, currentTop, result);
return result;
}
/**
* Are all the types in {@code subtypes} a subtype of {@code supertype}?
*
* <p>The underlying type mirrors of {@code subtypes} must be subtypes of the underlying type
* mirror of {@code supertype}.
*/
protected boolean areAllSubtypes(
final Iterable<? extends AnnotatedTypeMirror> subtypes, final AnnotatedTypeMirror supertype) {
for (final AnnotatedTypeMirror subtype : subtypes) {
if (!isSubtype(subtype, supertype, currentTop)) {
return false;
}
}
return true;
}
protected boolean areEqualInHierarchy(
final AnnotatedTypeMirror type1, final AnnotatedTypeMirror type2) {
return equalityComparer.areEqualInHierarchy(type1, type2, currentTop);
}
/**
* A declared type is considered a supertype of another declared type only if all of the type
* arguments of the declared type "contain" the corresponding type arguments of the subtype.
* Containment is described in <a
* href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-4.html#jls-4.5.1">JLS section
* 4.5.1 "Type Arguments of Parameterized Types"</a>.
*
* @param inside a type argument of the "subtype"
* @param outside a type argument of the "supertype"
* @param canBeCovariant whether or not type arguments are allowed to be covariant
* @return true if inside is contained by outside, or if canBeCovariant == true and {@code inside
* <: outside}
*/
protected boolean isContainedBy(
final AnnotatedTypeMirror inside, final AnnotatedTypeMirror outside, boolean canBeCovariant) {
if (ignoreUninferredTypeArgument(inside) || ignoreUninferredTypeArgument(outside)) {
areEqualVisitHistory.put(inside, outside, currentTop, true);
return true;
}
if (outside.getKind() != TypeKind.WILDCARD
&& !TypesUtils.isCaptured(outside.getUnderlyingType())) {
if (canBeCovariant) {
return isSubtype(inside, outside, currentTop);
}
return areEqualInHierarchy(inside, outside);
}
AnnotatedTypeMirror outsideUpperBound;
AnnotatedTypeMirror outsideLowerBound;
if (outside.getKind() == TypeKind.WILDCARD) {
outsideUpperBound = ((AnnotatedWildcardType) outside).getExtendsBound();
outsideLowerBound = ((AnnotatedWildcardType) outside).getSuperBound();
} else if (TypesUtils.isCaptured(outside.getUnderlyingType())) {
outsideUpperBound = ((AnnotatedTypeVariable) outside).getUpperBound();
outsideLowerBound = ((AnnotatedTypeVariable) outside).getLowerBound();
} else {
throw new BugInCF("Expected a wildcard or captured type variable, but found " + outside);
}
Boolean previousResult = areEqualVisitHistory.get(inside, outside, currentTop);
if (previousResult != null) {
return previousResult;
}
areEqualVisitHistory.put(inside, outside, currentTop, true);
boolean result =
isContainedWildcard(inside, outside, outsideUpperBound, outsideLowerBound, canBeCovariant);
areEqualVisitHistory.put(inside, outside, currentTop, result);
return result;
}
private boolean isContainedWildcard(
AnnotatedTypeMirror inside,
AnnotatedTypeMirror outside,
AnnotatedTypeMirror outsideUpperBound,
AnnotatedTypeMirror outsideLowerBound,
boolean canBeCovariant) {
if (inside.equals(outside)) {
// If they are equal, outside always contains inside.
return true;
}
if (inside.getKind() == TypeKind.WILDCARD) {
outsideUpperBound =
checker
.getTypeFactory()
.widenToUpperBound(outsideUpperBound, (AnnotatedWildcardType) inside);
}
while (outsideUpperBound.getKind() == TypeKind.WILDCARD) {
if (ignoreUninferredTypeArgument(outsideUpperBound)) {
return true;
}
outsideUpperBound = ((AnnotatedWildcardType) outsideUpperBound).getExtendsBound();
}
if (!TypesUtils.isErasedSubtype(
inside.getUnderlyingType(),
outsideUpperBound.getUnderlyingType(),
inside.atypeFactory.types)) {
// TODO: subtype is a wildcard that should have been captured, so just check the primary
// annotations.
AnnotationMirror subAnno = inside.getEffectiveAnnotationInHierarchy(currentTop);
AnnotationMirror superAnno = outsideUpperBound.getAnnotationInHierarchy(currentTop);
return qualifierHierarchy.isSubtype(subAnno, superAnno);
}
AnnotatedTypeMirror castedInside =
AnnotatedTypes.castedAsSuper(inside.atypeFactory, inside, outsideUpperBound);
if (!isSubtypeCaching(castedInside, outsideUpperBound)) {
return false;
}
if (outside.getKind() == TypeKind.WILDCARD && outsideLowerBound.getKind() == TypeKind.TYPEVAR) {
// tests/all-systems/Issue1991.java crashes without this.
return true;
}
return canBeCovariant || isSubtypeCaching(outsideLowerBound, inside);
}
/**
* Returns true if {@code type} is an uninferred type argument and if the checker should not issue
* warnings about uninferred type arguments.
*
* @param type type to check
* @return true if {@code type} is an uninferred type argument and if the checker should not issue
* warnings about uninferred type arguments
*/
private boolean ignoreUninferredTypeArgument(AnnotatedTypeMirror type) {
return type.atypeFactory.ignoreUninferredTypeArguments
&& type.getKind() == TypeKind.WILDCARD
&& ((AnnotatedWildcardType) type).isUninferredTypeArgument();
}
// ------------------------------------------------------------------------
// The rest of this file is the visitor methods. It is a lot of methods, one for each
// combination of types.
// ------------------------------------------------------------------------
// Arrays as subtypes
@Override
public Boolean visitArray_Array(
AnnotatedArrayType subtype, AnnotatedArrayType supertype, Void p) {
return isPrimarySubtype(subtype, supertype)
&& (invariantArrayComponents
? areEqualInHierarchy(subtype.getComponentType(), supertype.getComponentType())
: isSubtype(subtype.getComponentType(), supertype.getComponentType(), currentTop));
}
@Override
public Boolean visitArray_Declared(
AnnotatedArrayType subtype, AnnotatedDeclaredType supertype, Void p) {
return isPrimarySubtype(subtype, supertype);
}
@Override
public Boolean visitArray_Null(AnnotatedArrayType subtype, AnnotatedNullType supertype, Void p) {
return isPrimarySubtype(subtype, supertype);
}
@Override
public Boolean visitArray_Intersection(
AnnotatedArrayType subtype, AnnotatedIntersectionType supertype, Void p) {
return isSubtype(
AnnotatedTypes.castedAsSuper(subtype.atypeFactory, subtype, supertype),
supertype,
currentTop);
}
@Override
public Boolean visitArray_Wildcard(
AnnotatedArrayType subtype, AnnotatedWildcardType supertype, Void p) {
return visitWildcardSupertype(subtype, supertype);
}
// ------------------------------------------------------------------------
// Declared as subtype
@Override
public Boolean visitDeclared_Array(
AnnotatedDeclaredType subtype, AnnotatedArrayType supertype, Void p) {
return isPrimarySubtype(subtype, supertype);
}
@Override
public Boolean visitDeclared_Declared(
AnnotatedDeclaredType subtype, AnnotatedDeclaredType supertype, Void p) {
if (!isPrimarySubtype(subtype, supertype)) {
return false;
}
AnnotatedTypeFactory factory = subtype.atypeFactory;
if (factory.ignoreUninferredTypeArguments
&& (factory.containsUninferredTypeArguments(subtype)
|| factory.containsUninferredTypeArguments(supertype))) {
// Calling castedAsSuper may cause the uninferredTypeArguments to be lost. So, just
// return true here.
return true;
}
AnnotatedDeclaredType subtypeAsSuper =
AnnotatedTypes.castedAsSuper(subtype.atypeFactory, subtype, supertype);
if (isSubtypeVisitHistory.contains(subtypeAsSuper, supertype, currentTop)) {
return true;
}
final boolean result =
visitTypeArgs(subtypeAsSuper, supertype, subtype.wasRaw(), supertype.wasRaw());
isSubtypeVisitHistory.put(subtypeAsSuper, supertype, currentTop, result);
return result;
}
/**
* A helper class for visitDeclared_Declared. There are subtypes of DefaultTypeHierarchy that need
* to customize the handling of type arguments. This method provides a convenient extension point.
*/
protected boolean visitTypeArgs(
final AnnotatedDeclaredType subtype,
final AnnotatedDeclaredType supertype,
final boolean subtypeRaw,
final boolean supertypeRaw) {
final boolean ignoreTypeArgs = ignoreRawTypes && (subtypeRaw || supertypeRaw);
if (ignoreTypeArgs) {
return true;
}
final List<? extends AnnotatedTypeMirror> subtypeTypeArgs = subtype.getTypeArguments();
final List<? extends AnnotatedTypeMirror> supertypeTypeArgs = supertype.getTypeArguments();
if (subtypeTypeArgs.size() != supertypeTypeArgs.size()) {
return false;
}
if (subtypeTypeArgs.isEmpty()) {
return true;
}
final TypeElement supertypeElem = (TypeElement) supertype.getUnderlyingType().asElement();
AnnotationMirror covariantAnno =
supertype.atypeFactory.getDeclAnnotation(supertypeElem, Covariant.class);
List<Integer> covariantArgIndexes =
(covariantAnno == null)
? null
: AnnotationUtils.getElementValueArray(
covariantAnno, covariantValueElement, Integer.class);
for (int i = 0; i < supertypeTypeArgs.size(); i++) {
final AnnotatedTypeMirror superTypeArg = supertypeTypeArgs.get(i);
final AnnotatedTypeMirror subTypeArg = subtypeTypeArgs.get(i);
final boolean covariant = covariantArgIndexes != null && covariantArgIndexes.contains(i);
boolean result = isContainedBy(subTypeArg, superTypeArg, covariant);
if (!result) {
return false;
}
}
return true;
}
@Override
public Boolean visitDeclared_Intersection(
AnnotatedDeclaredType subtype, AnnotatedIntersectionType supertype, Void p) {
return visitIntersectionSupertype(subtype, supertype);
}
@Override
public Boolean visitDeclared_Null(
AnnotatedDeclaredType subtype, AnnotatedNullType supertype, Void p) {
return isPrimarySubtype(subtype, supertype);
}
@Override
public Boolean visitDeclared_Primitive(
AnnotatedDeclaredType subtype, AnnotatedPrimitiveType supertype, Void p) {
// We do an asSuper first because in some cases unboxing implies a more specific annotation
// e.g. @UnknownInterned Integer => @Interned int because primitives are always interned
final AnnotatedPrimitiveType subAsSuper =
AnnotatedTypes.castedAsSuper(subtype.atypeFactory, subtype, supertype);
if (subAsSuper == null) {
return isPrimarySubtype(subtype, supertype);
}
return isPrimarySubtype(subAsSuper, supertype);
}
@Override
public Boolean visitDeclared_Typevar(
AnnotatedDeclaredType subtype, AnnotatedTypeVariable supertype, Void p) {
return visitTypevarSupertype(subtype, supertype);
}
@Override
public Boolean visitDeclared_Union(
AnnotatedDeclaredType subtype, AnnotatedUnionType supertype, Void p) {
Types types = checker.getTypeUtils();
for (AnnotatedDeclaredType supertypeAltern : supertype.getAlternatives()) {
if (TypesUtils.isErasedSubtype(
subtype.getUnderlyingType(), supertypeAltern.getUnderlyingType(), types)
&& isSubtype(subtype, supertypeAltern, currentTop)) {
return true;
}
}
return false;
}
@Override
public Boolean visitDeclared_Wildcard(
AnnotatedDeclaredType subtype, AnnotatedWildcardType supertype, Void p) {
return visitWildcardSupertype(subtype, supertype);
}
// ------------------------------------------------------------------------
// Intersection as subtype
@Override
public Boolean visitIntersection_Declared(
AnnotatedIntersectionType subtype, AnnotatedDeclaredType supertype, Void p) {
return visitIntersectionSubtype(subtype, supertype);
}
@Override
public Boolean visitIntersection_Primitive(
AnnotatedIntersectionType subtype, AnnotatedPrimitiveType supertype, Void p) {
for (AnnotatedTypeMirror subtypeBound : subtype.getBounds()) {
if (TypesUtils.isBoxedPrimitive(subtypeBound.getUnderlyingType())
&& isSubtype(subtypeBound, supertype, currentTop)) {
return true;
}
}
return false;
}
@Override
public Boolean visitIntersection_Intersection(
AnnotatedIntersectionType subtype, AnnotatedIntersectionType supertype, Void p) {
Types types = checker.getTypeUtils();
for (AnnotatedTypeMirror subBound : subtype.getBounds()) {
for (AnnotatedTypeMirror superBound : supertype.getBounds()) {
if (TypesUtils.isErasedSubtype(
subBound.getUnderlyingType(), superBound.getUnderlyingType(), types)
&& !isSubtype(subBound, superBound, currentTop)) {
return false;
}
}
}
return true;
}
@Override
public Boolean visitIntersection_Null(
AnnotatedIntersectionType subtype, AnnotatedNullType supertype, Void p) {
// this can occur through capture conversion/comparing bounds
for (AnnotatedTypeMirror bound : subtype.getBounds()) {
if (isPrimarySubtype(bound, supertype)) {
return true;
}
}
return false;
}
@Override
public Boolean visitIntersection_Typevar(
AnnotatedIntersectionType subtype, AnnotatedTypeVariable supertype, Void p) {
return visitIntersectionSubtype(subtype, supertype);
}
@Override
public Boolean visitIntersection_Wildcard(
AnnotatedIntersectionType subtype, AnnotatedWildcardType supertype, Void p) {
return visitIntersectionSubtype(subtype, supertype);
}
// ------------------------------------------------------------------------
// Null as subtype
@Override
public Boolean visitNull_Array(AnnotatedNullType subtype, AnnotatedArrayType supertype, Void p) {
return isPrimarySubtype(subtype, supertype);
}
@Override
public Boolean visitNull_Declared(
AnnotatedNullType subtype, AnnotatedDeclaredType supertype, Void p) {
return isPrimarySubtype(subtype, supertype);
}
@Override
public Boolean visitNull_Typevar(
AnnotatedNullType subtype, AnnotatedTypeVariable supertype, Void p) {
return visitTypevarSupertype(subtype, supertype);
}
@Override
public Boolean visitNull_Wildcard(
AnnotatedNullType subtype, AnnotatedWildcardType supertype, Void p) {
return visitWildcardSupertype(subtype, supertype);
}
@Override
public Boolean visitNull_Null(AnnotatedNullType subtype, AnnotatedNullType supertype, Void p) {
// this can occur when comparing typevar lower bounds since they are usually null types
return isPrimarySubtype(subtype, supertype);
}
@Override
public Boolean visitNull_Union(AnnotatedNullType subtype, AnnotatedUnionType supertype, Void p) {
for (AnnotatedDeclaredType supertypeAltern : supertype.getAlternatives()) {
if (isSubtype(subtype, supertypeAltern, currentTop)) {
return true;
}
}
return false;
}
@Override
public Boolean visitNull_Intersection(
AnnotatedNullType subtype, AnnotatedIntersectionType supertype, Void p) {
return isPrimarySubtype(subtype, supertype);
}
@Override
public Boolean visitNull_Primitive(
AnnotatedNullType subtype, AnnotatedPrimitiveType supertype, Void p) {
return isPrimarySubtype(subtype, supertype);
}
// ------------------------------------------------------------------------
// Primitive as subtype
@Override
public Boolean visitPrimitive_Declared(
AnnotatedPrimitiveType subtype, AnnotatedDeclaredType supertype, Void p) {
// see comment in visitDeclared_Primitive
final AnnotatedDeclaredType subAsSuper =
AnnotatedTypes.castedAsSuper(subtype.atypeFactory, subtype, supertype);
if (subAsSuper == null) {
return isPrimarySubtype(subtype, supertype);
}
return isPrimarySubtype(subAsSuper, supertype);
}
@Override
public Boolean visitPrimitive_Primitive(
AnnotatedPrimitiveType subtype, AnnotatedPrimitiveType supertype, Void p) {
return isPrimarySubtype(subtype, supertype);
}
@Override
public Boolean visitPrimitive_Intersection(
AnnotatedPrimitiveType subtype, AnnotatedIntersectionType supertype, Void p) {
return visitIntersectionSupertype(subtype, supertype);
}
@Override
public Boolean visitPrimitive_Typevar(
AnnotatedPrimitiveType subtype, AnnotatedTypeVariable supertype, Void p) {
return AtmCombo.accept(subtype, supertype.getUpperBound(), null, this);
}
@Override
public Boolean visitPrimitive_Wildcard(
AnnotatedPrimitiveType subtype, AnnotatedWildcardType supertype, Void p) {
if (supertype.atypeFactory.ignoreUninferredTypeArguments
&& supertype.isUninferredTypeArgument()) {
return true;
}
// this can occur when passing a primitive to a method on a raw type (see test
// checker/tests/nullness/RawAndPrimitive.java). This can also occur because we don't box
// primitives when we should and don't capture convert.
return isPrimarySubtype(subtype, supertype.getSuperBound());
}
// ------------------------------------------------------------------------
// Union as subtype
@Override
public Boolean visitUnion_Declared(
AnnotatedUnionType subtype, AnnotatedDeclaredType supertype, Void p) {
return visitUnionSubtype(subtype, supertype);
}
@Override
public Boolean visitUnion_Intersection(
AnnotatedUnionType subtype, AnnotatedIntersectionType supertype, Void p) {
// For example:
// <T extends Throwable & Cloneable> void method(T param) {}
// ...
// catch (Exception1 | Exception2 union) { // Assuming Exception1 and Exception2 implement
// Cloneable
// method(union);
// This case happens when checking that the inferred type argument is a subtype of the
// declared type argument of method.
// See org.checkerframework.common.basetype.BaseTypeVisitor#checkTypeArguments
return visitUnionSubtype(subtype, supertype);
}
@Override
public Boolean visitUnion_Union(
AnnotatedUnionType subtype, AnnotatedUnionType supertype, Void p) {
// For example:
// <T> void method(T param) {}
// ...
// catch (Exception1 | Exception2 union) {
// method(union);
// This case happens when checking the arguments to method after type variable substitution
return visitUnionSubtype(subtype, supertype);
}
@Override
public Boolean visitUnion_Wildcard(
AnnotatedUnionType subtype, AnnotatedWildcardType supertype, Void p) {
// For example:
// } catch (RuntimeException | IOException e) {
// ArrayList<? super Exception> lWildcard = new ArrayList<>();
// lWildcard.add(e);
return visitWildcardSupertype(subtype, supertype);
}
// ------------------------------------------------------------------------
// typevar as subtype
@Override
public Boolean visitTypevar_Declared(
AnnotatedTypeVariable subtype, AnnotatedDeclaredType supertype, Void p) {
return visitTypevarSubtype(subtype, supertype);
}
@Override
public Boolean visitTypevar_Intersection(
AnnotatedTypeVariable subtype, AnnotatedIntersectionType supertype, Void p) {
// this can happen when checking type param bounds
return visitIntersectionSupertype(subtype, supertype);
}
@Override
public Boolean visitTypevar_Primitive(
AnnotatedTypeVariable subtype, AnnotatedPrimitiveType supertype, Void p) {
return visitTypevarSubtype(subtype, supertype);
}
@Override
public Boolean visitTypevar_Typevar(
AnnotatedTypeVariable subtype, AnnotatedTypeVariable supertype, Void p) {
if (AnnotatedTypes.haveSameDeclaration(checker.getTypeUtils(), subtype, supertype)) {
// subtype and supertype are uses of the same type parameter
boolean subtypeHasAnno = subtype.getAnnotationInHierarchy(currentTop) != null;
boolean supertypeHasAnno = supertype.getAnnotationInHierarchy(currentTop) != null;
if (subtypeHasAnno && supertypeHasAnno) {
// if both have primary annotations then you can just check the primary annotations
// as the bounds are the same
return isPrimarySubtype(subtype, supertype);
} else if (!subtypeHasAnno && !supertypeHasAnno && areEqualInHierarchy(subtype, supertype)) {
// two unannotated uses of the same type parameter are of the same type
return true;
} else if (subtype.getUpperBound().getKind() == TypeKind.INTERSECTION) {
// This case happens when a type has an intersection bound. e.g.,
// T extends A & B
//
// And one use of the type has an annotation and the other does not. e.g.,
// @X T xt = ...; T t = ..;
// xt = t;
//
return visit(subtype.getUpperBound(), supertype.getLowerBound(), null);
}
}
if (AnnotatedTypes.areCorrespondingTypeVariables(
checker.getProcessingEnvironment().getElementUtils(), subtype, supertype)) {
if (areEqualInHierarchy(subtype, supertype)) {
return true;
}
}
// check that the upper bound of the subtype is below the lower bound of the supertype
return visitTypevarSubtype(subtype, supertype);
}
@Override
public Boolean visitTypevar_Null(
AnnotatedTypeVariable subtype, AnnotatedNullType supertype, Void p) {
return visitTypevarSubtype(subtype, supertype);
}
@Override
public Boolean visitTypevar_Wildcard(
AnnotatedTypeVariable subtype, AnnotatedWildcardType supertype, Void p) {
return visitWildcardSupertype(subtype, supertype);
}
// ------------------------------------------------------------------------
// wildcard as subtype
@Override
public Boolean visitWildcard_Array(
AnnotatedWildcardType subtype, AnnotatedArrayType supertype, Void p) {
return visitWildcardSubtype(subtype, supertype);
}
@Override
public Boolean visitWildcard_Declared(
AnnotatedWildcardType subtype, AnnotatedDeclaredType supertype, Void p) {
if (subtype.isUninferredTypeArgument()) {
if (subtype.atypeFactory.ignoreUninferredTypeArguments) {
return true;
} else if (supertype.getTypeArguments().isEmpty()) {
// visitWildcardSubtype doesn't check uninferred type arguments, because the
// underlying Java types may not be in the correct relationship. But, if the
// declared type does not have type arguments, then checking primary annotations is
// sufficient.
// For example, if the wildcard is ? extends @Nullable Object and the supertype is
// @Nullable String, then it is safe to return true. However if the supertype is
// @NullableList<@NonNull String> then it's not possible to decide if it is a
// subtype of the wildcard.
AnnotationMirror subtypeAnno = subtype.getEffectiveAnnotationInHierarchy(currentTop);
AnnotationMirror supertypeAnno = supertype.getAnnotationInHierarchy(currentTop);
return qualifierHierarchy.isSubtype(subtypeAnno, supertypeAnno);
}
}
return visitWildcardSubtype(subtype, supertype);
}
@Override
public Boolean visitWildcard_Intersection(
AnnotatedWildcardType subtype, AnnotatedIntersectionType supertype, Void p) {
return visitWildcardSubtype(subtype, supertype);
}
@Override
public Boolean visitWildcard_Primitive(
AnnotatedWildcardType subtype, AnnotatedPrimitiveType supertype, Void p) {
if (subtype.isUninferredTypeArgument()) {
AnnotationMirror subtypeAnno = subtype.getEffectiveAnnotationInHierarchy(currentTop);
AnnotationMirror supertypeAnno = supertype.getAnnotationInHierarchy(currentTop);
return qualifierHierarchy.isSubtype(subtypeAnno, supertypeAnno);
}
return visitWildcardSubtype(subtype, supertype);
}
@Override
public Boolean visitWildcard_Typevar(
AnnotatedWildcardType subtype, AnnotatedTypeVariable supertype, Void p) {
return visitWildcardSubtype(subtype, supertype);
}
@Override
public Boolean visitWildcard_Wildcard(
AnnotatedWildcardType subtype, AnnotatedWildcardType supertype, Void p) {
return visitWildcardSubtype(subtype, supertype);
}
// ------------------------------------------------------------------------
// These "visit" methods are utility methods that aren't part of the visit interface
// but that handle cases that more than one visit method shares in common.
/**
* An intersection is a supertype if all of its bounds are a supertype of subtype.
*
* @param subtype the possible subtype
* @param supertype the possible supertype
* @return true {@code subtype} is a subtype of {@code supertype}
*/
protected boolean visitIntersectionSupertype(
AnnotatedTypeMirror subtype, AnnotatedIntersectionType supertype) {
if (isSubtypeVisitHistory.contains(subtype, supertype, currentTop)) {
return true;
}
boolean result = true;
for (AnnotatedTypeMirror bound : supertype.getBounds()) {
// Only call isSubtype if the Java type is actually a subtype; otherwise,
// only check primary qualifiers.
if (TypesUtils.isErasedSubtype(
subtype.getUnderlyingType(), bound.getUnderlyingType(), subtype.atypeFactory.types)
&& !isSubtype(subtype, bound, currentTop)) {
result = false;
break;
}
}
isSubtypeVisitHistory.put(subtype, supertype, currentTop, result);
return result;
}
/**
* An intersection is a subtype if one of its bounds is a subtype of {@code supertype}.
*
* @param subtype an intersection type
* @param supertype an annotated type
* @return whether {@code subtype} is a subtype of {@code supertype}
*/
protected boolean visitIntersectionSubtype(
AnnotatedIntersectionType subtype, AnnotatedTypeMirror supertype) {
Types types = checker.getTypeUtils();
// The primary annotations of the bounds should already be the same as the annotations on
// the intersection type.
for (AnnotatedTypeMirror subtypeBound : subtype.getBounds()) {
if (TypesUtils.isErasedSubtype(
subtypeBound.getUnderlyingType(), supertype.getUnderlyingType(), types)
&& isSubtype(subtypeBound, supertype, currentTop)) {
return true;
}
}
return false;
}
/**
* A type variable is a supertype if its lower bound is above subtype.
*
* @param subtype a type that might be a subtype
* @param supertype a type that might be a supertype
* @return true if {@code subtype} is a subtype of {@code supertype}
*/
protected boolean visitTypevarSupertype(
AnnotatedTypeMirror subtype, AnnotatedTypeVariable supertype) {
return isSubtypeCaching(subtype, supertype.getLowerBound());
}
/**
* A type variable is a subtype if its upper bounds is below the supertype. Note: When comparing
* two type variables this method and visitTypevarSupertype will combine to isValid the subtypes
* upper bound against the supertypes lower bound.
*/
protected boolean visitTypevarSubtype(
AnnotatedTypeVariable subtype, AnnotatedTypeMirror supertype) {
AnnotatedTypeMirror upperBound = subtype.getUpperBound();
if (TypesUtils.isBoxedPrimitive(upperBound.getUnderlyingType())
&& supertype instanceof AnnotatedPrimitiveType) {
upperBound = supertype.atypeFactory.getUnboxedType((AnnotatedDeclaredType) upperBound);
}
if (supertype.getKind() == TypeKind.DECLARED
&& TypesUtils.getTypeElement(supertype.getUnderlyingType()).getKind()
== ElementKind.INTERFACE) {
// Make sure the upper bound is no wildcard or type variable
while (upperBound.getKind() == TypeKind.TYPEVAR
|| upperBound.getKind() == TypeKind.WILDCARD) {
if (upperBound.getKind() == TypeKind.TYPEVAR) {
upperBound = ((AnnotatedTypeVariable) upperBound).getUpperBound();
}
if (upperBound.getKind() == TypeKind.WILDCARD) {
upperBound = ((AnnotatedWildcardType) upperBound).getExtendsBound();
}
}
// If the supertype is an interface, only compare the primary annotations.
// The actual type argument could implement the interface and the bound of
// the type variable must not implement the interface.
if (upperBound.getKind() == TypeKind.INTERSECTION) {
Types types = checker.getTypeUtils();
for (AnnotatedTypeMirror ub : ((AnnotatedIntersectionType) upperBound).getBounds()) {
if (TypesUtils.isErasedSubtype(
ub.getUnderlyingType(), supertype.getUnderlyingType(), types)
&& isPrimarySubtype(ub, supertype)) {
return true;
}
}
return false;
}
}
return isSubtypeCaching(upperBound, supertype);
}
/** A union type is a subtype if ALL of its alternatives are subtypes of supertype. */
protected boolean visitUnionSubtype(AnnotatedUnionType subtype, AnnotatedTypeMirror supertype) {
return areAllSubtypes(subtype.getAlternatives(), supertype);
}
protected boolean visitWildcardSupertype(
AnnotatedTypeMirror subtype, AnnotatedWildcardType supertype) {
if (supertype.isUninferredTypeArgument()) { // TODO: REMOVE WHEN WE FIX TYPE ARG INFERENCE
// Can't call isSubtype because underlying Java types won't be subtypes.
return supertype.atypeFactory.ignoreUninferredTypeArguments;
}
return isSubtype(subtype, supertype.getSuperBound(), currentTop);
}
protected boolean visitWildcardSubtype(
AnnotatedWildcardType subtype, AnnotatedTypeMirror supertype) {
if (subtype.isUninferredTypeArgument()) {
return subtype.atypeFactory.ignoreUninferredTypeArguments;
}
TypeMirror superTypeMirror = supertype.getUnderlyingType();
if (supertype.getKind() == TypeKind.TYPEVAR) {
TypeVariable atv = (TypeVariable) supertype.getUnderlyingType();
if (TypesUtils.isCaptured(atv)) {
superTypeMirror = TypesUtils.getCapturedWildcard(atv);
}
}
if (superTypeMirror.getKind() == TypeKind.WILDCARD) {
// This can happen at a method invocation where a type variable in the method
// declaration is substituted with a wildcard.
// For example:
// <T> void method(Gen<T> t) {}
// Gen<?> x;
// method(x); // this method is called when checking this method call
// And also when checking lambdas
boolean subtypeHasAnno = subtype.getAnnotationInHierarchy(currentTop) != null;
boolean supertypeHasAnno = supertype.getAnnotationInHierarchy(currentTop) != null;
if (subtypeHasAnno && supertypeHasAnno) {
// if both have primary annotations then just check the primary annotations
// as the bounds are the same
return isPrimarySubtype(subtype, supertype);
} else if (!subtypeHasAnno && !supertypeHasAnno && areEqualInHierarchy(subtype, supertype)) {
// TODO: wildcard capture conversion
// Two unannotated uses of wildcard types are the same type
return true;
}
}
if (TypesUtils.isErasedSubtype(
subtype.getExtendsBound().getUnderlyingType(),
supertype.getUnderlyingType(),
subtype.atypeFactory.types)) {
return isSubtype(subtype.getExtendsBound(), supertype, currentTop);
}
// TODO: subtype is a wildcard that should have been captured, so just check the primary
// annotations.
AnnotationMirror subAnno = subtype.getEffectiveAnnotationInHierarchy(currentTop);
AnnotationMirror superAnno = supertype.getAnnotationInHierarchy(currentTop);
return qualifierHierarchy.isSubtype(subAnno, superAnno);
}
}