blob: 113b98d402da27cb05c95aa64b7cbe18679c4066 [file] [log] [blame]
package org.checkerframework.framework.util.typeinference.solver;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.util.Types;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.QualifierHierarchy;
import org.checkerframework.framework.util.AnnotationMirrorMap;
import org.checkerframework.framework.util.AnnotationMirrorSet;
import org.checkerframework.framework.util.typeinference.GlbUtil;
import org.checkerframework.framework.util.typeinference.solver.InferredValue.InferredType;
import org.checkerframework.framework.util.typeinference.solver.TargetConstraints.Subtypes;
/**
* Infers type arguments by using the Greatest Lower Bound computation on the subtype relationships
* in a constraint map.
*/
public class SubtypesSolver {
/**
* Infers type arguments using subtype constraints.
*
* @param remainingTargets targets for which we still need to infer a value
* @param constraints the set of constraints for all targets
* @return a mapping from target to inferred type. Note this class always infers concrete types
* and will not infer that the target is equivalent to another target.
*/
public InferenceResult solveFromSubtypes(
final Set<TypeVariable> remainingTargets,
final ConstraintMap constraints,
final AnnotatedTypeFactory typeFactory) {
return glbSubtypes(remainingTargets, constraints, typeFactory);
}
public InferenceResult glbSubtypes(
final Set<TypeVariable> remainingTargets,
final ConstraintMap constraints,
final AnnotatedTypeFactory typeFactory) {
final InferenceResult inferenceResult = new InferenceResult();
final QualifierHierarchy qualifierHierarchy = typeFactory.getQualifierHierarchy();
final Types types = typeFactory.getProcessingEnv().getTypeUtils();
List<TypeVariable> targetsSubtypesLast = new ArrayList<>(remainingTargets);
// If we have two type variables <A, A extends B> order them A then B
// this is required because we will use the fact that B must be below A
// when determining the glb of B
Collections.sort(
targetsSubtypesLast,
new Comparator<TypeVariable>() {
@Override
public int compare(TypeVariable o1, TypeVariable o2) {
if (types.isSubtype(o1, o2)) {
return 1;
} else if (types.isSubtype(o2, o1)) {
return -1;
}
return 0;
}
});
for (final TypeVariable target : targetsSubtypesLast) {
Subtypes subtypes = constraints.getConstraints(target).subtypes;
if (subtypes.types.isEmpty()) {
continue;
}
propagatePreviousGlbs(subtypes, inferenceResult, subtypes.types);
// if the subtypes size is only 1 then we need not do any GLBing on the underlying types
// but we may have primary annotations that need to be GLBed
AnnotationMirrorMap<AnnotationMirrorSet> primaries = subtypes.primaries;
if (subtypes.types.size() == 1) {
final Map.Entry<AnnotatedTypeMirror, AnnotationMirrorSet> entry =
subtypes.types.entrySet().iterator().next();
AnnotatedTypeMirror supertype = entry.getKey().deepCopy();
for (AnnotationMirror top : entry.getValue()) {
final AnnotationMirrorSet superAnnos = primaries.get(top);
// if it is null we're just going to use the anno already on supertype
if (superAnnos != null) {
final AnnotationMirror supertypeAnno = supertype.getAnnotationInHierarchy(top);
superAnnos.add(supertypeAnno);
}
}
if (!primaries.isEmpty()) {
for (AnnotationMirror top : qualifierHierarchy.getTopAnnotations()) {
final AnnotationMirror glb =
greatestLowerBound(subtypes.primaries.get(top), qualifierHierarchy);
supertype.replaceAnnotation(glb);
}
}
inferenceResult.put(target, new InferredType(supertype));
} else {
// GLB all of the types than combine this with the GLB of primary annotation constraints
final AnnotatedTypeMirror glbType = GlbUtil.glbAll(subtypes.types, typeFactory);
if (glbType != null) {
if (!primaries.isEmpty()) {
for (AnnotationMirror top : qualifierHierarchy.getTopAnnotations()) {
final AnnotationMirror glb =
greatestLowerBound(subtypes.primaries.get(top), qualifierHierarchy);
final AnnotationMirror currentAnno = glbType.getAnnotationInHierarchy(top);
if (currentAnno == null) {
glbType.addAnnotation(glb);
} else if (glb != null) {
glbType.replaceAnnotation(qualifierHierarchy.greatestLowerBound(glb, currentAnno));
}
}
}
inferenceResult.put(target, new InferredType(glbType));
}
}
}
return inferenceResult;
}
/**
* /** If the target corresponding to targetRecord must be a subtype of another target for which
* we have already determined a GLB, add that target's GLB to the list of subtypes to be GLBed for
* this target.
*/
protected static void propagatePreviousGlbs(
final Subtypes targetSubtypes,
InferenceResult solution,
final Map<AnnotatedTypeMirror, AnnotationMirrorSet> subtypesOfTarget) {
for (final Map.Entry<TypeVariable, AnnotationMirrorSet> subtypeTarget :
targetSubtypes.targets.entrySet()) {
final InferredValue subtargetInferredGlb = solution.get(subtypeTarget.getKey());
if (subtargetInferredGlb != null) {
final AnnotatedTypeMirror subtargetGlbType = ((InferredType) subtargetInferredGlb).type;
AnnotationMirrorSet subtargetAnnos = subtypesOfTarget.get(subtargetGlbType);
if (subtargetAnnos != null) {
// there is already an equivalent type in the list of subtypes, just add
// any hierarchies that are not in its list but are in the supertarget's list
subtargetAnnos.addAll(subtypeTarget.getValue());
} else {
subtypesOfTarget.put(subtargetGlbType, subtypeTarget.getValue());
}
}
}
}
/**
* Returns the GLB of annos.
*
* @param annos a set of annotations in the same annotation hierarchy
* @param qualifierHierarchy the qualifier of the annotation hierarchy
* @return the GLB of annos
*/
private final AnnotationMirror greatestLowerBound(
final Iterable<? extends AnnotationMirror> annos, QualifierHierarchy qualifierHierarchy) {
Iterator<? extends AnnotationMirror> annoIter = annos.iterator();
AnnotationMirror glb = annoIter.next();
while (annoIter.hasNext()) {
glb = qualifierHierarchy.greatestLowerBound(glb, annoIter.next());
}
return glb;
}
}