package org.checkerframework.framework.util.typeinference.constraint;
import java.util.List;
import java.util.Set;
import javax.lang.model.type.TypeKind;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
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.AnnotatedUnionType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
import org.checkerframework.framework.type.DefaultTypeHierarchy;
import org.checkerframework.framework.type.visitor.AbstractAtmComboVisitor;
import org.checkerframework.framework.util.AnnotatedTypes;
import org.checkerframework.framework.util.PluginUtil;
import org.checkerframework.javacutil.TypesUtils;
/**
* Takes a single step in reducing a AFConstraint.
*
* <p>The visit method will determine if the given constraint should either:
*
* <ul>
* <li>be discarded - in this case, the visitor just returns
* <li>reduced to a simpler constraint or set of constraints - in this case, the new constraint or
* set of constraints is added to newConstraints
* </ul>
*
* Sprinkled throughout this class are comments of the form:
*
* <pre>{@code
* // If F has the form G<..., Yk-1, ? super U, Yk+1, ...>, where U involves Tj
* }</pre>
*
* These are excerpts from the JLS, if you search for them you will find the corresponding JLS
* description of the case being covered.
*/
abstract class AFReducingVisitor extends AbstractAtmComboVisitor<Void, Set<AFConstraint>> {
public final Class<? extends AFConstraint> reducerType;
public final AnnotatedTypeFactory typeFactory;
public AFReducingVisitor(
final Class<? extends AFConstraint> reducerType,
final AnnotatedTypeFactory typeFactory) {
this.reducerType = reducerType;
this.typeFactory = typeFactory;
}
public abstract AFConstraint makeConstraint(
AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype);
public abstract AFConstraint makeInverseConstraint(
AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype);
public abstract AFConstraint makeEqualityConstraint(
AnnotatedTypeMirror subtype, AnnotatedTypeMirror supertype);
public void addConstraint(
AnnotatedTypeMirror subtype,
AnnotatedTypeMirror supertype,
Set<AFConstraint> constraints) {
constraints.add(makeConstraint(subtype, supertype));
}
public void addInverseConstraint(
AnnotatedTypeMirror subtype,
AnnotatedTypeMirror supertype,
Set<AFConstraint> constraints) {
constraints.add(makeInverseConstraint(subtype, supertype));
}
public void addEqualityConstraint(
AnnotatedTypeMirror subtype,
AnnotatedTypeMirror supertype,
Set<AFConstraint> constraints) {
constraints.add(makeEqualityConstraint(subtype, supertype));
}
/**
* Called when we encounter an AF constraint on a type combination that we did not think is
* possible. This either implies that the type combination is possible, we accidentally created
* an invalid A2F or F2A Constraint, or we called the visit method on two AnnotatedTypeMirrors
* that do not appear together in a constraint.
*/
@Override
protected String defaultErrorMessage(
AnnotatedTypeMirror subtype,
AnnotatedTypeMirror supertype,
Set<AFConstraint> constraints) {
return "Unexpected "
+ reducerType.getSimpleName()
+ " + Combination:\n"
+ "subtype="
+ subtype
+ "\n"
+ "supertype="
+ supertype
+ "\n"
+ "constraints=[\n"
+ PluginUtil.join(", ", constraints)
+ "\n]";
}
//------------------------------------------------------------------------
// Arrays as arguments
// From the JLS
// If F = U[], where the type U involves Tj, then if A is an array type V[], or a type variable with an
// upper bound that is an array type V[], where V is a reference type, this algorithm is applied recursively
// to the constraint V << U or U << V (depending on the constraint type).
@Override
public Void visitArray_Array(
AnnotatedArrayType subtype,
AnnotatedArrayType supertype,
Set<AFConstraint> constraints) {
addConstraint(subtype.getComponentType(), supertype.getComponentType(), constraints);
return null;
}
@Override
public Void visitArray_Declared(
AnnotatedArrayType subtype,
AnnotatedDeclaredType supertype,
Set<AFConstraint> constraints) {
return null;
}
@Override
public Void visitArray_Null(
AnnotatedArrayType subtype,
AnnotatedNullType supertype,
Set<AFConstraint> constraints) {
return null;
}
@Override
public Void visitArray_Wildcard(
AnnotatedArrayType subtype,
AnnotatedWildcardType supertype,
Set<AFConstraint> constraints) {
visitWildcardAsSuperType(subtype, supertype, constraints);
return null;
}
// despite the above the comment at the beginning of the "array as arguments" section, a type variable cannot
// actually have an array type as its upper bound (e.g. <T extends Integer[]> is not allowed).
// so the only cases in which we visitArray_Typevar would be cases in which either:
// 1) Typevar is a type parameter for which we are inferring an argument, in which case the combination is
// already irreducible and we would not pass it to this class
// 2) Typevar is an outer scope type variable, in which case it could NOT reference any of the type parameters
// for which we are inferring arguments and therefore will not lead to any meaningful AFConstraints
// public void visitArray_Typevar
//------------------------------------------------------------------------
// Declared as argument
/**
* I believe there should be only 1 way to have a constraint of this form: {@code visit
* (Array<T>, T [])} At this point, I don't think that's a valid argument for a formal
* parameter. If this occurs it is because of idiosyncrasies with the Checker Framework . We're
* going to skip this case for now.
*/
@Override
public Void visitDeclared_Array(
AnnotatedDeclaredType subtype,
AnnotatedArrayType supertype,
Set<AFConstraint> constraints) {
return null;
}
// From the JLS Spec:
// If F has the form G<..., Yk-1,U, Yk+1, ...>, where U involves Tj
@Override
public Void visitDeclared_Declared(
AnnotatedDeclaredType subtype,
AnnotatedDeclaredType supertype,
Set<AFConstraint> constraints) {
if (subtype.wasRaw() || supertype.wasRaw()) {
return null;
}
if (!TypesUtils.isErasedSubtype(
typeFactory.getContext().getTypeUtils(),
subtype.getUnderlyingType(),
supertype.getUnderlyingType())) {
return null;
}
AnnotatedDeclaredType subAsSuper = DefaultTypeHierarchy.castedAsSuper(subtype, supertype);
final List<AnnotatedTypeMirror> subTypeArgs = subAsSuper.getTypeArguments();
final List<AnnotatedTypeMirror> superTypeArgs = supertype.getTypeArguments();
for (int i = 0; i < subTypeArgs.size(); i++) {
final AnnotatedTypeMirror subTypeArg = subTypeArgs.get(i);
final AnnotatedTypeMirror superTypeArg = superTypeArgs.get(i);
// If F has the form G<..., Yk-1, ? extends U, Yk+1, ...>, where U involves Tj
// If F has the form G<..., Yk-1, ? super U, Yk+1, ...>, where U involves Tj
// Since we always have both bounds in the checker framework we always compare both
if (superTypeArg.getKind() == TypeKind.WILDCARD) {
final AnnotatedWildcardType superWc = (AnnotatedWildcardType) superTypeArg;
if (subTypeArg.getKind() == TypeKind.WILDCARD) {
final AnnotatedWildcardType subWc = (AnnotatedWildcardType) subTypeArg;
addConstraint(subWc.getExtendsBound(), superWc.getExtendsBound(), constraints);
addInverseConstraint(
superWc.getSuperBound(), subWc.getSuperBound(), constraints);
} else {
addConstraint(subTypeArg, superWc.getExtendsBound(), constraints);
addInverseConstraint(superWc.getSuperBound(), subTypeArg, constraints);
}
} else {
// if F has the form G<..., Yk-1, U, Yk+1, ...>, where U is a type expression that involves Tj
addEqualityConstraint(subTypeArg, superTypeArg, constraints);
}
}
return null;
}
@Override
public Void visitDeclared_Intersection(
AnnotatedDeclaredType subtype,
AnnotatedIntersectionType supertype,
Set<AFConstraint> constraints) {
// Note: AnnotatedIntersectionTypes cannot have a type variable as one of the direct parameters but
// a type variable may be the type subtype to an intersection bound <e.g. <T extends Serializable & Iterable<T>>
for (final AnnotatedTypeMirror intersectionBound : supertype.directSuperTypes()) {
if (intersectionBound instanceof AnnotatedDeclaredType
&& ((AnnotatedDeclaredType) intersectionBound).getTypeArguments().size() > 0) {
addConstraint(subtype, supertype, constraints);
}
}
return null;
}
// Remember that NULL types can come from lower bounds
@Override
public Void visitDeclared_Null(
AnnotatedDeclaredType subtype,
AnnotatedNullType supertype,
Set<AFConstraint> constraints) {
return null;
}
// a primitive supertype provides us no information on the type of any type parameters for that method
@Override
public Void visitDeclared_Primitive(
AnnotatedDeclaredType subtype,
AnnotatedPrimitiveType supertype,
Set<AFConstraint> constraints) {
return null;
}
@Override
public Void visitDeclared_Typevar(
AnnotatedDeclaredType subtype,
AnnotatedTypeVariable supertype,
Set<AFConstraint> constraints) {
// Note: We expect the A2F constraints where F == a targeted type supertype to already be removed
// Note: Therefore, supertype should NOT be a target
addConstraint(subtype, supertype, constraints);
return null;
}
@Override
public Void visitDeclared_Union(
AnnotatedDeclaredType subtype,
AnnotatedUnionType supertype,
Set<AFConstraint> constraints) {
return null; //TODO: NOT SUPPORTED AT THE MOMENT
}
@Override
public Void visitDeclared_Wildcard(
AnnotatedDeclaredType subtype,
AnnotatedWildcardType supertype,
Set<AFConstraint> constraints) {
visitWildcardAsSuperType(subtype, supertype, constraints);
return null;
}
//------------------------------------------------------------------------
// Intersection as subtype
@Override
public Void visitIntersection_Declared(
AnnotatedIntersectionType subtype,
AnnotatedDeclaredType supertype,
Set<AFConstraint> constraints) {
// at least one of the intersection bound types must be convertible to the param type
final AnnotatedDeclaredType subtypeAsParam =
DefaultTypeHierarchy.castedAsSuper(subtype, supertype);
if (subtypeAsParam != null && !subtypeAsParam.equals(subtype)) {
addConstraint(subtypeAsParam, supertype, constraints);
}
return null;
}
@Override
public Void visitIntersection_Intersection(
AnnotatedIntersectionType argument,
AnnotatedIntersectionType parameter,
Set<AFConstraint> constraints) {
return null; //TODO: NOT SUPPORTED AT THE MOMENT
}
// provides no information as the AnnotatedNullType cannot refer to a type parameter
@Override
public Void visitIntersection_Null(
AnnotatedIntersectionType argument,
AnnotatedNullType parameter,
Set<AFConstraint> constraints) {
return null;
}
//------------------------------------------------------------------------
// Null as argument
/**
* NULL types only have primary annotations. A type parameter could only appear as a component
* of the parameter type and therefore has no relationship to these primary annotations
*/
@Override
public Void visitNull_Array(
AnnotatedNullType argument,
AnnotatedArrayType parameter,
Set<AFConstraint> constraints) {
return null;
}
/**
* NULL types only have primary annotations. A type parameter could only appear as a component
* of the parameter type and therefore has no relationship to these primary annotations
*/
@Override
public Void visitNull_Declared(
AnnotatedNullType argument,
AnnotatedDeclaredType parameter,
Set<AFConstraint> constraints) {
return null;
}
/**
* TODO: PERHAPS FOR ALL OF THESE WHERE WE COMPARE AGAINST THE LOWER BOUND, WE SHOULD INSTEAD
* COMPARE TODO: against the UPPER_BOUND with the LOWER_BOUND's PRIMARY ANNOTATIONS For captured
* types, the lower bound might be interesting so we compare against the lower bound but for
* most types the constraint added in this method is probably discarded in the next round of
* reduction (especially since we don't implement capture at the moment).
*/
@Override
public Void visitNull_Typevar(
AnnotatedNullType subtype,
AnnotatedTypeVariable supertype,
Set<AFConstraint> constraints) {
// Note: We would expect that parameter is not one of the targets or else it would already be removed
// NOTE: Therefore we compare NULL against its bound
addConstraint(subtype, supertype.getLowerBound(), constraints);
return null;
}
@Override
public Void visitNull_Wildcard(
AnnotatedNullType subtype,
AnnotatedWildcardType supertype,
Set<AFConstraint> constraints) {
// we don't use visitSupertype because Null types won't have interesting components
constraints.add(new A2F(subtype, supertype.getSuperBound()));
return null;
}
@Override
public Void visitNull_Null(
AnnotatedNullType argument,
AnnotatedNullType parameter,
Set<AFConstraint> constraints) {
return null;
}
@Override
public Void visitNull_Union(
AnnotatedNullType argument,
AnnotatedUnionType parameter,
Set<AFConstraint> constraints) {
return null; //TODO: UNIONS ARE NOT YET SUPPORTED
}
// Despite the fact that intersections are not yet supported, this is the right impelementation. NULL types
// only have primary annotations. Since type parameters cannot be a member of the intersection's bounds
//(though they can be component types), we do not need to do anything further
@Override
public Void visitNull_Intersection(
AnnotatedNullType argument,
AnnotatedIntersectionType parameter,
Set<AFConstraint> constraints) {
return null;
}
// Primitive parameter types tell us nothing about the type parameters
@Override
public Void visitNull_Primitive(
AnnotatedNullType argument,
AnnotatedPrimitiveType parameter,
Set<AFConstraint> constraints) {
return null;
}
//------------------------------------------------------------------------
// Primitive as argument
@Override
public Void visitPrimitive_Declared(
AnnotatedPrimitiveType subtype,
AnnotatedDeclaredType supertype,
Set<AFConstraint> constraints) {
// we may be able to eliminate this case, since I believe the corresponding constraint will just be discarded
// as the parameter must be a boxed primitive
addConstraint(typeFactory.getBoxedType(subtype), supertype, constraints);
return null;
}
// Primitive parameter types tell us nothing about the type parameters
@Override
public Void visitPrimitive_Primitive(
AnnotatedPrimitiveType subtype,
AnnotatedPrimitiveType supertype,
Set<AFConstraint> constraints) {
return null;
}
@Override
public Void visitPrimitive_Intersection(
AnnotatedPrimitiveType subtype,
AnnotatedIntersectionType supertype,
Set<AFConstraint> constraints) {
addConstraint(typeFactory.getBoxedType(subtype), supertype, constraints);
return null;
}
//------------------------------------------------------------------------
// Union as argument
@Override
public Void visitUnion_Declared(
AnnotatedUnionType argument,
AnnotatedDeclaredType parameter,
Set<AFConstraint> constraints) {
return null; //TODO: UNIONS ARE NOT CURRENTLY SUPPORTED
}
//------------------------------------------------------------------------
// typevar as argument
// If we've reached this point, the typevar is NOT one of the types we are inferring.
@Override
public Void visitTypevar_Declared(
AnnotatedTypeVariable subtype,
AnnotatedDeclaredType supertype,
Set<AFConstraint> constraints) {
addConstraint(subtype.getUpperBound(), supertype, constraints);
return null;
}
@Override
public Void visitTypevar_Typevar(
AnnotatedTypeVariable subtype,
AnnotatedTypeVariable supertype,
Set<AFConstraint> constraints) {
// if we've reached this point and the two are corresponding type variables, then they are NOT ones that
// may have a type variable we are inferring types for and therefore we can discard this constraint
if (!AnnotatedTypes.areCorrespondingTypeVariables(
typeFactory.getElementUtils(), subtype, supertype)) {
addConstraint(subtype.getUpperBound(), supertype.getLowerBound(), constraints);
}
return null;
}
@Override
public Void visitTypevar_Null(
AnnotatedTypeVariable subtype,
AnnotatedNullType supertype,
Set<AFConstraint> constraints) {
addConstraint(subtype.getUpperBound(), supertype, constraints);
return null;
}
@Override
public Void visitTypevar_Wildcard(
AnnotatedTypeVariable subtype,
AnnotatedWildcardType supertype,
Set<AFConstraint> constraints) {
visitWildcardAsSuperType(subtype, supertype, constraints);
return null;
}
@Override
public Void visitTypevar_Intersection(
AnnotatedTypeVariable subtype,
AnnotatedIntersectionType supertype,
Set<AFConstraint> constraints) {
addConstraint(subtype.getUpperBound(), supertype, constraints);
return null;
}
//------------------------------------------------------------------------
// wildcard as subtype
@Override
public Void visitWildcard_Array(
AnnotatedWildcardType subtype,
AnnotatedArrayType supertype,
Set<AFConstraint> constraints) {
addConstraint(subtype.getExtendsBound(), supertype, constraints);
return null;
}
@Override
public Void visitWildcard_Declared(
AnnotatedWildcardType subtype,
AnnotatedDeclaredType supertype,
Set<AFConstraint> constraints) {
addConstraint(subtype.getExtendsBound(), supertype, constraints);
return null;
}
@Override
public Void visitWildcard_Intersection(
AnnotatedWildcardType subtype,
AnnotatedIntersectionType supertype,
Set<AFConstraint> constraints) {
addConstraint(subtype.getExtendsBound(), supertype, constraints);
return null;
}
@Override
public Void visitWildcard_Primitive(
AnnotatedWildcardType subtype,
AnnotatedPrimitiveType supertype,
Set<AFConstraint> constraints) {
return null;
}
@Override
public Void visitWildcard_Typevar(
AnnotatedWildcardType subtype,
AnnotatedTypeVariable supertype,
Set<AFConstraint> constraints) {
addConstraint(subtype.getExtendsBound(), supertype, constraints);
return null;
}
@Override
public Void visitWildcard_Wildcard(
AnnotatedWildcardType subtype,
AnnotatedWildcardType supertype,
Set<AFConstraint> constraints) {
// since wildcards are handled in visitDeclared_Declared this could only occur if two wildcards
// were passed to type subtype inference at the top level. This can only occur because we do not implement
// capture conversion
visitWildcardAsSuperType(subtype.getExtendsBound(), supertype, constraints);
return null;
}
// should the same logic apply to typevars?
public void visitWildcardAsSuperType(
AnnotatedTypeMirror subtype,
AnnotatedWildcardType supertype,
Set<AFConstraint> constraints) {
// this case occur only when supertype should actually be capture converted (which we don't do)
// because all other wildcard cases would be handled via Declared_Declared
addConstraint(subtype, supertype.getSuperBound(), constraints);
// if type1 is below the superbound then it is necessarily below the extends bound
// BUT the extends bound may have interesting component types (like the array component)
// to which we also want to apply constraints
// e.g. visitArray_Wildcard(@I String[], ? extends @A String[])
// if @I is an annotation we are trying to infer then we still want to infer that @I <: @A
// in fact
addInverseConstraint(subtype, supertype.getExtendsBound(), constraints);
}
}