package org.checkerframework.framework.type; import static org.checkerframework.framework.util.AnnotatedTypes.isDeclarationOfJavaLangEnum; import static org.checkerframework.framework.util.AnnotatedTypes.isEnum; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; 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.lang.model.util.Elements; import javax.lang.model.util.Types; import org.checkerframework.common.basetype.BaseTypeChecker; 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.type.visitor.VisitHistory; import org.checkerframework.framework.util.AnnotatedTypes; import org.checkerframework.framework.util.AtmCombo; import org.checkerframework.framework.util.PluginUtil; import org.checkerframework.framework.util.TypeArgumentMapper; import org.checkerframework.javacutil.ErrorReporter; import org.checkerframework.javacutil.InternalUtils; import org.checkerframework.javacutil.Pair; 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 section 4.10, "Subtyping": * http://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.10 * * <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. It does not make * sense to call the visit methods on their own. */ public class DefaultTypeHierarchy extends AbstractAtmComboVisitor<Boolean, VisitHistory> implements TypeHierarchy { // used for processingEnvironment when needed protected final BaseTypeChecker checker; protected final QualifierHierarchy qualifierHierarchy; protected final StructuralEqualityComparer equalityComparer; protected final DefaultRawnessComparer rawnessComparer; protected final boolean ignoreRawTypes; protected final boolean invariantArrayComponents; // Some subtypes of DefaultTypeHierarchy will allow covariant type arguments in certain // cases. This field identifies those cases. See isContainedBy. protected final boolean covariantTypeArgs; //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 leadd ot 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. protected AnnotationMirror currentTop; /** * Whether to ignore uninferred type arguments. This is a temporary flag to work around Issue * 979. */ protected final boolean ignoreUninferredTypeArguments; public DefaultTypeHierarchy( final BaseTypeChecker checker, final QualifierHierarchy qualifierHierarchy, boolean ignoreRawTypes, boolean invariantArrayComponents) { this(checker, qualifierHierarchy, ignoreRawTypes, invariantArrayComponents, false); } public DefaultRawnessComparer createRawnessComparer() { return new DefaultRawnessComparer(this); } public StructuralEqualityComparer createEqualityComparer() { return new StructuralEqualityComparer(rawnessComparer); } public DefaultTypeHierarchy( final BaseTypeChecker checker, final QualifierHierarchy qualifierHierarchy, boolean ignoreRawTypes, boolean invariantArrayComponents, boolean covariantTypeArgs) { this.checker = checker; this.qualifierHierarchy = qualifierHierarchy; this.rawnessComparer = createRawnessComparer(); this.equalityComparer = createEqualityComparer(); this.ignoreRawTypes = ignoreRawTypes; this.invariantArrayComponents = invariantArrayComponents; this.covariantTypeArgs = covariantTypeArgs; ignoreUninferredTypeArguments = !checker.hasOption("conservativeUninferredTypeArguments"); } /** * Returns true if subtype {@literal <:} supertype * * @param subtype expected subtype * @param supertype expected supertype * @return true if subtype is actually a subtype of supertype */ @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 subtype {@literal <:} supertype * * @param subtype expected subtype * @param supertype expected supertype * @param top the hierarchy for which we want to make a comparison * @return true if subtype is actually a subtype of supertype */ @Override public boolean isSubtype( final AnnotatedTypeMirror subtype, final AnnotatedTypeMirror supertype, final AnnotationMirror top) { currentTop = top; return isSubtype(subtype, supertype, new VisitHistory()); } /** * Calls is subtype pair-wise on the elements of the subtypes/supertypes Iterable. * * @return true if for each pair, the subtype element is a subtype of the supertype element. An * exception will be thrown if the iterables are of different sizes. */ public boolean areSubtypes( final Iterable<? extends AnnotatedTypeMirror> subtypes, final Iterable<? extends AnnotatedTypeMirror> supertypes) { final Iterator<? extends AnnotatedTypeMirror> subtypeIterator = subtypes.iterator(); final Iterator<? extends AnnotatedTypeMirror> supertypesIterator = supertypes.iterator(); while (subtypeIterator.hasNext() && supertypesIterator.hasNext()) { final AnnotatedTypeMirror subtype = subtypeIterator.next(); final AnnotatedTypeMirror supertype = supertypesIterator.next(); if (!isSubtype(subtype, supertype)) { return false; } } if (subtypeIterator.hasNext() || supertypesIterator.hasNext()) { ErrorReporter.errorAbort( "Unbalanced set of type arguments.\n" + " subtype=( " + PluginUtil.join(", ", subtypes) + ")\n" + " supertype=( " + PluginUtil.join(", ", supertypes) + ")"); } return true; } /** @return error message for the case when two types shouldn't be compared */ @Override protected String defaultErrorMessage( final AnnotatedTypeMirror subtype, final AnnotatedTypeMirror supertype, final VisitHistory visited) { return "Incomparable types ( " + subtype + ", " + supertype + ")" + "visitHistory = " + visited; } /** * Returns true if subtype {@literal <:} supertype. The only difference between this and * isSubtype(subtype, supertype) is that this method passes a pre-existing visited * * @param subtype expected subtype * @param supertype expected supertype * @return true if subtype is actually a subtype of supertype */ public boolean isSubtype( final AnnotatedTypeMirror subtype, final AnnotatedTypeMirror supertype, VisitHistory visited) { return AtmCombo.accept(subtype, supertype, visited, this); } /** * Compare the primary annotations of subtype and supertype. Neither type can be missing * 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) { return isPrimarySubtype(subtype, supertype, false); } /** * Compare the primary annotations of subtype and supertype. * * @param annosCanBeEmtpy indicates that annotations may be missing from the typemirror * @return true if the primary annotation on subtype {@literal <:} primary annotation on * supertype for the current top or both annotations are null. False is returned if one * annotation is null and the other is not. */ protected boolean isPrimarySubtype( AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype, boolean annosCanBeEmtpy) { final AnnotationMirror subtypeAnno = subtype.getAnnotationInHierarchy(currentTop); final AnnotationMirror supertypeAnno = supertype.getAnnotationInHierarchy(currentTop); return isAnnoSubtype(subtypeAnno, supertypeAnno, annosCanBeEmtpy); } /** * Compare the primary annotations of subtype and supertype. * * @param subtypeAnno annotation we expect to be a subtype * @param supertypeAnno annotation we expect to be a supertype of subtype * @param annosCanBeEmtpy indicates that annotations may be missing from the typemirror * @return true if subtype {@literal <:} supertype or both annotations are null. False is * returned if one annotation is null and the other is not. */ protected boolean isAnnoSubtype( AnnotationMirror subtypeAnno, AnnotationMirror supertypeAnno, boolean annosCanBeEmtpy) { if (annosCanBeEmtpy && subtypeAnno == null && supertypeAnno == null) { return true; } return qualifierHierarchy.isSubtype(subtypeAnno, supertypeAnno); } /** * Checks to see if subtype is bottom (if a bottom exists) If there is no explicit bottom then * false is returned * * @param subtype type to isValid against bottom * @return true if subtype's primary annotation is bottom */ protected boolean isBottom(final AnnotatedTypeMirror subtype) { final AnnotationMirror bottom = qualifierHierarchy.getBottomAnnotation(currentTop); if (bottom == null) { return false; // can't be below infinitely sized hierarchy } switch (subtype.getKind()) { case TYPEVAR: return isBottom(((AnnotatedTypeVariable) subtype).getUpperBound()); case WILDCARD: final AnnotatedWildcardType subtypeWc = (AnnotatedWildcardType) subtype; return isBottom(subtypeWc); //TODO: DO ANYTHING SPECIAL FOR INTERSECTIONS OR UNIONS? //TODO: ENUMERATE THE VALID CASES? default: final AnnotationMirror subtypeAnno = subtype.getAnnotationInHierarchy(currentTop); return isAnnoSubtype(subtypeAnno, bottom, false); } } /** * Check and subtype first determines if the subtype/supertype combination has already been * visited. If so, it returns true, otherwise add the subtype/supertype combination and then * make a subtype check */ protected boolean checkAndSubtype( final AnnotatedTypeMirror subtype, final AnnotatedTypeMirror supertype, VisitHistory visited) { if (visited.contains(subtype, supertype)) { return true; } visited.add(subtype, supertype); return isSubtype(subtype, supertype, visited); } protected boolean isSubtypeOfAll( final AnnotatedTypeMirror subtype, final Iterable<? extends AnnotatedTypeMirror> supertypes, final VisitHistory visited) { for (final AnnotatedTypeMirror supertype : supertypes) { if (!isSubtype(subtype, supertype, visited)) { return false; } } return true; } protected boolean areAllSubtypes( final Iterable<? extends AnnotatedTypeMirror> subtypes, final AnnotatedTypeMirror supertype, final VisitHistory visited) { for (final AnnotatedTypeMirror subtype : subtypes) { if (!isSubtype(subtype, supertype, visited)) { return false; } } return true; } protected boolean areEqual(final AnnotatedTypeMirror type1, final AnnotatedTypeMirror type2) { return equalityComparer.areEqual(type1, type2); } protected boolean areEqualInHierarchy( final AnnotatedTypeMirror type1, final AnnotatedTypeMirror type2, final AnnotationMirror top) { return equalityComparer.areEqualInHierarchy(type1, type2, top); } /** * 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 the JLS section 4.5.1 "Type Arguments of Parameterized Types", * http://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.5.1 * * @param inside the "subtype" type argument * @param outside the "supertype" type argument * @param visited a history of type pairs that have been visited, used to halt on recursive * bounds * @param canBeCovariant whether or not type arguments are allowed to be covariant * @return true if inside is contained by outside OR, if canBeCovariant == true, inside is a * subtype of outside */ protected boolean isContainedBy( final AnnotatedTypeMirror inside, final AnnotatedTypeMirror outside, VisitHistory visited, boolean canBeCovariant) { if (canBeCovariant && isSubtype(inside, outside, visited)) { return true; } if (ignoreUninferredTypeArguments && inside.getKind() == TypeKind.WILDCARD) { final AnnotatedWildcardType insideWc = (AnnotatedWildcardType) inside; if (insideWc.isUninferredTypeArgument()) { return true; } } if (outside.getKind() == TypeKind.WILDCARD) { final AnnotatedWildcardType outsideWc = (AnnotatedWildcardType) outside; if (!checkAndSubtype(outsideWc.getSuperBound(), inside, visited)) { return false; } AnnotatedTypeMirror outsideWcUB = outsideWc.getExtendsBound(); if (inside.getKind() == TypeKind.WILDCARD) { outsideWcUB = checker.getTypeFactory() .widenToUpperBound(outsideWcUB, (AnnotatedWildcardType) inside); } while (outsideWcUB.getKind() == TypeKind.WILDCARD) { outsideWcUB = ((AnnotatedWildcardType) outsideWcUB).getExtendsBound(); } AnnotatedTypeMirror castedInside = castedAsSuper(inside, outsideWcUB); return checkAndSubtype(castedInside, outsideWcUB, visited); } else { //TODO: IF WE NEED TO COMPARE A WILDCARD TO A CAPTURE OF A WILDCARD WE FAIL IN ARE_EQUAL -> DO CAPTURE CONVERSION return areEqualInHierarchy(inside, outside, currentTop); } } //------------------------------------------------------------------------ // Arrays as subtypes @Override public Boolean visitArray_Array( AnnotatedArrayType subtype, AnnotatedArrayType supertype, VisitHistory visited) { return isPrimarySubtype(subtype, supertype) && (invariantArrayComponents ? areEqualInHierarchy( subtype.getComponentType(), supertype.getComponentType(), currentTop) : isSubtype( subtype.getComponentType(), supertype.getComponentType(), visited)); } @Override public Boolean visitArray_Declared( AnnotatedArrayType subtype, AnnotatedDeclaredType supertype, VisitHistory visited) { return isPrimarySubtype(subtype, supertype); } @Override public Boolean visitArray_Null( AnnotatedArrayType subtype, AnnotatedNullType supertype, VisitHistory visited) { return isPrimarySubtype(subtype, supertype); } @Override public Boolean visitArray_Intersection( AnnotatedArrayType subtype, AnnotatedIntersectionType supertype, VisitHistory visitHistory) { return isSubtype(castedAsSuper(subtype, supertype), supertype); } @Override public Boolean visitArray_Wildcard( AnnotatedArrayType subtype, AnnotatedWildcardType supertype, VisitHistory visited) { return visitWildcardSupertype(subtype, supertype, visited); } //------------------------------------------------------------------------ // Declared as subtype @Override public Boolean visitDeclared_Array( AnnotatedDeclaredType subtype, AnnotatedArrayType supertype, VisitHistory visited) { return isPrimarySubtype(subtype, supertype); } @Override public Boolean visitDeclared_Declared( AnnotatedDeclaredType subtype, AnnotatedDeclaredType supertype, VisitHistory visited) { AnnotatedDeclaredType subtypeAsSuper = castedAsSuper(subtype, supertype); if (!isPrimarySubtype(subtypeAsSuper, supertype)) { return false; } if (visited.contains(subtypeAsSuper, supertype)) { return true; } visited.add(subtypeAsSuper, supertype); final Boolean result = visitTypeArgs( subtypeAsSuper, supertype, visited, subtype.wasRaw(), supertype.wasRaw()); 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. */ public Boolean visitTypeArgs( final AnnotatedDeclaredType subtype, final AnnotatedDeclaredType supertype, final VisitHistory visited, final boolean subtypeRaw, final boolean supertypeRaw) { final boolean ignoreTypeArgs = ignoreRawTypes && (subtypeRaw || supertypeRaw); if (!ignoreTypeArgs) { final List<? extends AnnotatedTypeMirror> subtypeTypeArgs = subtype.getTypeArguments(); final List<? extends AnnotatedTypeMirror> supertypeTypeArgs = supertype.getTypeArguments(); //TODO: IN THE ORIGINAL TYPE_HIERARCHY WE ALWAYS RETURN TRUE IF ONE OF THE LISTS IS EMPTY //TODO: THIS SEEMS LIKE WE SHOULD ONLY RETURN TRUE HERE IF ignoreRawTypes == TRUE OR IF BOTH ARE EMPTY //TODO: ARE WE MORE STRICT THAN JAVAC OR DO WE WANT TO FOLLOW JAVAC RAW TYPES SEMANTICS? if (subtypeTypeArgs.isEmpty() || supertypeTypeArgs.isEmpty()) { return true; } if (supertypeTypeArgs.size() > 0) { for (int i = 0; i < supertypeTypeArgs.size(); i++) { final AnnotatedTypeMirror superTypeArg = supertypeTypeArgs.get(i); final AnnotatedTypeMirror subTypeArg = subtypeTypeArgs.get(i); if (!compareTypeArgs( subTypeArg, superTypeArg, supertypeRaw, subtypeRaw, visited)) { return false; } } } } return true; } /** * Compare typeArgs is called on a single pair of type args that should share a relationship * subTypeArg {@literal <:} superTypeArg (subtypeArg is contained by superTypeArg). However, if * either type is raw then either (subTypeArg {@literal <:} superTypeArg) or the * rawnessComparer.isValid(superTypeArg, subTypeArg, visited) */ protected boolean compareTypeArgs( AnnotatedTypeMirror subTypeArg, AnnotatedTypeMirror superTypeArg, boolean subtypeRaw, boolean supertypeRaw, VisitHistory visited) { if (subtypeRaw || supertypeRaw) { return rawnessComparer.isValidInHierarchy(subTypeArg, superTypeArg, currentTop, visited) || isContainedBy(subTypeArg, superTypeArg, visited, this.covariantTypeArgs); } else { return isContainedBy(subTypeArg, superTypeArg, visited, this.covariantTypeArgs); } } @Override public Boolean visitDeclared_Intersection( AnnotatedDeclaredType subtype, AnnotatedIntersectionType supertype, VisitHistory visited) { return visitIntersectionSupertype(subtype, supertype, visited); } @Override public Boolean visitDeclared_Null( AnnotatedDeclaredType subtype, AnnotatedNullType supertype, VisitHistory visited) { return isPrimarySubtype(subtype, supertype); } @Override public Boolean visitDeclared_Primitive( AnnotatedDeclaredType subtype, AnnotatedPrimitiveType supertype, VisitHistory visited) { // 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 = castedAsSuper(subtype, supertype); if (subAsSuper == null) { return isPrimarySubtype(subtype, supertype); } return isPrimarySubtype(subAsSuper, supertype); } @Override public Boolean visitDeclared_Typevar( AnnotatedDeclaredType subtype, AnnotatedTypeVariable supertype, VisitHistory visited) { return visitTypevarSupertype(subtype, supertype, visited); } @Override public Boolean visitDeclared_Union( AnnotatedDeclaredType subtype, AnnotatedUnionType supertype, VisitHistory visited) { Types types = checker.getTypeUtils(); for (AnnotatedDeclaredType supertypeAltern : supertype.getAlternatives()) { if (TypesUtils.isErasedSubtype( types, subtype.getUnderlyingType(), supertypeAltern.getUnderlyingType()) && isSubtype(subtype, supertypeAltern)) { return true; } } return false; } @Override public Boolean visitDeclared_Wildcard( AnnotatedDeclaredType subtype, AnnotatedWildcardType supertype, VisitHistory visited) { return visitWildcardSupertype(subtype, supertype, visited); } //------------------------------------------------------------------------ // Intersection as subtype @Override public Boolean visitIntersection_Declared( AnnotatedIntersectionType subtype, AnnotatedDeclaredType supertype, VisitHistory visited) { for (AnnotatedDeclaredType subtypeI : subtype.directSuperTypes()) { Types types = checker.getTypeUtils(); if (TypesUtils.isErasedSubtype( types, subtypeI.getUnderlyingType(), supertype.getUnderlyingType()) && isSubtype(subtypeI, supertype)) { return true; } } return false; } @Override public Boolean visitIntersection_Primitive( AnnotatedIntersectionType subtype, AnnotatedPrimitiveType supertype, VisitHistory visited) { for (AnnotatedDeclaredType subtypeI : subtype.directSuperTypes()) { if (TypesUtils.isBoxedPrimitive(subtypeI.getUnderlyingType()) && isSubtype(subtypeI, supertype)) { return true; } } return false; } @Override public Boolean visitIntersection_Intersection( AnnotatedIntersectionType subtype, AnnotatedIntersectionType supertype, VisitHistory visited) { Types types = checker.getTypeUtils(); for (AnnotatedDeclaredType subtypeI : subtype.directSuperTypes()) { for (AnnotatedDeclaredType supertypeI : supertype.directSuperTypes()) { if (TypesUtils.isErasedSubtype( types, subtypeI.getUnderlyingType(), supertypeI.getUnderlyingType()) && !isSubtype(subtypeI, supertypeI)) { return false; } } } return true; } @Override public Boolean visitIntersection_Null( AnnotatedIntersectionType subtype, AnnotatedNullType supertype, VisitHistory visited) { // this can occur through capture conversion/comparing bounds for (AnnotatedDeclaredType subtypeI : subtype.directSuperTypes()) { if (isPrimarySubtype(subtypeI, supertype)) { return true; } } return false; } @Override public Boolean visitIntersection_Typevar( AnnotatedIntersectionType subtype, AnnotatedTypeVariable supertype, VisitHistory visited) { // this can occur through capture conversion/comparing bounds for (AnnotatedDeclaredType subtypeI : subtype.directSuperTypes()) { Types types = checker.getTypeUtils(); if (TypesUtils.isErasedSubtype( types, subtypeI.getUnderlyingType(), supertype.getUnderlyingType()) && isSubtype(subtypeI, supertype)) { return true; } } return false; } //------------------------------------------------------------------------ // Null as subtype @Override public Boolean visitNull_Array( AnnotatedNullType subtype, AnnotatedArrayType supertype, VisitHistory visited) { return isPrimarySubtype(subtype, supertype); } @Override public Boolean visitNull_Declared( AnnotatedNullType subtype, AnnotatedDeclaredType supertype, VisitHistory visited) { return isPrimarySubtype(subtype, supertype); } @Override public Boolean visitNull_Typevar( AnnotatedNullType subtype, AnnotatedTypeVariable supertype, VisitHistory visited) { return visitTypevarSupertype(subtype, supertype, visited); } @Override public Boolean visitNull_Wildcard( AnnotatedNullType subtype, AnnotatedWildcardType supertype, VisitHistory visited) { return visitWildcardSupertype(subtype, supertype, visited); } @Override public Boolean visitNull_Null( AnnotatedNullType subtype, AnnotatedNullType supertype, VisitHistory visited) { // 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, VisitHistory visited) { for (AnnotatedDeclaredType supertypeAltern : supertype.getAlternatives()) { if (isSubtype(subtype, supertypeAltern)) { return true; } } return false; } @Override public Boolean visitNull_Intersection( AnnotatedNullType subtype, AnnotatedIntersectionType supertype, VisitHistory visited) { return isPrimarySubtype(subtype, supertype); } @Override public Boolean visitNull_Primitive( AnnotatedNullType subtype, AnnotatedPrimitiveType supertype, VisitHistory visited) { return isPrimarySubtype(subtype, supertype); } //------------------------------------------------------------------------ // Primitive as subtype @Override public Boolean visitPrimitive_Declared( AnnotatedPrimitiveType subtype, AnnotatedDeclaredType supertype, VisitHistory visited) { // see comment in visitDeclared_Primitive final AnnotatedDeclaredType subAsSuper = castedAsSuper(subtype, supertype); if (subAsSuper == null) { return isPrimarySubtype(subtype, supertype); } return isPrimarySubtype(subAsSuper, supertype); } @Override public Boolean visitPrimitive_Primitive( AnnotatedPrimitiveType subtype, AnnotatedPrimitiveType supertype, VisitHistory visited) { return isPrimarySubtype(subtype, supertype); } @Override public Boolean visitPrimitive_Intersection( AnnotatedPrimitiveType subtype, AnnotatedIntersectionType supertype, VisitHistory visited) { return visitIntersectionSupertype(subtype, supertype, visited); } @Override public Boolean visitPrimitive_Wildcard( AnnotatedPrimitiveType subtype, AnnotatedWildcardType supertype, VisitHistory visitHistory) { // 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, VisitHistory visited) { return visitUnionSubtype(subtype, supertype, visited); } @Override public Boolean visitUnion_Intersection( AnnotatedUnionType subtype, AnnotatedIntersectionType supertype, VisitHistory visited) { // <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, visited); } @Override public Boolean visitUnion_Union( AnnotatedUnionType subtype, AnnotatedUnionType supertype, VisitHistory visited) { // <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, visited); } //------------------------------------------------------------------------ // typevar as subtype @Override public Boolean visitTypevar_Declared( AnnotatedTypeVariable subtype, AnnotatedDeclaredType supertype, VisitHistory visited) { return visitTypevarSubtype(subtype, supertype, visited); } @Override public Boolean visitTypevar_Intersection( AnnotatedTypeVariable subtype, AnnotatedIntersectionType supertype, VisitHistory visited) { // this can happen when checking type param bounds return visitIntersectionSupertype(subtype, supertype, visited); } @Override public Boolean visitTypevar_Primitive( AnnotatedTypeVariable subtype, AnnotatedPrimitiveType supertype, VisitHistory visited) { return visitTypevarSubtype(subtype, supertype, visited); } @Override public Boolean visitTypevar_Typevar( AnnotatedTypeVariable subtype, AnnotatedTypeVariable supertype, VisitHistory visited) { 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, true); } else if (!subtypeHasAnno && !supertypeHasAnno && areEqualInHierarchy(subtype, supertype, currentTop)) { // 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(), visited); } } if (AnnotatedTypes.areCorrespondingTypeVariables( checker.getProcessingEnvironment().getElementUtils(), subtype, supertype)) { if (areEqualInHierarchy(subtype, supertype, currentTop)) { return true; } } // check that the upper bound of the subtype is below the lower bound of the supertype return visitTypevarSubtype(subtype, supertype, visited); } @Override public Boolean visitTypevar_Null( AnnotatedTypeVariable subtype, AnnotatedNullType supertype, VisitHistory visited) { return visitTypevarSubtype(subtype, supertype, visited); } @Override public Boolean visitTypevar_Wildcard( AnnotatedTypeVariable subtype, AnnotatedWildcardType supertype, VisitHistory visited) { return visitWildcardSupertype(subtype, supertype, visited); } //------------------------------------------------------------------------ // wildcard as subtype @Override public Boolean visitWildcard_Array( AnnotatedWildcardType subtype, AnnotatedArrayType supertype, VisitHistory visited) { return visitWildcardSubtype(subtype, supertype, visited); } @Override public Boolean visitWildcard_Declared( AnnotatedWildcardType subtype, AnnotatedDeclaredType supertype, VisitHistory visited) { return visitWildcardSubtype(subtype, supertype, visited); } @Override public Boolean visitWildcard_Intersection( AnnotatedWildcardType subtype, AnnotatedIntersectionType supertype, VisitHistory visited) { return visitWildcardSubtype(subtype, supertype, visited); } @Override public Boolean visitWildcard_Primitive( AnnotatedWildcardType subtype, AnnotatedPrimitiveType supertype, VisitHistory visited) { return visitWildcardSubtype(subtype, supertype, visited); } @Override public Boolean visitWildcard_Typevar( AnnotatedWildcardType subtype, AnnotatedTypeVariable supertype, VisitHistory visited) { return visitWildcardSubtype(subtype, supertype, visited); } @Override public Boolean visitWildcard_Wildcard( AnnotatedWildcardType subtype, AnnotatedWildcardType supertype, VisitHistory visited) { return visitWildcardSubtype(subtype, supertype, visited); } //------------------------------------------------------------------------ // 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 commmon /** An intersection is a supertype if all of its bounds are a supertype of subtype */ protected boolean visitIntersectionSupertype( AnnotatedTypeMirror subtype, AnnotatedIntersectionType supertype, VisitHistory visited) { if (visited.contains(subtype, supertype)) { return true; } visited.add(subtype, supertype); return isSubtypeOfAll(subtype, supertype.directSuperTypes(), visited); } /** A type variable is a supertype if its lower bound is above subtype. */ protected boolean visitTypevarSupertype( AnnotatedTypeMirror subtype, AnnotatedTypeVariable supertype, VisitHistory visited) { return checkAndSubtype(subtype, supertype.getLowerBound(), visited); } /** * 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, VisitHistory visited) { AnnotatedTypeMirror upperBound = subtype.getUpperBound(); if (TypesUtils.isBoxedPrimitive(upperBound.getUnderlyingType()) && supertype instanceof AnnotatedPrimitiveType) { upperBound = supertype.atypeFactory.getUnboxedType((AnnotatedDeclaredType) upperBound); } return checkAndSubtype(upperBound, supertype, visited); } /** A union type is a subtype if ALL of its alternatives are subtypes of supertype */ protected Boolean visitUnionSubtype( AnnotatedUnionType subtype, AnnotatedTypeMirror supertype, VisitHistory visited) { return areAllSubtypes(subtype.getAlternatives(), supertype, visited); } protected boolean visitWildcardSupertype( AnnotatedTypeMirror subtype, AnnotatedWildcardType supertype, VisitHistory visited) { if (supertype.isUninferredTypeArgument()) { //TODO: REMOVE WHEN WE FIX TYPE ARG INFERENCE return isSubtype(subtype, supertype.getExtendsBound()); } return isSubtype(subtype, supertype.getSuperBound(), visited); } protected boolean visitWildcardSubtype( AnnotatedWildcardType subtype, AnnotatedTypeMirror supertype, VisitHistory visited) { TypeMirror superTypeMirror = supertype.getUnderlyingType(); if (supertype.getKind() == TypeKind.TYPEVAR) { TypeVariable atv = (TypeVariable) supertype.getUnderlyingType(); if (InternalUtils.isCaptured(atv)) { superTypeMirror = InternalUtils.getCapturedWildcard(atv); } } if (subtype.getUnderlyingType() == superTypeMirror) { // 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, true); } else if (!subtypeHasAnno && !supertypeHasAnno && areEqualInHierarchy(subtype, supertype, currentTop)) { // TODO: wildcard capture conversion // Two unannotated uses of reference-equal wildcard types are the same type return true; } } return isSubtype(subtype.getExtendsBound(), supertype, visited); } /** * Calls asSuper and casts the result to the same type as the input supertype * * @param subtype subtype to be transformed to supertype * @param supertype supertype that subtype is transformed to * @param <T> the type of supertype and return type * @return subtype as an instance of supertype */ @SuppressWarnings("unchecked") public static <T extends AnnotatedTypeMirror> T castedAsSuper( final AnnotatedTypeMirror subtype, final T supertype) { final Types types = subtype.atypeFactory.getProcessingEnv().getTypeUtils(); final Elements elements = subtype.atypeFactory.getProcessingEnv().getElementUtils(); if (subtype.getKind() == TypeKind.NULL) { // Make a copy of the supertype so that if supertype is a composite type, the // returned type will be fully annotated. (For example, if sub is @C null and super is // @A List<@B String>, then the returned type is @C List<@B String>.) T copy = (T) supertype.deepCopy(); copy.replaceAnnotations(subtype.getAnnotations()); return copy; } final T asSuperType = AnnotatedTypes.asSuper(subtype.atypeFactory, subtype, supertype); fixUpRawTypes(subtype, asSuperType, supertype, types); // if we have a type for enum MyEnum {...} // When the supertype is the declaration of java.lang.Enum<E>, MyEnum values become // Enum<MyEnum>. Where really, we would like an Enum<E> with the annotations from Enum<MyEnum> // are transferred to Enum<E>. That is, if we have a type: // @1 Enum<@2 MyEnum> // asSuper should return: // @1 Enum<@2 E> if (asSuperType != null && isEnum(asSuperType) && isDeclarationOfJavaLangEnum(types, elements, supertype)) { final AnnotatedDeclaredType resultAtd = ((AnnotatedDeclaredType) supertype).deepCopy(); resultAtd.clearAnnotations(); resultAtd.addAnnotations(asSuperType.getAnnotations()); final AnnotatedDeclaredType asSuperAdt = (AnnotatedDeclaredType) asSuperType; if (resultAtd.getTypeArguments().size() > 0 && asSuperAdt.getTypeArguments().size() > 0) { final AnnotatedTypeMirror sourceTypeArg = asSuperAdt.getTypeArguments().get(0); final AnnotatedTypeMirror resultTypeArg = resultAtd.getTypeArguments().get(0); resultTypeArg.clearAnnotations(); resultTypeArg.addAnnotations(sourceTypeArg.getAnnotations()); return (T) resultAtd; } } return asSuperType; } /** * Some times we create type arguments for types that were raw. When we do an asSuper we lose * these arguments. If in the converted type (i.e. the subtype as super) is missing type * arguments AND those type arguments should come from the original subtype's type arguments * then we copy the original type arguments to the converted type. e.g. We have a type W, that * "wasRaw" {@code ArrayList<? extends Object>} When W is converted to type A, List, using * asSuper it no longer has its type argument. But since the type argument to List should be the * same as that to ArrayList we copy over the type argument of W to A. A becomes {@code List<? * extends Object>} * * @param originalSubtype the subtype before being converted by asSuper * @param asSuperType he subtype after being converted by asSuper * @param supertype the supertype for which asSuperType should have the same underlying type * @param types the types utility */ private static void fixUpRawTypes( final AnnotatedTypeMirror originalSubtype, final AnnotatedTypeMirror asSuperType, final AnnotatedTypeMirror supertype, final Types types) { if (asSuperType != null && asSuperType.getKind() == TypeKind.DECLARED && originalSubtype.getKind() == TypeKind.DECLARED) { final AnnotatedDeclaredType declaredAsSuper = (AnnotatedDeclaredType) asSuperType; final AnnotatedDeclaredType declaredSubtype = (AnnotatedDeclaredType) originalSubtype; if (declaredAsSuper.wasRaw() && declaredAsSuper.getTypeArguments().isEmpty() && !declaredSubtype.getTypeArguments().isEmpty()) { Set<Pair<Integer, Integer>> typeArgMap = TypeArgumentMapper.mapTypeArgumentIndices( (TypeElement) declaredSubtype.getUnderlyingType().asElement(), (TypeElement) declaredAsSuper.getUnderlyingType().asElement(), types); if (typeArgMap.size() == declaredSubtype.getTypeArguments().size()) { List<AnnotatedTypeMirror> newTypeArgs = new ArrayList<AnnotatedTypeMirror>(); List<Pair<Integer, Integer>> orderedByDestination = new ArrayList<>(typeArgMap); Collections.sort( orderedByDestination, new Comparator<Pair<Integer, Integer>>() { @Override public int compare( Pair<Integer, Integer> o1, Pair<Integer, Integer> o2) { return o1.second - o2.second; } }); final List<? extends AnnotatedTypeMirror> subTypeArgs = declaredSubtype.getTypeArguments(); if (typeArgMap.size() == ((AnnotatedDeclaredType) supertype).getTypeArguments().size()) { for (Pair<Integer, Integer> mapping : orderedByDestination) { newTypeArgs.add(subTypeArgs.get(mapping.first).deepCopy()); } } declaredAsSuper.setTypeArguments(newTypeArgs); } } } } }