blob: 8f33aafa96b1bca2acda8f4aaa61c02863b8fcbd [file] [log] [blame]
package org.checkerframework.framework.util.typeinference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedNullType;
import org.checkerframework.framework.type.QualifierHierarchy;
import org.checkerframework.framework.type.TypeHierarchy;
import org.checkerframework.framework.util.AnnotationMirrorMap;
import org.checkerframework.framework.util.AnnotationMirrorSet;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.TypesUtils;
/** A class used to determine the greatest lower bounds for a set of AnnotatedTypeMirrors. */
public class GlbUtil {
/**
* Note: This method can be improved for wildcards and type variables.
*
* @return the greatest lower bound of typeMirrors. If any of the type mirrors are incomparable,
* use an AnnotatedNullType that will contain the greatest lower bounds of the primary
* annotations of typeMirrors.
*/
public static AnnotatedTypeMirror glbAll(
final Map<AnnotatedTypeMirror, AnnotationMirrorSet> typeMirrors,
final AnnotatedTypeFactory typeFactory) {
final QualifierHierarchy qualifierHierarchy = typeFactory.getQualifierHierarchy();
if (typeMirrors.isEmpty()) {
return null;
}
// dtermine the greatest lower bounds for the primary annotations
AnnotationMirrorMap<AnnotationMirror> glbPrimaries = new AnnotationMirrorMap<>();
for (Map.Entry<AnnotatedTypeMirror, AnnotationMirrorSet> tmEntry : typeMirrors.entrySet()) {
final AnnotationMirrorSet typeAnnoHierarchies = tmEntry.getValue();
final AnnotatedTypeMirror type = tmEntry.getKey();
for (AnnotationMirror top : typeAnnoHierarchies) {
// TODO: When all of the typeMirrors are either wildcards or type variables than the
// greatest lower bound should involve handling the bounds individually rather than
// using the effective annotation. We are doing this for expediency.
final AnnotationMirror typeAnno = type.getEffectiveAnnotationInHierarchy(top);
final AnnotationMirror currentAnno = glbPrimaries.get(top);
if (typeAnno != null && currentAnno != null) {
glbPrimaries.put(top, qualifierHierarchy.greatestLowerBound(currentAnno, typeAnno));
} else if (typeAnno != null) {
glbPrimaries.put(top, typeAnno);
}
}
}
final List<AnnotatedTypeMirror> glbTypes = new ArrayList<>();
// create a copy of all of the types and apply the glb primary annotation
final AnnotationMirrorSet values = new AnnotationMirrorSet(glbPrimaries.values());
for (AnnotatedTypeMirror type : typeMirrors.keySet()) {
if (type.getKind() != TypeKind.TYPEVAR
|| !qualifierHierarchy.isSubtype(type.getEffectiveAnnotations(), values)) {
final AnnotatedTypeMirror copy = type.deepCopy();
copy.replaceAnnotations(values);
glbTypes.add(copy);
} else {
// if the annotations came from the upper bound of this typevar
// we do NOT want to place them as primary annotations (and destroy the
// type vars lower bound)
glbTypes.add(type);
}
}
final TypeHierarchy typeHierarchy = typeFactory.getTypeHierarchy();
// sort placing supertypes first
sortForGlb(glbTypes, typeFactory);
// find the lowest type in the list that is not an AnnotatedNullType
AnnotatedTypeMirror glbType = glbTypes.get(0);
int index = 1;
while (index < glbTypes.size()) {
// avoid using null if possible, since constraints form the lower bound will often have
// NULL types
if (glbType.getKind() != TypeKind.NULL) {
glbType = glbTypes.get(index);
}
index += 1;
}
// if the lowest type is a subtype of all glbTypes then it is the GLB, otherwise there are two
// types in glbTypes that are incomparable and we need to use bottom (AnnotatedNullType)
boolean incomparable = false;
for (final AnnotatedTypeMirror type : glbTypes) {
if (!incomparable
&& type.getKind() != TypeKind.NULL
&& (!TypesUtils.isErasedSubtype(
glbType.getUnderlyingType(),
type.getUnderlyingType(),
typeFactory.getChecker().getTypeUtils())
|| !typeHierarchy.isSubtype(glbType, type))) {
incomparable = true;
}
}
// we had two incomparable types in glbTypes
if (incomparable) {
return createBottom(typeFactory, glbType.getEffectiveAnnotations());
}
return glbType;
}
/** Returns an AnnotatedNullType with the given annotations as primaries. */
private static AnnotatedNullType createBottom(
final AnnotatedTypeFactory typeFactory, final Set<? extends AnnotationMirror> annos) {
return typeFactory.getAnnotatedNullType(annos);
}
/**
* Sort the list of type mirrors, placing supertypes first and subtypes last.
*
* <p>E.g. the list: {@code ArrayList<String>, List<String>, AbstractList<String>} becomes: {@code
* List<String>, AbstractList<String>, ArrayList<String>}
*/
public static void sortForGlb(
final List<? extends AnnotatedTypeMirror> typeMirrors,
final AnnotatedTypeFactory typeFactory) {
final QualifierHierarchy qualifierHierarchy = typeFactory.getQualifierHierarchy();
final Types types = typeFactory.getProcessingEnv().getTypeUtils();
Collections.sort(
typeMirrors,
new Comparator<AnnotatedTypeMirror>() {
@Override
public int compare(AnnotatedTypeMirror type1, AnnotatedTypeMirror type2) {
final TypeMirror underlyingType1 = type1.getUnderlyingType();
final TypeMirror underlyingType2 = type2.getUnderlyingType();
if (types.isSameType(underlyingType1, underlyingType2)) {
return compareAnnotations(qualifierHierarchy, type1, type2);
}
if (types.isSubtype(underlyingType1, underlyingType2)) {
return 1;
}
// if they're incomparable or type2 is a subtype of type1
return -1;
}
private int compareAnnotations(
final QualifierHierarchy qualHierarchy,
final AnnotatedTypeMirror type1,
final AnnotatedTypeMirror type2) {
if (AnnotationUtils.areSame(type1.getAnnotations(), type2.getAnnotations())) {
return 0;
}
if (qualHierarchy.isSubtype(type1.getAnnotations(), type2.getAnnotations())) {
return 1;
} else {
return -1;
}
}
});
}
}