| package org.checkerframework.framework.type; |
| |
| import java.util.Collection; |
| import java.util.Iterator; |
| import java.util.List; |
| import javax.lang.model.element.AnnotationMirror; |
| import javax.lang.model.type.TypeKind; |
| import javax.lang.model.util.Types; |
| import org.checkerframework.checker.interning.qual.EqualsMethod; |
| 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.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.TypesUtils; |
| import org.plumelib.util.StringsPlume; |
| |
| /** |
| * A visitor used to compare two type mirrors for "structural" equality. Structural equality implies |
| * that, for two objects, all fields are also structurally equal and for primitives their values are |
| * equal. One reason this class is necessary is that at the moment we compare wildcards and type |
| * variables for "equality". This occurs because we do not employ capture conversion. |
| * |
| * <p>See also DefaultTypeHierarchy, and SubtypeVisitHistory |
| */ |
| public class StructuralEqualityComparer extends AbstractAtmComboVisitor<Boolean, Void> { |
| /** History saving the result of previous comparisons. */ |
| protected final StructuralEqualityVisitHistory visitHistory; |
| |
| // See org.checkerframework.framework.type.DefaultTypeHierarchy.currentTop |
| private AnnotationMirror currentTop = null; |
| |
| /** |
| * Create a StructuralEqualityComparer. |
| * |
| * @param typeargVisitHistory history saving the result of previous comparisons |
| */ |
| public StructuralEqualityComparer(StructuralEqualityVisitHistory typeargVisitHistory) { |
| this.visitHistory = typeargVisitHistory; |
| } |
| |
| @Override |
| protected Boolean defaultAction(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, Void p) { |
| if (type1.atypeFactory.ignoreUninferredTypeArguments) { |
| if (type1.getKind() == TypeKind.WILDCARD |
| && ((AnnotatedWildcardType) type1).isUninferredTypeArgument()) { |
| return true; |
| } |
| |
| if (type2.getKind() == TypeKind.WILDCARD |
| && ((AnnotatedWildcardType) type2).isUninferredTypeArgument()) { |
| return true; |
| } |
| } |
| if (type1.getKind() == TypeKind.TYPEVAR || type2.getKind() == TypeKind.TYPEVAR) { |
| // TODO: Handle any remaining typevar combinations correctly. |
| return true; |
| } |
| if (type1.getKind() == TypeKind.NULL || type2.getKind() == TypeKind.NULL) { |
| // If one of the types is the NULL type, compare main qualifiers only. |
| return arePrimeAnnosEqual(type1, type2); |
| } |
| return super.defaultAction(type1, type2, p); |
| } |
| |
| /** |
| * Called for every combination that isn't specifically handled. |
| * |
| * @return error message explaining the two types' classes are not the same |
| */ |
| @Override |
| protected String defaultErrorMessage( |
| AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, Void p) { |
| return StringsPlume.joinLines( |
| "AnnotatedTypeMirrors aren't structurally equal.", |
| " type1 = " + type1.getClass().getSimpleName() + "( " + type1 + " )", |
| " type2 = " + type2.getClass().getSimpleName() + "( " + type2 + " )", |
| " visitHistory = " + visitHistory); |
| } |
| |
| /** |
| * Returns true if type1 and type2 are structurally equivalent. With one exception, |
| * type1.getClass().equals(type2.getClass()) must be true. However, because the Checker Framework |
| * sometimes "infers" Typevars to be Wildcards, we allow the combination Wildcard,Typevar. In this |
| * case, the two types are "equal" if their bounds are. |
| * |
| * @param type1 the first AnnotatedTypeMirror to compare |
| * @param type2 the second AnnotatedTypeMirror to compare |
| * @return true if type1 and type2 are equal |
| */ |
| @EqualsMethod |
| private boolean areEqual(final AnnotatedTypeMirror type1, final AnnotatedTypeMirror type2) { |
| if (type1 == type2) { |
| return true; |
| } |
| assert currentTop != null; |
| if (type1 == null || type2 == null) { |
| return false; |
| } |
| return AtmCombo.accept(type1, type2, null, this); |
| } |
| |
| public boolean areEqualInHierarchy( |
| final AnnotatedTypeMirror type1, |
| final AnnotatedTypeMirror type2, |
| final AnnotationMirror top) { |
| assert top != null; |
| boolean areEqual; |
| AnnotationMirror prevTop = currentTop; |
| currentTop = top; |
| try { |
| areEqual = areEqual(type1, type2); |
| } finally { |
| currentTop = prevTop; |
| } |
| |
| return areEqual; |
| } |
| |
| /** Return true if type1 and type2 have the same set of annotations. */ |
| protected boolean arePrimeAnnosEqual( |
| final AnnotatedTypeMirror type1, final AnnotatedTypeMirror type2) { |
| if (currentTop != null) { |
| return AnnotationUtils.areSame( |
| type1.getAnnotationInHierarchy(currentTop), type2.getAnnotationInHierarchy(currentTop)); |
| } else { |
| throw new BugInCF("currentTop null"); |
| } |
| } |
| |
| /** |
| * Compare each type in types1 and types2 pairwise and return true if they are all equal. This |
| * method throws an exceptions if types1.size() != types2.size() |
| * |
| * @return true if for each pair (t1 = types1.get(i); t2 = types2.get(i)), areEqual(t1,t2) |
| */ |
| protected boolean areAllEqual( |
| final Collection<? extends AnnotatedTypeMirror> types1, |
| final Collection<? extends AnnotatedTypeMirror> types2) { |
| if (types1.size() != types2.size()) { |
| throw new BugInCF( |
| "Mismatching collection sizes:%n types 1: %s (%d)%n types 2: %s (%d)", |
| StringsPlume.join("; ", types1), |
| types1.size(), |
| StringsPlume.join("; ", types2), |
| types2.size()); |
| } |
| |
| final Iterator<? extends AnnotatedTypeMirror> types1Iter = types1.iterator(); |
| final Iterator<? extends AnnotatedTypeMirror> types2Iter = types2.iterator(); |
| while (types1Iter.hasNext()) { |
| final AnnotatedTypeMirror type1 = types1Iter.next(); |
| final AnnotatedTypeMirror type2 = types2Iter.next(); |
| if (!checkOrAreEqual(type1, type2)) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| /** |
| * First check visitHistory to see if type1 and type2 have been compared once already. If so |
| * return true; otherwise compare them and put them in visitHistory. |
| * |
| * @param type1 the first type |
| * @param type2 the second type |
| * @return whether the two types are equal |
| */ |
| protected boolean checkOrAreEqual( |
| final AnnotatedTypeMirror type1, final AnnotatedTypeMirror type2) { |
| Boolean pastResult = visitHistory.get(type1, type2, currentTop); |
| if (pastResult != null) { |
| return pastResult; |
| } |
| |
| final Boolean result = areEqual(type1, type2); |
| visitHistory.put(type1, type2, currentTop, result); |
| return result; |
| } |
| |
| /** |
| * Two arrays are equal if: |
| * |
| * <ol> |
| * <li>Their sets of primary annotations are equal, and |
| * <li>Their component types are equal |
| * </ol> |
| */ |
| @Override |
| public Boolean visitArray_Array( |
| final AnnotatedArrayType type1, final AnnotatedArrayType type2, final Void p) { |
| if (!arePrimeAnnosEqual(type1, type2)) { |
| return false; |
| } |
| |
| return areEqual(type1.getComponentType(), type2.getComponentType()); |
| } |
| |
| /** |
| * Two declared types are equal if: |
| * |
| * <ol> |
| * <li>The types are of the same class/interfaces |
| * <li>Their sets of primary annotations are equal |
| * <li>Their sets of type arguments are equal or one type is raw |
| * </ol> |
| */ |
| @Override |
| public Boolean visitDeclared_Declared( |
| final AnnotatedDeclaredType type1, final AnnotatedDeclaredType type2, final Void p) { |
| Boolean pastResult = visitHistory.get(type1, type2, currentTop); |
| if (pastResult != null) { |
| return pastResult; |
| } |
| |
| // TODO: same class/interface is not enforced. Why? |
| |
| if (!arePrimeAnnosEqual(type1, type2)) { |
| return false; |
| } |
| |
| // Prevent infinite recursion e.g. in Issue1587b |
| visitHistory.put(type1, type2, currentTop, true); |
| |
| boolean result = visitTypeArgs(type1, type2); |
| visitHistory.put(type1, type2, 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 type1, final AnnotatedDeclaredType type2) { |
| |
| // TODO: ANYTHING WITH RAW TYPES? SHOULD WE HANDLE THEM LIKE DefaultTypeHierarchy, i.e. use |
| // ignoreRawTypes |
| final List<? extends AnnotatedTypeMirror> type1Args = type1.getTypeArguments(); |
| final List<? extends AnnotatedTypeMirror> type2Args = type2.getTypeArguments(); |
| |
| if (type1Args.isEmpty() && type2Args.isEmpty()) { |
| return true; |
| } |
| |
| if (type1Args.size() == type2Args.size()) { |
| return areAllEqual(type1Args, type2Args); |
| } else { |
| return true; |
| /* TODO! This should be an error. See framework/tests/all-systems/InitializationVisitor.java |
| * for a failure. |
| throw new BugInCF( |
| "Mismatching type argument sizes:%n type 1: %s (%d)%n type 2: %s (%d)", |
| type1, type1Args.size(), type2, type2Args.size()); |
| return false; |
| */ |
| } |
| } |
| |
| /** |
| * TODO: SHOULD PRIMARY ANNOTATIONS OVERRIDE INDIVIDUAL BOUND ANNOTATIONS? IF SO THEN WE SHOULD |
| * REMOVE THE arePrimeAnnosEqual AND FIX AnnotatedIntersectionType. |
| * |
| * <p>Two intersection types are equal if: |
| * |
| * <ul> |
| * <li>Their sets of primary annotations are equal |
| * <li>Their sets of bounds (the types being intersected) are equal |
| * </ul> |
| */ |
| @Override |
| public Boolean visitIntersection_Intersection( |
| final AnnotatedIntersectionType type1, final AnnotatedIntersectionType type2, final Void p) { |
| if (!arePrimeAnnosEqual(type1, type2)) { |
| return false; |
| } |
| |
| boolean result = areAllEqual(type1.getBounds(), type2.getBounds()); |
| visitHistory.put(type1, type2, currentTop, result); |
| return result; |
| } |
| |
| /** |
| * Two primitive types are equal if: |
| * |
| * <ul> |
| * <li>Their sets of primary annotations are equal |
| * </ul> |
| */ |
| @Override |
| public Boolean visitPrimitive_Primitive( |
| final AnnotatedPrimitiveType type1, final AnnotatedPrimitiveType type2, final Void p) { |
| return arePrimeAnnosEqual(type1, type2); |
| } |
| |
| /** |
| * Two type variables are equal if: |
| * |
| * <ul> |
| * <li>Their bounds are equal |
| * </ul> |
| * |
| * Note: Primary annotations will be taken into account when the bounds are retrieved |
| */ |
| @Override |
| public Boolean visitTypevar_Typevar( |
| final AnnotatedTypeVariable type1, final AnnotatedTypeVariable type2, final Void p) { |
| Boolean pastResult = visitHistory.get(type1, type2, currentTop); |
| if (pastResult != null) { |
| return pastResult; |
| } |
| |
| // TODO: Remove this code when capture conversion is implemented |
| if (TypesUtils.isCaptured(type1.getUnderlyingType()) |
| || TypesUtils.isCaptured(type2.getUnderlyingType())) { |
| if (!boundsMatch(type1, type2)) { |
| Boolean result = |
| subtypeAndCompare(type1.getUpperBound(), type2.getUpperBound()) |
| && subtypeAndCompare(type1.getLowerBound(), type2.getLowerBound()); |
| visitHistory.put(type1, type2, currentTop, result); |
| return result; |
| } |
| } |
| |
| Boolean result = |
| areEqual(type1.getUpperBound(), type2.getUpperBound()) |
| && areEqual(type1.getLowerBound(), type2.getLowerBound()); |
| visitHistory.put(type1, type2, currentTop, result); |
| return result; |
| } |
| |
| /** |
| * A temporary solution until we handle CaptureConversion, subtypeAndCompare handles cases in |
| * which we encounter a captured type being compared against a non-captured type. The captured |
| * type may have type arguments that are subtypes of the other type it is being compared to. In |
| * these cases, we will convert the bounds via this method to the other type and then continue on |
| * with the equality comparison. If neither of the type args can be converted to the other than we |
| * just compare the effective annotations on the two types for equality. |
| */ |
| boolean subtypeAndCompare(final AnnotatedTypeMirror type1, final AnnotatedTypeMirror type2) { |
| final Types types = type1.atypeFactory.types; |
| final AnnotatedTypeMirror t1; |
| final AnnotatedTypeMirror t2; |
| |
| if (type1.getKind() == TypeKind.NULL && type2.getKind() == TypeKind.NULL) { |
| return areEqual(type1, type2); |
| } |
| if (type1.getKind() == TypeKind.NULL || type2.getKind() == TypeKind.NULL) { |
| t1 = type1; |
| t2 = type2; |
| } else if (types.isSubtype(type2.getUnderlyingType(), type1.getUnderlyingType())) { |
| t1 = type1; |
| t2 = AnnotatedTypes.asSuper(type1.atypeFactory, type2, type1); |
| |
| } else if (types.isSubtype(type1.getUnderlyingType(), type2.getUnderlyingType())) { |
| t1 = AnnotatedTypes.asSuper(type1.atypeFactory, type1, type2); |
| t2 = type2; |
| |
| } else { |
| t1 = null; |
| t2 = null; |
| } |
| |
| if (t1 == null || t2 == null) { |
| final QualifierHierarchy qualifierHierarchy = type1.atypeFactory.getQualifierHierarchy(); |
| if (currentTop != null) { |
| return AnnotationUtils.areSame( |
| AnnotatedTypes.findEffectiveAnnotationInHierarchy( |
| qualifierHierarchy, type1, currentTop), |
| AnnotatedTypes.findEffectiveAnnotationInHierarchy( |
| qualifierHierarchy, type2, currentTop)); |
| } else { |
| throw new BugInCF("currentTop null"); |
| } |
| } |
| |
| return areEqual(t1, t2); |
| } |
| |
| /** |
| * Returns true if the underlying types of the bounds for type1 and type2 are equal. |
| * |
| * @return true if the underlying types of the bounds for type1 and type2 are equal |
| */ |
| public boolean boundsMatch(final AnnotatedTypeVariable type1, final AnnotatedTypeVariable type2) { |
| final Types types = type1.atypeFactory.types; |
| |
| return types.isSameType( |
| type1.getUpperBound().getUnderlyingType(), type2.getUpperBound().getUnderlyingType()) |
| && types.isSameType( |
| type1.getLowerBound().getUnderlyingType(), type2.getLowerBound().getUnderlyingType()); |
| } |
| |
| /** |
| * Two wildcards are equal if: |
| * |
| * <ul> |
| * <li>Their bounds are equal |
| * </ul> |
| * |
| * Note: Primary annotations will be taken into account when the bounds are retrieved |
| * |
| * <p>TODO: IDENTIFY TESTS THAT LEAD TO RECURSIVE BOUNDED WILDCARDS, PERHAPS THE RIGHT THING IS TO |
| * MOVE THE CODE THAT IDENTIFIES REFERENCES TO THE SAME WILDCARD TYPE HERE. WOULD WE EVER WANT TO |
| * HAVE A REFERENCE TO THE SAME WILDCARD WITH DIFFERENT ANNOTATIONS? |
| */ |
| @Override |
| public Boolean visitWildcard_Wildcard( |
| final AnnotatedWildcardType type1, final AnnotatedWildcardType type2, final Void p) { |
| Boolean pastResult = visitHistory.get(type1, type2, currentTop); |
| if (pastResult != null) { |
| return pastResult; |
| } |
| |
| if (type1.atypeFactory.ignoreUninferredTypeArguments |
| && (type1.isUninferredTypeArgument() || type2.isUninferredTypeArgument())) { |
| return true; |
| } |
| |
| Boolean result = |
| areEqual(type1.getExtendsBound(), type2.getExtendsBound()) |
| && areEqual(type1.getSuperBound(), type2.getSuperBound()); |
| visitHistory.put(type1, type2, currentTop, result); |
| return result; |
| } |
| |
| // since we don't do a boxing conversion between primitive and declared types in some cases |
| // we must compare primitives with their boxed counterparts |
| @Override |
| public Boolean visitDeclared_Primitive( |
| AnnotatedDeclaredType type1, AnnotatedPrimitiveType type2, Void p) { |
| if (!TypesUtils.isBoxOf(type1.getUnderlyingType(), type2.getUnderlyingType())) { |
| defaultErrorMessage(type1, type2, p); |
| } |
| |
| return arePrimeAnnosEqual(type1, type2); |
| } |
| |
| @Override |
| public Boolean visitPrimitive_Declared( |
| AnnotatedPrimitiveType type1, AnnotatedDeclaredType type2, Void p) { |
| if (!TypesUtils.isBoxOf(type2.getUnderlyingType(), type1.getUnderlyingType())) { |
| defaultErrorMessage(type1, type2, p); |
| } |
| |
| return arePrimeAnnosEqual(type1, type2); |
| } |
| |
| // The following methods are because we use WILDCARDS instead of TYPEVARS for capture converted |
| // wildcards. |
| // TODO: REMOVE THE METHOD BELOW WHEN CAPTURE CONVERSION IS IMPLEMENTED |
| /** |
| * Since the Checker Framework doesn't engage in capture conversion, and since sometimes type |
| * variables are "inferred" to be wildcards, this method allows the comparison of a wildcard to a |
| * type variable even though they should never truly be equal. |
| * |
| * <p>A wildcard is equal to a type variable if: |
| * |
| * <ul> |
| * <li>The wildcard's bounds are equal to the type variable's bounds |
| * </ul> |
| */ |
| @Override |
| public Boolean visitWildcard_Typevar( |
| final AnnotatedWildcardType type1, final AnnotatedTypeVariable type2, final Void p) { |
| Boolean pastResult = visitHistory.get(type1, type2, currentTop); |
| if (pastResult != null) { |
| return pastResult; |
| } |
| |
| if (type1.atypeFactory.ignoreUninferredTypeArguments && type1.isUninferredTypeArgument()) { |
| return true; |
| } |
| |
| Boolean result = |
| areEqual(type1.getExtendsBound(), type2.getUpperBound()) |
| && areEqual(type1.getSuperBound(), type2.getLowerBound()); |
| |
| visitHistory.put(type1, type2, currentTop, result); |
| return result; |
| } |
| |
| @Override |
| public Boolean visitWildcard_Declared( |
| AnnotatedWildcardType type1, AnnotatedDeclaredType type2, Void p) { |
| if (type1.atypeFactory.ignoreUninferredTypeArguments && type1.isUninferredTypeArgument()) { |
| return true; |
| } |
| // TODO: add proper checks |
| return arePrimeAnnosEqual(type1.getExtendsBound(), type2); |
| } |
| |
| @Override |
| public Boolean visitDeclared_Wildcard( |
| AnnotatedDeclaredType type1, AnnotatedWildcardType type2, Void p) { |
| if (type2.atypeFactory.ignoreUninferredTypeArguments && type2.isUninferredTypeArgument()) { |
| return true; |
| } |
| final QualifierHierarchy qualifierHierarchy = type1.atypeFactory.getQualifierHierarchy(); |
| |
| // TODO: add proper checks |
| // TODO: compare whole types instead of just main modifiers |
| AnnotationMirror q1 = |
| AnnotatedTypes.findEffectiveAnnotationInHierarchy(qualifierHierarchy, type1, currentTop); |
| AnnotationMirror q2 = |
| AnnotatedTypes.findEffectiveAnnotationInHierarchy(qualifierHierarchy, type2, currentTop); |
| |
| Boolean result = qualifierHierarchy.isSubtype(q1, q2); |
| visitHistory.put(type1, type2, currentTop, result); |
| return result; |
| } |
| } |