All Downloads are FREE. Search and download functionalities are using the official Maven repository.

framework.src.org.checkerframework.framework.type.StructuralEqualityComparer Maven / Gradle / Ivy

package org.checkerframework.framework.type;

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;

import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.util.Types;

/**
 * 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.
 *
 * See also DefaultTypeHierarchy, and VisitHistory
 */
public class StructuralEqualityComparer extends AbstractAtmComboVisitor {

    //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 types1,
                                  final Collection types2,
                                  final VisitHistory visited ) {
        if (types1.size() != types2.size()) {
            ErrorReporter.errorAbort(
                "Mismatching collection sizes:\n"
              + PluginUtil.join(",", types1) + "\n"
              + PluginUtil.join(",", types2)
            );
        }

        final Iterator types1Iter = types1.iterator();
        final Iterator 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:
     *   1) Their sets of primary annotations are equal
     *   2) Their component types are equal
     */
    @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:
     *   1) The types are of the same class/interfaces
     *   2) Their sets of primary annotations are equal
     *   3) Their sets of type arguments are equal or one type is raw
     */
    @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 type1Args = type1.getTypeArguments();
        final List 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)
        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:
     *   1) Their sets of primary annotations are equal
     *   2) Their sets of bounds (the types being intersected) are equal
     */
    @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:
     *   1) Their sets of primary annotations are equal
     */
    @Override
    public Boolean visitNull_Null(final AnnotatedNullType type1, final AnnotatedNullType type2,
                                  final VisitHistory visited) {
        return arePrimeAnnosEqual(type1, type2);
    }

    /**
     * Two primitive types are equal if:
     *   1) Their sets of primary annotations are equal
     */
    @Override
    public Boolean visitPrimitive_Primitive(final AnnotatedPrimitiveType type1, final AnnotatedPrimitiveType type2,
                                            final VisitHistory visited) {
        return arePrimeAnnosEqual(type1, type2);
    }

    /**
     * Two type variables are equal if:
     *   1) Their bounds are equal
     *
     * 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 (types.isSubtype(type2.getUnderlyingType(), type1.getUnderlyingType())) {
            t1 = type1;
            t2 = AnnotatedTypes.asSuper(types, type1.atypeFactory, type2, type1);

        } else if (types.isSubtype(type1.getUnderlyingType(), type2.getUnderlyingType())) {
            t1 = AnnotatedTypes.asSuper(types, 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());
    }
    /**
     * TODO: IDENTIFY TESTS THAT LEAD TO RECURSIVE BOUNDED WILDCARDS, PERHAPS THE RIGHT THING IS TO
     * TODO: MOVE THE CODE THAT IDENTIFIES REFERENCES TO THE SAME WILDCARD TYPE HERE.
     * TODO  WOULD WE EVER WANT TO HAVE A REFERENCE TO THE SAME WILDCARD WITH DIFFERENT ANNOTATIONS?
     * Two wildcards are equal if:
     *   1) Their bounds are equal
     *
     * Note:  Primary annotations will be taken into account when the bounds are retrieved
     */
    @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.
     *
     * A wildcard is equal tyo a type variable if:
     *   1) The wildcard's bounds are equal to the type variable's bounds
     */
    @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);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy