blob: 3d8b5253f1e3b016cc8c9b9c1a8c19cfb267ea5d [file] [log] [blame]
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;
}
}