blob: b886f2017556c76c854bcda64095bc50928207b9 [file] [log] [blame]
package org.checkerframework.checker.nullness;
import com.sun.source.tree.ExpressionTree;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import org.checkerframework.checker.nullness.qual.KeyFor;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.framework.flow.CFAbstractAnalysis;
import org.checkerframework.framework.flow.CFAbstractValue;
import org.checkerframework.javacutil.AnnotationUtils;
/**
* KeyForValue holds additional information about which maps this value is a key for. This extra
* information is required when adding the @KeyFor qualifier to the type is not a refinement of the
* type. For example,
*
* <pre>
* {@code Map<T, Object> map = ...;}
* {@code <T> T method(T param) { }
* if (map.contains(param) {
* {@code @NonNull Object o = map.get(param);}
* return param;
* }
* }
* }</pre>
*
* Inside the if statement, {@code param} is a key for "map". This would normally be represented as
* {@code @KeyFor("map") T}, but this is not a subtype of {@code T}, so the type cannot be refined.
* Instead, the value for {@code param} includes "map" in the list of keyForMaps. This information
* is used in {@link KeyForAnnotatedTypeFactory#isKeyForMap(String, ExpressionTree)}.
*/
public class KeyForValue extends CFAbstractValue<KeyForValue> {
/**
* If the underlying type is a type variable or a wildcard, then this is a set of maps for which
* this value is a key. Otherwise, it's null.
*/
// Cannot be final because lub re-assigns; add a new constructor to do this cleanly?
private @Nullable Set<String> keyForMaps;
/** Create an instance. */
public KeyForValue(
CFAbstractAnalysis<KeyForValue, ?, ?> analysis,
Set<AnnotationMirror> annotations,
TypeMirror underlyingType) {
super(analysis, annotations, underlyingType);
KeyForAnnotatedTypeFactory atypeFactory =
(KeyForAnnotatedTypeFactory) analysis.getTypeFactory();
AnnotationMirror keyfor = atypeFactory.getAnnotationByClass(annotations, KeyFor.class);
if (keyfor != null
&& (underlyingType.getKind() == TypeKind.TYPEVAR
|| underlyingType.getKind() == TypeKind.WILDCARD)) {
List<String> list =
AnnotationUtils.getElementValueArray(
keyfor, atypeFactory.keyForValueElement, String.class);
keyForMaps = new LinkedHashSet<>(list.size());
keyForMaps.addAll(list);
} else {
keyForMaps = null;
}
}
/**
* If the underlying type is a type variable or a wildcard, then this is a set of maps for which
* this value is a key. Otherwise, it's null.
*/
public Set<String> getKeyForMaps() {
return keyForMaps;
}
@Override
public KeyForValue leastUpperBound(KeyForValue other) {
KeyForValue lub = super.leastUpperBound(other);
if (other == null || other.keyForMaps == null || this.keyForMaps == null) {
return lub;
}
// Lub the keyForMaps by intersecting the sets.
lub.keyForMaps = new LinkedHashSet<>(this.keyForMaps.size());
lub.keyForMaps.addAll(this.keyForMaps);
lub.keyForMaps.retainAll(other.keyForMaps);
if (lub.keyForMaps.isEmpty()) {
lub.keyForMaps = null;
}
return lub;
}
@Override
public KeyForValue mostSpecific(KeyForValue other, KeyForValue backup) {
KeyForValue mostSpecific = super.mostSpecific(other, backup);
if (mostSpecific == null) {
if (other == null) {
return this;
}
// mostSpecific is null if the two types are not comparable. This is normally
// because one of this or other is a type variable and annotations is empty, but the
// other annotations are not empty. In this case, copy the keyForMaps and to the
// value with the no annotations and return it as most specific.
if (other.getAnnotations().isEmpty()) {
other.addKeyFor(this.keyForMaps);
return other;
} else if (this.getAnnotations().isEmpty()) {
this.addKeyFor(other.keyForMaps);
return this;
}
return null;
}
mostSpecific.addKeyFor(this.keyForMaps);
if (other != null) {
mostSpecific.addKeyFor(other.keyForMaps);
}
return mostSpecific;
}
private void addKeyFor(Set<String> newKeyForMaps) {
if (newKeyForMaps == null || newKeyForMaps.isEmpty()) {
return;
}
if (keyForMaps == null) {
keyForMaps = new LinkedHashSet<>();
}
keyForMaps.addAll(newKeyForMaps);
}
}