blob: 30bdb02cb0c502f8a074db7e037cf6bc07992020 [file] [log] [blame]
package org.checkerframework.framework.util.typeinference.solver;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeVariable;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.util.typeinference.solver.InferredValue.InferredTarget;
import org.checkerframework.framework.util.typeinference.solver.InferredValue.InferredType;
/**
* Represents the result from inferring type arguments. InferenceResult is a map from: target type
* variable to (inferred type or target).
*/
public class InferenceResult extends LinkedHashMap<TypeVariable, InferredValue> {
private static final long serialVersionUID = 6911459752070485818L;
/**
* Returns the set of targets that still don't have an inferred argument.
*
* @return the set of targets that still don't have an inferred argument
*/
public Set<TypeVariable> getRemainingTargets(
final Set<TypeVariable> allTargets, boolean inferredTypesOnly) {
final LinkedHashSet<TypeVariable> remainingTargets = new LinkedHashSet<>(allTargets);
if (inferredTypesOnly) {
for (TypeVariable target : keySet()) {
if (this.get(target) instanceof InferredType) {
remainingTargets.remove(target);
}
}
} else {
remainingTargets.removeAll(this.keySet());
}
return remainingTargets;
}
/**
* Returns true if we have inferred a concrete type for all targets.
*
* @param targets type variables to check
* @return true if we have inferred a concrete type for all targets
*/
public boolean isComplete(final Set<TypeVariable> targets) {
for (final TypeVariable target : targets) {
final InferredValue inferred = this.get(target);
if (inferred == null || inferred instanceof InferredTarget) {
return false;
} else if (inferred instanceof InferredType
&& ((InferredType) inferred).type.getKind() == TypeKind.NULL) {
// NullType is not a valid type argument, so continue looking for the correct type.
return false;
}
}
return this.keySet().containsAll(targets);
}
/**
* If we had a set of inferred results, (e.g. T1 = T2, T2 = T3, T3 = String) propagate any results
* we have (the above constraints become T1 = String, T2 = String, T3 = String)
*/
public void resolveChainedTargets() {
final Map<TypeVariable, InferredValue> inferredTypes = new LinkedHashMap<>(this.size());
// TODO: we can probably make this a bit more efficient
boolean grew = true;
while (grew) {
grew = false;
for (final Map.Entry<TypeVariable, InferredValue> inferred : this.entrySet()) {
final TypeVariable target = inferred.getKey();
final InferredValue value = inferred.getValue();
if (value instanceof InferredType) {
inferredTypes.put(target, value);
} else {
final InferredTarget currentTarget = (InferredTarget) value;
final InferredType equivalentType =
(InferredType) inferredTypes.get(((InferredTarget) value).target);
if (equivalentType != null) {
grew = true;
final AnnotatedTypeMirror type = equivalentType.type.deepCopy();
type.replaceAnnotations(currentTarget.additionalAnnotations);
final InferredType newConstraint = new InferredType(type);
inferredTypes.put(currentTarget.target, newConstraint);
}
}
}
}
this.putAll(inferredTypes);
}
public Map<TypeVariable, AnnotatedTypeMirror> toAtmMap() {
final Map<TypeVariable, AnnotatedTypeMirror> result = new LinkedHashMap<>(this.size());
for (final Map.Entry<TypeVariable, InferredValue> entry : this.entrySet()) {
final InferredValue inferredValue = entry.getValue();
if (inferredValue instanceof InferredType) {
result.put(entry.getKey(), ((InferredType) inferredValue).type);
}
}
return result;
}
/**
* Merges values in subordinate into this result, keeping the results form any type arguments that
* were already contained by this InferenceResult.
*
* @param subordinate a result which we wish to merge into this result
*/
public void mergeSubordinate(final InferenceResult subordinate) {
final LinkedHashSet<TypeVariable> previousKeySet = new LinkedHashSet<>(this.keySet());
final LinkedHashSet<TypeVariable> remainingSubKeys = new LinkedHashSet<>(subordinate.keySet());
remainingSubKeys.removeAll(keySet());
for (TypeVariable target : previousKeySet) {
mergeTarget(target, subordinate);
}
for (TypeVariable target : remainingSubKeys) {
this.put(target, subordinate.get(target));
}
resolveChainedTargets();
}
/** Performs a merge for a specific target, we keep only results that lead to a concrete type. */
protected InferredType mergeTarget(final TypeVariable target, final InferenceResult subordinate) {
final InferredValue inferred = this.get(target);
if (inferred instanceof InferredTarget) {
InferredType newType = mergeTarget(((InferredTarget) inferred).target, subordinate);
if (newType == null) {
final InferredValue subValue = subordinate.get(target);
if (subValue instanceof InferredType) {
this.put(target, subValue);
return null;
}
} else {
if (newType.type.getKind() == TypeKind.NULL) {
// If the newType is null, then use the subordinate type, but with the
// primary annotations on null.
final InferredValue subValue = subordinate.get(target);
if (subValue instanceof InferredType) {
AnnotatedTypeMirror copy = ((InferredType) subValue).type.deepCopy();
copy.replaceAnnotations(newType.type.getAnnotations());
newType = new InferredType(copy);
}
}
this.put(target, newType);
return newType;
}
return null;
} // else
return (InferredType) inferred;
}
}