package checkers.types; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.type.TypeKind; import checkers.types.AnnotatedTypeMirror.*; import checkers.util.AnnotationUtils; /** * Class to test {@link AnnotatedTypeMirror} subtype relationships. * <p> * * This implementation uses the regular Java subtyping rules. * More specifically, let * <: be the subtyping relationship, * {@code qual(A) = qualifier on A}, and * {@code |A| = type without qualifier}: * * <ol> * * <li>A <: B iff |A| <: |B| && qual(A) <: qual(B) * * <li>A[] <: B[] iff A <: B and qual(A[]) <: qual(B[]) * * <li>A<A1, ..., An> <: B<B1, ..., Bn> * if A <: B && A1 = B1 && ... && An = Bn * * </ol> * * Subclasses may override this behavior. * * <p> * * Note that the array subtyping rules depends on a runtime check to * guarantee the soundness of the type system. It is unsafe for static check * purposes. */ public class TypeHierarchy { /** The hierarchy of qualifiers */ private final QualifierHierarchy qualifierHierarchy; /** Prevent infinite loops in cases of recursive type bound */ private final Set<Element> visited; /** * Constructs an instance of {@code TypeHierarchy} for the type system * whose qualifiers represented in qualifierHierarchy. * @param qualifierHierarchy */ public TypeHierarchy(QualifierHierarchy qualifierHierarchy) { this.qualifierHierarchy = qualifierHierarchy; this.visited = new HashSet<Element>(); } /** * Entry point for subtype checking: * Checks whether rhs is a subtype of lhs. * * @return a true iff rhs a subtype of lhs */ public final boolean isSubtype(AnnotatedTypeMirror rhs, AnnotatedTypeMirror lhs) { boolean result = isSubtypeImpl(rhs, lhs); this.visited.clear(); return result; } /** * Checks if the rhs is a subtype of the lhs. * * Private method to be called internally only. * It populates the visited field. */ private final boolean isSubtypeImpl(AnnotatedTypeMirror rhs, AnnotatedTypeMirror lhs) { // If already checked this type (in case of recusive type bound // return true. if not subtype, we wouldn't have gotten here again if (visited.contains(lhs.getElement())) return true; AnnotatedTypeMirror lhsBase = lhs; while (lhsBase.getKind() != rhs.getKind() && (lhsBase.getKind() == TypeKind.WILDCARD || lhsBase.getKind() == TypeKind.TYPEVAR)) { if (lhsBase.getKind() == TypeKind.WILDCARD && rhs.getKind() != TypeKind.WILDCARD) { AnnotatedWildcardType wildcard = (AnnotatedWildcardType)lhsBase; if (wildcard.getSuperBound() != null && isSubtypeImpl(rhs, wildcard.getSuperBound())) return true; lhsBase = ((AnnotatedWildcardType)lhsBase).getExtendsBound(); if (lhsBase == null || !lhsBase.isAnnotated()) return true; visited.add(lhsBase.getElement()); } else if (rhs.getKind() == TypeKind.WILDCARD) { rhs = ((AnnotatedWildcardType)rhs).getExtendsBound(); } else if (lhsBase.getKind() == TypeKind.TYPEVAR && rhs.getKind() != TypeKind.TYPEVAR) { if (!lhsBase.annotations.isEmpty()) return qualifierHierarchy.isSubtype(rhs.getAnnotations(), lhsBase.annotations); return qualifierHierarchy.getBottomQualifier() == null ? rhs.getAnnotations().isEmpty() : rhs.getAnnotations().contains(qualifierHierarchy.getBottomQualifier()); } } if (lhsBase.getKind() == TypeKind.WILDCARD && rhs.getKind() == TypeKind.WILDCARD) { return isSubtype(((AnnotatedWildcardType)rhs).getExtendsBound(), ((AnnotatedWildcardType)lhsBase).getExtendsBound()); } AnnotatedTypeMirror rhsBase = rhs.typeFactory.atypes.asSuper(rhs, lhsBase); // FIXME: the following line should be removed, but erasure code is buggy // related to bug tests/framework/OverrideCrash if (rhsBase == null) rhsBase = rhs; if (!qualifierHierarchy.isSubtype(rhsBase.getAnnotations(), lhsBase.getAnnotations())) return false; if (lhs.getKind() == TypeKind.ARRAY && rhsBase.getKind() == TypeKind.ARRAY) { AnnotatedTypeMirror rhsComponent = ((AnnotatedArrayType)rhsBase).getComponentType(); AnnotatedTypeMirror lhsComponent = ((AnnotatedArrayType)lhsBase).getComponentType(); return isSubtypeAsArrayComponent(rhsComponent, lhsComponent); } else if (lhsBase.getKind() == TypeKind.DECLARED && rhsBase.getKind() == TypeKind.DECLARED) { return isSubtypeTypeArguments((AnnotatedDeclaredType)rhsBase, (AnnotatedDeclaredType)lhsBase); } return true; } /** * Checks that rhs and lhs are subtypes with respect to type arguments only. * Returns true if any of the provided types is not a parameterized type. * * A parameterized type, rhs, is a subtype of another, lhs, only if their * actual type parameters are invariant. * * <p> * * As an implementation detail, this method uses * {@link #isSubtypeAsTypeArgument(AnnotatedTypeMirror, AnnotatedTypeMirror)} * to compare each type argument individually. Subclasses may override * either methods to allow type argument to change covariantly. * * @return true iff the type arguments of lhs and rhs are invariant. */ protected boolean isSubtypeTypeArguments(AnnotatedDeclaredType rhs, AnnotatedDeclaredType lhs) { List<AnnotatedTypeMirror> rhsTypeArgs = rhs.getTypeArguments(); List<AnnotatedTypeMirror> lhsTypeArgs = lhs.getTypeArguments(); if (rhsTypeArgs.isEmpty() || lhsTypeArgs.isEmpty()) return true; assert lhsTypeArgs.size() == rhsTypeArgs.size(); for (int i = 0; i < lhsTypeArgs.size(); ++i) { if (!isSubtypeAsTypeArgument(rhsTypeArgs.get(i), lhsTypeArgs.get(i))) return false; } return true; } /** * Checks that rhs is a subtype of lhs, as actual type arguments. In JLS * terms, rhs needs to be invariant of lhs. * * @return true if the types have the same annotations */ protected boolean isSubtypeAsTypeArgument(AnnotatedTypeMirror rhs, AnnotatedTypeMirror lhs) { if (lhs.getKind() == TypeKind.WILDCARD && rhs.getKind() != TypeKind.WILDCARD) { if (visited.contains(lhs.getElement())) return true; visited.add(lhs.getElement()); lhs = ((AnnotatedWildcardType)lhs).getExtendsBound(); if (lhs == null) return true; return isSubtypeImpl(rhs, lhs); } if (lhs.getKind() == TypeKind.WILDCARD && rhs.getKind() == TypeKind.WILDCARD) { return isSubtype(((AnnotatedWildcardType)rhs).getExtendsBound(), ((AnnotatedWildcardType)lhs).getExtendsBound()); } if (lhs.getKind() == TypeKind.TYPEVAR && rhs.getKind() != TypeKind.TYPEVAR) { if (visited.contains(lhs.getElement())) return true; visited.add(lhs.getElement()); return isSubtype(rhs, ((AnnotatedTypeVariable)lhs).getUpperBound()); } Set<AnnotationMirror> las = lhs.getAnnotations(); Set<AnnotationMirror> ras = rhs.getAnnotations(); if (!AnnotationUtils.areSame(las, ras)) return false; if (lhs.getKind() == TypeKind.DECLARED && rhs.getKind() == TypeKind.DECLARED) return isSubtypeTypeArguments((AnnotatedDeclaredType)rhs, (AnnotatedDeclaredType)lhs); else if (lhs.getKind() == TypeKind.ARRAY && rhs.getKind() == TypeKind.ARRAY) { // arrays components within type arguments are invariants too // List<String[]> is not a subtype of List<Object[]> AnnotatedTypeMirror rhsComponent = ((AnnotatedArrayType)rhs).getComponentType(); AnnotatedTypeMirror lhsComponent = ((AnnotatedArrayType)lhs).getComponentType(); return isSubtypeAsTypeArgument(rhsComponent, lhsComponent); } return true; } /** * Checks that rhs is a subtype of lhs, as an array component type. * (Unfortunately) Java specifies array components are co-variant while * maintaining subtype relations. * * <p> * * This property of arrays makes the code unsafe at run-time. Subclasses * may override this method to enforce a stricter relationship. * * * @return true iff rhs is a subtype of lhs */ protected boolean isSubtypeAsArrayComponent(AnnotatedTypeMirror rhs, AnnotatedTypeMirror lhs) { return isSubtypeImpl(rhs, lhs); } }