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.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.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.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.ErrorReporter;
import org.checkerframework.javacutil.InternalUtils;
import org.checkerframework.javacutil.TypesUtils;
/**
* 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 VisitHistory
*/
public class StructuralEqualityComparer extends AbstractAtmComboVisitor<Boolean, VisitHistory> {
//TODO: REMOVE THIS OVERRIDE WHEN inferTypeArgs NO LONGER GENERATES INCOMPARABLE TYPES
//TODO: THE PROBLEM IS THIS CLASS SHOULD FAIL WHEN INCOMPARABLE TYPES ARE COMPARED BUT
//TODO: TO CURRENTLY SUPPORT THE BUGGY inferTypeArgs WE FALL BACK TO the RawnessComparer
//TODO: WHICH IS CLOSE TO THE OLD TypeHierarchy behavior
private final DefaultRawnessComparer fallback;
// explain this one
private AnnotationMirror currentTop = null;
public StructuralEqualityComparer() {
this(null);
}
public StructuralEqualityComparer(final DefaultRawnessComparer fallback) {
this.fallback = fallback;
}
@Override
protected Boolean defaultAction(
AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, VisitHistory visitHistory) {
//TODO: REMOVE THIS OVERRIDE WHEN inferTypeArgs NO LONGER GENERATES INCOMPARABLE TYPES
//TODO: THe rawness comparer is close to the old implementation of TypeHierarchy
if (fallback != null) {
return fallback.isValidInHierarchy(type1, type2, currentTop, visitHistory);
}
return super.defaultAction(type1, type2, 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.
*
* @return true if type1 and type2 are equal
*/
public boolean areEqual(final AnnotatedTypeMirror type1, final AnnotatedTypeMirror type2) {
return AtmCombo.accept(type1, type2, new VisitHistory(), this);
}
/**
* The same as areEqual(type1, type2) except now a visited is passed along in order to avoid
* infinite recursion on recursive bounds. This method is only used internally to
* EqualityComparer.
*/
public boolean areEqual(
final AnnotatedTypeMirror type1,
final AnnotatedTypeMirror type2,
final VisitHistory visited) {
if (type1 == null) {
return type2 == null;
}
if (type2 == null) {
return false;
}
return AtmCombo.accept(type1, type2, visited, this);
}
public boolean areEqualInHierarchy(
final AnnotatedTypeMirror type1,
final AnnotatedTypeMirror type2,
final AnnotationMirror top) {
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
return AnnotationUtils.areSame(type1.getAnnotations(), type2.getAnnotations());
}
/**
* 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()
*
* @param visited a store of what types have already been visited
* @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,
final VisitHistory visited) {
if (types1.size() != types2.size()) {
ErrorReporter.errorAbort(
"Mismatching collection sizes:\n"
+ PluginUtil.join(",", types1)
+ "\n"
+ PluginUtil.join(",", types2));
}
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, visited)) {
return false;
}
}
return true;
}
/**
* First check visited to see if type1 and type2 have been compared once already. If so return
* true; otherwise compare them and add them to visited
*/
protected boolean checkOrAreEqual(
final AnnotatedTypeMirror type1,
final AnnotatedTypeMirror type2,
final VisitHistory visited) {
if (visited.contains(type1, type2)) {
return true;
}
final Boolean result = areEqual(type1, type2, visited);
visited.add(type1, type2);
return result;
}
/**
* Called for every combination in which !type1.getClass().equals(type2.getClass()) except for
* Wildcard_Typevar.
*
* @return error message explaining the two types' classes are not the same
*/
@Override
protected String defaultErrorMessage(
AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, VisitHistory visited) {
return "AnnotatedTypeMirror classes aren't equal.\n"
+ "type1 = "
+ type1.getClass().getSimpleName()
+ "( "
+ type1
+ " )\n"
+ "type2 = "
+ type2.getClass().getSimpleName()
+ "( "
+ type2
+ " )\n"
+ "visitHistory = "
+ visited;
}
/**
* 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 VisitHistory visited) {
if (!arePrimeAnnosEqual(type1, type2)) {
return false;
}
return areEqual(type1.getComponentType(), type2.getComponentType(), visited);
}
/**
* 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 VisitHistory visited) {
if (visited.contains(type1, type2)) {
return true;
}
if (!arePrimeAnnosEqual(type1, type2)) {
return false;
}
visited.add(type1, type2);
return visitTypeArgs(type1, type2, visited);
}
/**
* 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,
final VisitHistory visited) {
//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();
//TODO: IN THE ORIGINAL TYPE_HIERARCHY WE ALWAYS RETURN TRUE IF ONE OF THE LISTS IS EMPTY
//TODO: WE SHOULD NEVER GET HERE UNLESS type's declared class and type2's declared class are equal
//TODO: but potentially this would return true if say we compared (Object, List<String>)
if (type1Args.isEmpty() || type2Args.isEmpty()) {
return true;
}
if (type1Args.size() > 0) {
if (!areAllEqual(type1Args, type2Args, visited)) {
return false;
}
}
return true;
}
/**
* //TODO: SHOULD PRIMARY ANNOTATIONS OVERRIDE INDIVIDUAL BOUND ANNOTATIONS? //TODO: IF SO THEN
* WE SHOULD REMOVE THE arePrimeAnnosEqual AND FIX AnnotatedIntersectionType 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 VisitHistory visited) {
if (!arePrimeAnnosEqual(type1, type2)) {
return false;
}
visited.add(type1, type2);
return areAllEqual(type1.directSuperTypes(), type2.directSuperTypes(), visited);
}
/**
* Two null types are equal if:
*
* <ul>
* <li>Their sets of primary annotations are equal
* </ul>
*/
@Override
public Boolean visitNull_Null(
final AnnotatedNullType type1,
final AnnotatedNullType type2,
final VisitHistory visited) {
return arePrimeAnnosEqual(type1, type2);
}
/**
* 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 VisitHistory visited) {
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 VisitHistory visited) {
if (visited.contains(type1, type2)) {
return true;
}
visited.add(type1, type2);
//TODO: Remove this code when capture conversion is implemented
if (InternalUtils.isCaptured(type1.getUnderlyingType())
|| InternalUtils.isCaptured(type2.getUnderlyingType())) {
if (!boundsMatch(type1, type2)) {
return subtypeAndCompare(type1.getUpperBound(), type2.getUpperBound(), visited)
&& subtypeAndCompare(type1.getLowerBound(), type2.getLowerBound(), visited);
}
}
visited.add(type1, type2);
return areEqual(type1.getUpperBound(), type2.getUpperBound(), visited)
&& areEqual(type1.getLowerBound(), type2.getLowerBound(), visited);
}
/**
* 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 VisitHistory visited) {
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.findEffectiveAnnotations(qualifierHierarchy, type1),
AnnotatedTypes.findEffectiveAnnotations(qualifierHierarchy, type2));
} else {
return AnnotationUtils.areSame(
AnnotatedTypes.findEffectiveAnnotationInHierarchy(
qualifierHierarchy, type1, currentTop),
AnnotatedTypes.findEffectiveAnnotationInHierarchy(
qualifierHierarchy, type2, currentTop));
}
}
return areEqual(t1, t2, visited);
}
/** @return true if the underlying types of the bounds for type1 and type2 are equal */
public boolean boundsMatch(
final AnnotatedTypeVariable type1, final AnnotatedTypeVariable type2) {
return type1.getUpperBound()
.getUnderlyingType()
.equals(type2.getUpperBound().getUnderlyingType())
&& type1.getLowerBound()
.getUnderlyingType()
.equals(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 VisitHistory visited) {
if (visited.contains(type1, type2)) {
return true;
}
visited.add(type1, type2);
return areEqual(type1.getExtendsBound(), type2.getExtendsBound(), visited)
&& areEqual(type1.getSuperBound(), type2.getSuperBound(), visited);
}
// 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, VisitHistory visitHistory) {
if (!TypesUtils.isBoxOf(type1.getUnderlyingType(), type2.getUnderlyingType())) {
defaultErrorMessage(type1, type2, visitHistory);
}
return arePrimeAnnosEqual(type1, type2);
}
@Override
public Boolean visitPrimitive_Declared(
AnnotatedPrimitiveType type1, AnnotatedDeclaredType type2, VisitHistory visitHistory) {
if (!TypesUtils.isBoxOf(type2.getUnderlyingType(), type1.getUnderlyingType())) {
defaultErrorMessage(type1, type2, visitHistory);
}
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 tyo 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 VisitHistory visited) {
if (visited.contains(type1, type2)) {
return true;
}
visited.add(type1, type2);
return areEqual(type1.getExtendsBound(), type2.getUpperBound(), visited)
&& areEqual(type1.getSuperBound(), type2.getLowerBound(), visited);
}
}