| 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; |
| } |
| } |