blob: 29ed8a8fb4cd8d94580de95322b5059dfad6283e [file] [log] [blame]
package org.checkerframework.framework.type.poly;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.NewClassTree;
import java.util.ArrayList;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.VariableElement;
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.AnnotatedExecutableType;
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.QualifierHierarchy;
import org.checkerframework.framework.type.visitor.EquivalentAtmComboScanner;
import org.checkerframework.framework.type.visitor.SimpleAnnotatedTypeScanner;
import org.checkerframework.framework.util.AnnotatedTypes;
import org.checkerframework.framework.util.AnnotationMirrorMap;
import org.checkerframework.framework.util.AnnotationMirrorSet;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypesUtils;
/**
* Implements framework support for qualifier polymorphism.
*
* <p>{@link DefaultQualifierPolymorphism} implements the abstract methods in this class. Subclasses
* can alter the way instantiations of polymorphic qualifiers are {@link #combine combined}.
*
* <p>An "instantiation" is a mapping from declaration type to use-site type &mdash; that is, a
* mapping from {@code @Poly*} to concrete qualifiers.
*
* <p>The implementation performs these steps:
*
* <ul>
* <li>the PolyCollector creates an instantiation
* <li>if the instantiation is non-empty: the Replacer does resolution -- that is, it replaces
* each occurrence of {@code @Poly*} by the concrete qualifier it maps to in the instantiation
* <li>if the instantiation is empty, the Completer replaces each {@code @Poly*} by the top
* qualifier
* </ul>
*/
public abstract class AbstractQualifierPolymorphism implements QualifierPolymorphism {
/** Annotated type factory. */
protected final AnnotatedTypeFactory atypeFactory;
/** The qualifier hierarchy to use. */
protected final QualifierHierarchy qualHierarchy;
/**
* The polymorphic qualifiers: mapping from a polymorphic qualifier of {@code qualHierarchy} to
* the top qualifier of that hierarchy.
*/
protected final AnnotationMirrorMap<AnnotationMirror> polyQuals = new AnnotationMirrorMap<>();
/**
* The qualifiers at the top of {@code qualHierarchy}. These are the values in {@code polyQuals}.
*/
protected final AnnotationMirrorSet topQuals;
/** Determines the instantiations for each polymorphic qualifier. */
private PolyCollector collector = new PolyCollector();
/** Resolves each polymorphic qualifier by replacing it with its instantiation. */
private final SimpleAnnotatedTypeScanner<Void, AnnotationMirrorMap<AnnotationMirror>> replacer;
/**
* Completes a type by removing any unresolved polymorphic qualifiers, replacing them with the
* bottom qualifiers.
*/
private final SimpleAnnotatedTypeScanner<Void, Void> completer;
/** Mapping from poly qualifier to its instantiation for types with a qualifier parameter. */
protected final AnnotationMirrorMap<AnnotationMirror> polyInstantiationForQualifierParameter =
new AnnotationMirrorMap<>();
/**
* Creates an {@link AbstractQualifierPolymorphism} instance that uses the given checker for
* querying type qualifiers and the given factory for getting annotated types. Subclasses need to
* add polymorphic qualifiers to {@code this.polyQuals}.
*
* @param env the processing environment
* @param factory the factory for the current checker
*/
protected AbstractQualifierPolymorphism(ProcessingEnvironment env, AnnotatedTypeFactory factory) {
this.atypeFactory = factory;
this.qualHierarchy = factory.getQualifierHierarchy();
this.topQuals = new AnnotationMirrorSet(qualHierarchy.getTopAnnotations());
this.completer =
new SimpleAnnotatedTypeScanner<>(
(type, p) -> {
for (Map.Entry<AnnotationMirror, AnnotationMirror> entry : polyQuals.entrySet()) {
AnnotationMirror poly = entry.getKey();
AnnotationMirror top = entry.getValue();
if (type.hasAnnotation(poly)) {
type.removeAnnotation(poly);
if (type.getKind() != TypeKind.TYPEVAR && type.getKind() != TypeKind.WILDCARD) {
// Do not add qualifiers to type variables and
// wildcards
type.addAnnotation(this.qualHierarchy.getBottomAnnotation(top));
}
}
}
return null;
});
this.replacer =
new SimpleAnnotatedTypeScanner<>(
(type, map) -> {
replace(type, map);
return null;
});
}
/**
* Reset to allow reuse of the same instance. Subclasses should override this method. The
* overriding implementation should clear its additional state and then call the super
* implementation.
*/
protected void reset() {
collector.reset();
replacer.reset();
completer.reset();
polyInstantiationForQualifierParameter.clear();
}
/**
* Resolves polymorphism annotations for the given type.
*
* @param tree the tree associated with the type
* @param type the type to annotate
*/
@Override
public void resolve(MethodInvocationTree tree, AnnotatedExecutableType type) {
if (polyQuals.isEmpty()) {
return;
}
// javac produces enum super calls with zero arguments even though the
// method element requires two.
// See also BaseTypeVisitor.visitMethodInvocation and
// CFGBuilder.CFGTranslationPhaseOne.visitMethodInvocation.
if (TreeUtils.isEnumSuper(tree)) {
return;
}
List<AnnotatedTypeMirror> parameters =
AnnotatedTypes.expandVarArgs(atypeFactory, type, tree.getArguments());
List<AnnotatedTypeMirror> arguments =
AnnotatedTypes.getAnnotatedTypes(atypeFactory, parameters, tree.getArguments());
AnnotationMirrorMap<AnnotationMirror> instantiationMapping =
collector.visit(arguments, parameters);
// For super() and this() method calls, getReceiverType(tree) does not return the correct
// type. So, just skip those. This is consistent with skipping receivers of constructors below.
if (type.getReceiverType() != null
&& !TreeUtils.isSuperConstructorCall(tree)
&& !TreeUtils.isThisConstructorCall(tree)) {
instantiationMapping =
collector.reduce(
instantiationMapping,
collector.visit(atypeFactory.getReceiverType(tree), type.getReceiverType()));
}
if (instantiationMapping != null && !instantiationMapping.isEmpty()) {
replacer.visit(type, instantiationMapping);
} else {
completer.visit(type);
}
reset();
}
@Override
public void resolve(NewClassTree tree, AnnotatedExecutableType type) {
if (polyQuals.isEmpty()) {
return;
}
List<AnnotatedTypeMirror> parameters =
AnnotatedTypes.expandVarArgs(atypeFactory, type, tree.getArguments());
List<AnnotatedTypeMirror> arguments =
AnnotatedTypes.getAnnotatedTypes(atypeFactory, parameters, tree.getArguments());
AnnotationMirrorMap<AnnotationMirror> instantiationMapping =
collector.visit(arguments, parameters);
// TODO: poly on receiver for constructors?
// instantiationMapping = collector.reduce(instantiationMapping,
// collector.visit(factory.getReceiverType(tree), type.getReceiverType()));
AnnotatedTypeMirror newClassType = atypeFactory.fromNewClass(tree);
instantiationMapping =
collector.reduce(
instantiationMapping, mapQualifierToPoly(newClassType, type.getReturnType()));
if (instantiationMapping != null && !instantiationMapping.isEmpty()) {
replacer.visit(type, instantiationMapping);
} else {
completer.visit(type);
}
reset();
}
@Override
public void resolve(VariableElement field, AnnotatedTypeMirror owner, AnnotatedTypeMirror type) {
if (polyQuals.isEmpty()) {
return;
}
AnnotationMirrorMap<AnnotationMirror> matchingMapping = new AnnotationMirrorMap<>();
polyQuals.forEach(
(polyAnnotation, topAnno) -> {
AnnotationMirror annoOnOwner = owner.getAnnotationInHierarchy(topAnno);
if (annoOnOwner != null) {
matchingMapping.put(polyAnnotation, annoOnOwner);
}
});
if (!matchingMapping.isEmpty()) {
replacer.visit(type, matchingMapping);
} else {
completer.visit(type);
}
reset();
}
@Override
public void resolve(
AnnotatedExecutableType functionalInterface, AnnotatedExecutableType memberReference) {
for (AnnotationMirror type : functionalInterface.getReturnType().getAnnotations()) {
if (atypeFactory.getQualifierHierarchy().isPolymorphicQualifier(type)) {
// functional interface has a polymorphic qualifier, so they should not be resolved
// on memberReference.
return;
}
}
AnnotationMirrorMap<AnnotationMirror> instantiationMapping;
List<AnnotatedTypeMirror> parameters = memberReference.getParameterTypes();
List<AnnotatedTypeMirror> args = functionalInterface.getParameterTypes();
if (args.size() == parameters.size() + 1) {
// If the member reference is a reference to an instance method of an arbitrary
// object, then first parameter of the functional interface corresponds to the
// receiver of the member reference.
List<AnnotatedTypeMirror> newParameters = new ArrayList<>(parameters.size() + 1);
newParameters.add(memberReference.getReceiverType());
newParameters.addAll(parameters);
parameters = newParameters;
instantiationMapping = new AnnotationMirrorMap<>();
} else {
if (memberReference.getReceiverType() != null
&& functionalInterface.getReceiverType() != null) {
instantiationMapping =
mapQualifierToPoly(
functionalInterface.getReceiverType(), memberReference.getReceiverType());
} else {
instantiationMapping = new AnnotationMirrorMap<>();
}
}
// Deal with varargs
if (memberReference.isVarArgs() && !functionalInterface.isVarArgs()) {
parameters = AnnotatedTypes.expandVarArgsFromTypes(memberReference, args);
}
instantiationMapping =
collector.reduce(instantiationMapping, collector.visit(args, parameters));
if (instantiationMapping != null && !instantiationMapping.isEmpty()) {
replacer.visit(memberReference, instantiationMapping);
} else {
// TODO: Do we need this (return type?)
completer.visit(memberReference);
}
reset();
}
/**
* If the primary annotation of {@code polyType} is a polymorphic qualifier, then it is mapped to
* the primary annotation of {@code type} and the map is returned. Otherwise, an empty map is
* returned.
*
* @param type type with qualifier to us in the map
* @param polyType type that may have polymorphic qualifiers
* @return a mapping from the polymorphic qualifiers in {@code polyType} to the qualifiers in
* {@code type}
*/
private AnnotationMirrorMap<AnnotationMirror> mapQualifierToPoly(
AnnotatedTypeMirror type, AnnotatedTypeMirror polyType) {
AnnotationMirrorMap<AnnotationMirror> result = new AnnotationMirrorMap<>();
for (Map.Entry<AnnotationMirror, AnnotationMirror> kv : polyQuals.entrySet()) {
AnnotationMirror top = kv.getValue();
AnnotationMirror poly = kv.getKey();
if (polyType.hasAnnotation(poly)) {
AnnotationMirror typeQual = type.getAnnotationInHierarchy(top);
if (typeQual != null) {
if (atypeFactory.hasQualifierParameterInHierarchy(type, top)) {
polyInstantiationForQualifierParameter.put(poly, typeQual);
}
result.put(poly, typeQual);
}
}
}
return result;
}
/**
* Returns annotation that is the combination of the two annotations. The annotations are
* instantiations for {@code polyQual}.
*
* <p>The combination is typically their least upper bound. (It could be the GLB in the case that
* all arguments to a polymorphic method must have the same annotation.)
*
* @param polyQual polymorphic qualifier for which {@code a1} and {@code a2} are instantiations
* @param a1 an annotation that is an instantiation of {@code polyQual}
* @param a2 an annotation that is an instantiation of {@code polyQual}
* @return an annotation that is the combination of the two annotations
*/
protected abstract AnnotationMirror combine(
AnnotationMirror polyQual, AnnotationMirror a1, AnnotationMirror a2);
/**
* Replaces the top-level polymorphic annotations in {@code type} with the instantiations in
* {@code replacements}.
*
* <p>This method is called on all parts of a type.
*
* @param type AnnotatedTypeMirror whose poly annotations are replaced; it is side-effected by
* this method
* @param replacements mapping from polymorphic annotation to instantiation
*/
protected abstract void replace(
AnnotatedTypeMirror type, AnnotationMirrorMap<AnnotationMirror> replacements);
/**
* A helper class that resolves the polymorphic qualifiers with the most restrictive qualifier. It
* returns a mapping from the polymorphic qualifier to the substitution for that qualifier.
*/
private class PolyCollector
extends EquivalentAtmComboScanner<AnnotationMirrorMap<AnnotationMirror>, Void> {
/**
* Set of {@link AnnotatedTypeVariable} or {@link AnnotatedWildcardType} that have been visited.
* Used to prevent infinite recursion on recursive types.
*
* <p>Uses reference equality rather than equals because the visitor may visit two types that
* are structurally equal, but not actually the same. For example, the wildcards in {@code
* Pair<?,?>} may be equal, but they both should be visited.
*/
private final Set<AnnotatedTypeMirror> visitedTypes =
Collections.newSetFromMap(new IdentityHashMap<AnnotatedTypeMirror, Boolean>());
/**
* Returns true if the {@link AnnotatedTypeMirror} has been visited. If it has not, then it is
* added to the list of visited AnnotatedTypeMirrors.
*/
private boolean visited(AnnotatedTypeMirror atm) {
return !visitedTypes.add(atm);
}
@Override
protected AnnotationMirrorMap<AnnotationMirror> scanWithNull(
AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, Void aVoid) {
return new AnnotationMirrorMap<>();
}
@Override
public AnnotationMirrorMap<AnnotationMirror> reduce(
AnnotationMirrorMap<AnnotationMirror> r1, AnnotationMirrorMap<AnnotationMirror> r2) {
if (r1 == null || r1.isEmpty()) {
return r2;
}
if (r2 == null || r2.isEmpty()) {
return r1;
}
AnnotationMirrorMap<AnnotationMirror> res = new AnnotationMirrorMap<>();
// Ensure that all qualifiers from r1 and r2 are visited.
AnnotationMirrorSet r2remain = new AnnotationMirrorSet();
r2remain.addAll(r2.keySet());
for (Map.Entry<AnnotationMirror, AnnotationMirror> entry : r1.entrySet()) {
AnnotationMirror polyQual = entry.getKey();
AnnotationMirror a1Annos = entry.getValue();
AnnotationMirror a2Annos = r2.get(polyQual);
if (a2Annos == null) {
res.put(polyQual, a1Annos);
} else {
res.put(polyQual, combine(polyQual, a1Annos, a2Annos));
}
r2remain.remove(polyQual);
}
for (AnnotationMirror key2 : r2remain) {
res.put(key2, r2.get(key2));
}
return res;
}
/**
* Calls {@link #visit(AnnotatedTypeMirror, AnnotatedTypeMirror)} for each type in {@code
* types}.
*
* @param types AnnotateTypeMirrors used to find instantiations
* @param polyTypes AnnotatedTypeMirrors that may have polymorphic qualifiers
* @return a mapping of polymorphic qualifiers to their instantiations
*/
private AnnotationMirrorMap<AnnotationMirror> visit(
Iterable<? extends AnnotatedTypeMirror> types,
Iterable<? extends AnnotatedTypeMirror> polyTypes) {
AnnotationMirrorMap<AnnotationMirror> result = new AnnotationMirrorMap<>();
Iterator<? extends AnnotatedTypeMirror> itert = types.iterator();
Iterator<? extends AnnotatedTypeMirror> itera = polyTypes.iterator();
while (itert.hasNext() && itera.hasNext()) {
AnnotatedTypeMirror type = itert.next();
AnnotatedTypeMirror actualType = itera.next();
result = reduce(result, visit(type, actualType));
}
if (itert.hasNext()) {
throw new BugInCF(
"PolyCollector.visit: types is longer than polyTypes:%n"
+ " types = %s%n polyTypes = %s%n",
types, polyTypes);
}
if (itera.hasNext()) {
throw new BugInCF(
"PolyCollector.visit: types is shorter than polyTypes:%n"
+ " types = %s%n polyTypes = %s%n",
types, polyTypes);
}
return result;
}
/**
* Creates a mapping of polymorphic qualifiers to their instantiations by visiting each
* composite type in {@code type}.
*
* @param type AnnotateTypeMirror used to find instantiations
* @param polyType AnnotatedTypeMirror that may have polymorphic qualifiers
* @return a mapping of polymorphic qualifiers to their instantiations
*/
private AnnotationMirrorMap<AnnotationMirror> visit(
AnnotatedTypeMirror type, AnnotatedTypeMirror polyType) {
if (type.getKind() == TypeKind.NULL) {
return mapQualifierToPoly(type, polyType);
}
if (type.getKind() == TypeKind.WILDCARD) {
AnnotatedWildcardType wildcardType = (AnnotatedWildcardType) type;
if (wildcardType.getExtendsBound().getKind() == TypeKind.WILDCARD) {
wildcardType = (AnnotatedWildcardType) wildcardType.getExtendsBound();
}
if (wildcardType.isUninferredTypeArgument()) {
return mapQualifierToPoly(wildcardType.getExtendsBound(), polyType);
}
switch (polyType.getKind()) {
case WILDCARD:
AnnotatedTypeMirror asSuper =
AnnotatedTypes.asSuper(atypeFactory, wildcardType, polyType);
return visit(asSuper, polyType, null);
case TYPEVAR:
return mapQualifierToPoly(wildcardType.getExtendsBound(), polyType);
default:
return mapQualifierToPoly(wildcardType.getExtendsBound(), polyType);
}
}
AnnotatedTypeMirror asSuper = AnnotatedTypes.asSuper(atypeFactory, type, polyType);
return visit(asSuper, polyType, null);
}
@Override
protected String defaultErrorMessage(
AnnotatedTypeMirror type1, AnnotatedTypeMirror type2, Void aVoid) {
return String.format(
"AbstractQualifierPolymorphism: Unexpected combination: type1: %s (%s) type2: %s (%s).",
type1, type1.getKind(), type2, type2.getKind());
}
@Override
public AnnotationMirrorMap<AnnotationMirror> visitArray_Array(
AnnotatedArrayType type1, AnnotatedArrayType type2, Void aVoid) {
AnnotationMirrorMap<AnnotationMirror> result = mapQualifierToPoly(type1, type2);
return reduce(result, super.visitArray_Array(type1, type2, aVoid));
}
@Override
public AnnotationMirrorMap<AnnotationMirror> visitDeclared_Declared(
AnnotatedDeclaredType type1, AnnotatedDeclaredType type2, Void aVoid) {
// Don't call super because asSuper has to be called on each type argument.
if (visited(type2)) {
return new AnnotationMirrorMap<>();
}
AnnotationMirrorMap<AnnotationMirror> result = mapQualifierToPoly(type1, type2);
Iterator<AnnotatedTypeMirror> type2Args = type2.getTypeArguments().iterator();
for (AnnotatedTypeMirror type1Arg : type1.getTypeArguments()) {
AnnotatedTypeMirror type2Arg = type2Args.next();
if (TypesUtils.isErasedSubtype(
type1Arg.getUnderlyingType(),
type2Arg.getUnderlyingType(),
atypeFactory.getChecker().getTypeUtils())) {
result = reduce(result, visit(type1Arg, type2Arg));
} // else an unchecked warning was issued by Java, ignore this part of the type.
}
return result;
}
@Override
public AnnotationMirrorMap<AnnotationMirror> visitIntersection_Intersection(
AnnotatedIntersectionType type1, AnnotatedIntersectionType type2, Void aVoid) {
AnnotationMirrorMap<AnnotationMirror> result = mapQualifierToPoly(type1, type2);
return reduce(result, super.visitIntersection_Intersection(type1, type2, aVoid));
}
@Override
public AnnotationMirrorMap<AnnotationMirror> visitNull_Null(
AnnotatedNullType type1, AnnotatedNullType type2, Void aVoid) {
return mapQualifierToPoly(type1, type2);
}
@Override
public AnnotationMirrorMap<AnnotationMirror> visitPrimitive_Primitive(
AnnotatedPrimitiveType type1, AnnotatedPrimitiveType type2, Void aVoid) {
return mapQualifierToPoly(type1, type2);
}
@Override
public AnnotationMirrorMap<AnnotationMirror> visitTypevar_Typevar(
AnnotatedTypeVariable type1, AnnotatedTypeVariable type2, Void aVoid) {
if (visited(type2)) {
return new AnnotationMirrorMap<>();
}
AnnotationMirrorMap<AnnotationMirror> result = mapQualifierToPoly(type1, type2);
return reduce(result, super.visitTypevar_Typevar(type1, type2, aVoid));
}
@Override
public AnnotationMirrorMap<AnnotationMirror> visitUnion_Union(
AnnotatedUnionType type1, AnnotatedUnionType type2, Void aVoid) {
AnnotationMirrorMap<AnnotationMirror> result = mapQualifierToPoly(type1, type2);
return reduce(result, super.visitUnion_Union(type1, type2, aVoid));
}
@Override
public AnnotationMirrorMap<AnnotationMirror> visitWildcard_Wildcard(
AnnotatedWildcardType type1, AnnotatedWildcardType type2, Void aVoid) {
if (visited(type2)) {
return new AnnotationMirrorMap<>();
}
AnnotationMirrorMap<AnnotationMirror> result = mapQualifierToPoly(type1, type2);
return reduce(result, super.visitWildcard_Wildcard(type1, type2, aVoid));
}
/** Resets the state. */
public void reset() {
this.visitedTypes.clear();
this.visited.clear();
}
}
}