framework.src.org.checkerframework.framework.util.QualifierPolymorphism Maven / Gradle / Ivy
Show all versions of checker Show documentation
package org.checkerframework.framework.util;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.Tree;
import org.checkerframework.framework.qual.PolyAll;
import org.checkerframework.framework.qual.PolymorphicQualifier;
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.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.QualifierHierarchy;
import org.checkerframework.framework.type.visitor.AnnotatedTypeScanner;
import org.checkerframework.framework.type.visitor.SimpleAnnotatedTypeVisitor;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.ErrorReporter;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypesUtils;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
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.Element;
import javax.lang.model.element.Name;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
/**
* Implements framework support for type qualifier polymorphism. Checkers that
* wish to use it should add calls to
* {@link #annotate(MethodInvocationTree, AnnotatedTypeMirror.AnnotatedExecutableType)} to the
* {@link AnnotatedTypeFactory#addComputedTypeAnnotations(Tree, AnnotatedTypeMirror)} and
* {@link AnnotatedTypeFactory#addComputedTypeAnnotations(Tree, AnnotatedTypeMirror)}
* methods.
*
*
*
* This implementation currently only supports polymorphism for method
* invocations, for which the return type depends on the unification of the
* parameter/receiver types.
*
* @see PolymorphicQualifier
*/
public class QualifierPolymorphism {
private final Types types;
private final AnnotatedTypeFactory atypeFactory;
private final Completer completer;
/** The polymorphic qualifiers: mapping from the top of a qualifier
* hierarchy to the polymorphic qualifier of that hierarchy.
* Field always non-null but might be an empty mapping.
* The "null" key, if present, always maps to PolyAll.
*/
protected final Map polyQuals;
/** The qualifiers at the top of the qualifier hierarchy. */
protected final Set topQuals;
/** The qualifier hierarchy to use. */
protected final QualifierHierarchy qualhierarchy;
private final AnnotationMirror POLYALL;
/**
* Creates a {@link QualifierPolymorphism} instance that uses the given
* checker for querying type qualifiers and the given factory for getting
* annotated types.
*
* @param env the processing environment
* @param factory the factory for the current checker
*/
public QualifierPolymorphism(ProcessingEnvironment env, AnnotatedTypeFactory factory) {
this.atypeFactory = factory;
this.types = env.getTypeUtils();
Elements elements = env.getElementUtils();
POLYALL = AnnotationUtils.fromClass(elements, PolyAll.class);
this.qualhierarchy = factory.getQualifierHierarchy();
Map polys = new HashMap();
for (AnnotationMirror aam : qualhierarchy.getTypeQualifiers()) {
if (isPolyAll(aam)) {
polys.put(null, aam);
continue;
}
for (AnnotationMirror aa : aam.getAnnotationType().asElement().getAnnotationMirrors() ) {
if (aa.getAnnotationType().toString().equals(PolymorphicQualifier.class.getCanonicalName())) {
Name plval = AnnotationUtils.getElementValueClassName(aa, "value", true);
AnnotationMirror ttreetop;
if (PolymorphicQualifier.class.getCanonicalName().contentEquals(plval)) {
Set tops = qualhierarchy.getTopAnnotations();
if (tops.size() != 1) {
ErrorReporter.errorAbort(
"QualifierPolymorphism: PolymorphicQualifier has to specify type hierarchy, if more than one exist; top types: " +
tops);
}
ttreetop = tops.iterator().next();
} else {
AnnotationMirror ttree = AnnotationUtils.fromName(elements, plval);
ttreetop = qualhierarchy.getTopAnnotation(ttree);
}
if (polys.containsKey(ttreetop)) {
ErrorReporter.errorAbort(
"QualifierPolymorphism: checker has multiple polymorphic qualifiers: " +
polys.get(ttreetop) + " and " + aam);
}
polys.put(ttreetop, aam);
}
}
}
this.polyQuals = polys;
this.topQuals = qualhierarchy.getTopAnnotations();
this.collector = new PolyCollector();
this.completer = new Completer();
}
public static AnnotationMirror getPolymorphicQualifier(AnnotationMirror qual) {
if (qual == null) {
return null;
}
Element qualElt = qual.getAnnotationType().asElement();
for (AnnotationMirror am : qualElt.getAnnotationMirrors()) {
if (am.getAnnotationType().toString().equals(PolymorphicQualifier.class.getCanonicalName())) {
return am;
}
}
return null;
}
public static boolean isPolymorphicQualified(AnnotationMirror qual) {
return getPolymorphicQualifier(qual) != null;
}
public static boolean isPolyAll(AnnotationMirror qual) {
return AnnotationUtils.areSameByClass(qual, PolyAll.class);
}
/**
* Returns null if the qualifier is not polymorphic.
* Returns the (given) top of the type hierarchy, in which it is polymorphic, otherwise.
* The top qualifier is given by the programmer, so must be normalized to ensure its the real top.
*/
public static Class getPolymorphicQualifierTop(Elements elements, AnnotationMirror qual) {
AnnotationMirror poly = getPolymorphicQualifier(qual);
// System.out.println("poly: " + poly + " pq: " + PolymorphicQualifier.class.getCanonicalName());
if (poly == null) {
return null;
}
@SuppressWarnings("unchecked")
Class ret = (Class) AnnotationUtils.getElementValueClass(poly, "value", true);
return ret;
}
/**
* Resolves polymorphism annotations for the given type.
*
* @param tree the tree associated with the type
* @param type the type to annotate
*/
public void annotate(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 requiredArgs = AnnotatedTypes.expandVarArgs(atypeFactory, type, tree.getArguments());
List arguments = AnnotatedTypes.getAnnotatedTypes(atypeFactory, requiredArgs, tree.getArguments());
Map> matchingMapping = collector.visit(arguments, requiredArgs);
// 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.isSuperCall(tree)
&& !TreeUtils.isThisCall(tree)) {
matchingMapping = collector.reduce(matchingMapping,
collector.visit(atypeFactory.getReceiverType(tree), type.getReceiverType()));
}
if (matchingMapping != null && !matchingMapping.isEmpty()) {
replacer.visit(type, matchingMapping);
} else {
completer.visit(type);
}
}
public void annotate(NewClassTree tree, AnnotatedExecutableType type) {
if (polyQuals.isEmpty()) return;
List requiredArgs = AnnotatedTypes.expandVarArgs(atypeFactory, type, tree.getArguments());
List arguments = AnnotatedTypes.getAnnotatedTypes(atypeFactory, requiredArgs, tree.getArguments());
Map> matchingMapping = collector.visit(arguments, requiredArgs);
// TODO: poly on receiver for constructors?
// matchingMapping = collector.reduce(matchingMapping,
// collector.visit(factory.getReceiverType(tree), type.getReceiverType()));
if (matchingMapping != null && !matchingMapping.isEmpty()) {
replacer.visit(type, matchingMapping);
} else {
completer.visit(type);
}
}
public void annotate(AnnotatedExecutableType functionalInterface, AnnotatedExecutableType memberReference) {
for (AnnotationMirror type : functionalInterface.getReturnType().getAnnotations()) {
if (isPolymorphicQualified(type)) {
// functional interface has a polymorphic qualifier, so they should not be resolved on memberReference.
return;
}
}
List args = functionalInterface.getParameterTypes();
List requiredArgs = memberReference.getParameterTypes();
if (args.size() == requiredArgs.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 newRequiredArgs = new ArrayList<>();
newRequiredArgs.add(memberReference.getReceiverType());
newRequiredArgs.addAll(requiredArgs);
requiredArgs = newRequiredArgs;
}
Map> matchingMapping = collector.visit(args, requiredArgs);
if (matchingMapping != null && !matchingMapping.isEmpty()) {
replacer.visit(memberReference, matchingMapping);
} else {
//TODO: Do we need this (return type?)
completer.visit(memberReference);
}
}
private final AnnotatedTypeScanner>> replacer
= new AnnotatedTypeScanner>>() {
@Override
public Void scan(AnnotatedTypeMirror type, Map> matches) {
if (type != null) {
for (Map.Entry> pqentry : matches.entrySet()) {
AnnotationMirror poly = pqentry.getKey();
if (poly != null && type.hasAnnotation(poly)) {
type.removeAnnotation(poly);
Set quals = pqentry.getValue();
type.replaceAnnotations(quals);
}
}
}
return super.scan(type, matches);
}
};
/**
* Completes a type by removing any unresolved polymorphic qualifiers,
* replacing them with the top qualifiers.
*/
class Completer extends AnnotatedTypeScanner {
@Override
protected Void scan(AnnotatedTypeMirror type, Void p) {
if (type != null) {
for (Map.Entry pqentry : polyQuals.entrySet()) {
AnnotationMirror top = pqentry.getKey();
AnnotationMirror poly = pqentry.getValue();
if (type.hasAnnotation(poly)) {
type.removeAnnotation(poly);
if (top == null) {
// poly is PolyAll -> add all tops not explicitly given
type.addMissingAnnotations(topQuals);
} else if (type.getKind() != TypeKind.TYPEVAR &&
type.getKind() != TypeKind.WILDCARD) {
// Do not add the top qualifiers to type variables and wildcards
type.addAnnotation(top);
}
}
}
}
return super.scan(type, p);
}
}
private final PolyCollector collector;
/**
* A Helper class that tries to resolve the polymorhpic qualifiers with
* the most restricted qualifier.
* The mapping is from the polymorhpic qualifier to the substitution for that qualifier,
* which is a set of qualifiers. For most polymorphic qualifiers this will be a singleton set.
* For the @PolyAll qualifier, this might be a set of qualifiers.
*/
private class PolyCollector
extends SimpleAnnotatedTypeVisitor