blob: a054662091557b3e86fd8f2d73ff4dcb93e8565f [file] [log] [blame]
package org.checkerframework.framework.util.typeinference;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.Tree;
import com.sun.source.util.TreePath;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic.Kind;
import org.checkerframework.framework.source.SourceChecker;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
import org.checkerframework.framework.type.QualifierHierarchy;
import org.checkerframework.framework.type.TypeHierarchy;
import org.checkerframework.framework.util.AnnotatedTypes;
import org.checkerframework.framework.util.AnnotationMirrorSet;
import org.checkerframework.framework.util.typeinference.constraint.A2F;
import org.checkerframework.framework.util.typeinference.constraint.A2FReducer;
import org.checkerframework.framework.util.typeinference.constraint.AFConstraint;
import org.checkerframework.framework.util.typeinference.constraint.AFReducer;
import org.checkerframework.framework.util.typeinference.constraint.F2A;
import org.checkerframework.framework.util.typeinference.constraint.F2AReducer;
import org.checkerframework.framework.util.typeinference.constraint.FIsA;
import org.checkerframework.framework.util.typeinference.constraint.FIsAReducer;
import org.checkerframework.framework.util.typeinference.constraint.TSubU;
import org.checkerframework.framework.util.typeinference.constraint.TSuperU;
import org.checkerframework.framework.util.typeinference.constraint.TUConstraint;
import org.checkerframework.framework.util.typeinference.solver.ConstraintMap;
import org.checkerframework.framework.util.typeinference.solver.ConstraintMapBuilder;
import org.checkerframework.framework.util.typeinference.solver.EqualitiesSolver;
import org.checkerframework.framework.util.typeinference.solver.InferenceResult;
import org.checkerframework.framework.util.typeinference.solver.InferredValue;
import org.checkerframework.framework.util.typeinference.solver.InferredValue.InferredType;
import org.checkerframework.framework.util.typeinference.solver.SubtypesSolver;
import org.checkerframework.framework.util.typeinference.solver.SupertypesSolver;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.Pair;
import org.checkerframework.javacutil.TreePathUtil;
import org.checkerframework.javacutil.TypeAnnotationUtils;
import org.checkerframework.javacutil.TypesUtils;
import org.plumelib.util.StringsPlume;
/**
* An implementation of TypeArgumentInference that mostly follows the process outlined in JLS7 See
* the JLS 7: <a
* href="https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.12.2.7">JLS
* &sect;5.12.2.7</a>
*
* <p>Note, there are some deviations JLS 7 for the following cases:
*
* <ul>
* <li>Places where the JLS is vague. For these cases, first the OpenJDK implementation was
* consulted and then we favored the behavior we desire rather than the implied behavior of
* the JLS or JDK implementation.
* <li>The fact that any given type variable type may or may not have annotations for multiple
* hierarchies means that constraints are more complicated than their Java equivalents. Every
* constraint must identify the hierarchies to which they apply. This makes solving the
* constraint sets more complicated.
* <li>If an argument to a method is null, then the JLS says that it does not constrain the type
* argument. However, null may constrain the qualifiers on the type argument, so it is
* included in the constraints but is not used as the underlying type of the type argument.
* </ul>
*
* TODO: The following limitations need to be fixed, as at the time of this writing we do not have
* the time to handle them:
*
* <ul>
* <li>The GlbUtil does not correctly handled wildcards/typevars when the glb result should be a
* wildcard or typevar
* <li>Interdependent Method Invocations -- Currently we do not correctly handle the case where
* two methods need to have their arguments inferred and one is the argument to the other.
* E.g.
* <pre>{@code
* <T> T get()
* <S> void set(S s)
* set(get())
* }</pre>
* Presumably, we want to detect these situations and combine the set of constraints with
* {@code T <: S}.
* </ul>
*/
public class DefaultTypeArgumentInference implements TypeArgumentInference {
private final EqualitiesSolver equalitiesSolver = new EqualitiesSolver();
private final SupertypesSolver supertypesSolver = new SupertypesSolver();
private final SubtypesSolver subtypesSolver = new SubtypesSolver();
private final ConstraintMapBuilder constraintMapBuilder = new ConstraintMapBuilder();
private final boolean showInferenceSteps;
public DefaultTypeArgumentInference(AnnotatedTypeFactory typeFactory) {
this.showInferenceSteps = typeFactory.getChecker().hasOption("showInferenceSteps");
}
@Override
public Map<TypeVariable, AnnotatedTypeMirror> inferTypeArgs(
AnnotatedTypeFactory typeFactory,
ExpressionTree expressionTree,
ExecutableElement methodElem,
AnnotatedExecutableType methodType) {
final List<AnnotatedTypeMirror> argTypes =
TypeArgInferenceUtil.getArgumentTypes(expressionTree, typeFactory);
final TreePath pathToExpression = typeFactory.getPath(expressionTree);
assert pathToExpression != null;
AnnotatedTypeMirror assignedTo = TypeArgInferenceUtil.assignedTo(typeFactory, pathToExpression);
SourceChecker checker = typeFactory.getChecker();
if (showInferenceSteps) {
checker.message(
Kind.NOTE,
"DTAI: expression: %s%n argTypes: %s%n assignedTo: %s",
expressionTree.toString().replace(System.lineSeparator(), " "),
argTypes,
assignedTo);
}
final Set<TypeVariable> targets = TypeArgInferenceUtil.methodTypeToTargets(methodType);
if (TreePathUtil.enclosingNonParen(pathToExpression).first.getKind()
== Tree.Kind.LAMBDA_EXPRESSION
|| (assignedTo == null && TreePathUtil.getAssignmentContext(pathToExpression) != null)) {
// If the type of the assignment context isn't found, but the expression is assigned,
// then don't attempt to infer type arguments, because the Java type inferred will be
// incorrect. The assignment type is null when it includes uninferred type arguments.
// For example:
// <T> T outMethod()
// <U> void inMethod(U u);
// inMethod(outMethod())
// would require solving the constraints for both type argument inferences
// simultaneously
// Also, if the parent of the expression is a lambda, then the type arguments cannot be
// inferred.
Map<TypeVariable, AnnotatedTypeMirror> inferredArgs = new LinkedHashMap<>();
handleUninferredTypeVariables(typeFactory, methodType, targets, inferredArgs);
return inferredArgs;
}
if (assignedTo == null) {
assignedTo = typeFactory.getDummyAssignedTo(expressionTree);
}
Map<TypeVariable, AnnotatedTypeMirror> inferredArgs;
try {
inferredArgs =
infer(typeFactory, argTypes, assignedTo, methodElem, methodType, targets, true);
if (showInferenceSteps) {
checker.message(Kind.NOTE, " after infer: %s", inferredArgs);
}
handleNullTypeArguments(
typeFactory, methodElem, methodType, argTypes, assignedTo, targets, inferredArgs);
if (showInferenceSteps) {
checker.message(Kind.NOTE, " after handleNull: %s", inferredArgs);
}
} catch (Exception ex) {
// Catch any errors thrown by inference.
inferredArgs = new LinkedHashMap<>();
if (showInferenceSteps) {
checker.message(Kind.NOTE, " exception: %s", ex.getLocalizedMessage());
}
}
handleUninferredTypeVariables(typeFactory, methodType, targets, inferredArgs);
if (showInferenceSteps) {
checker.message(Kind.NOTE, " results: %s", inferredArgs);
}
try {
return TypeArgInferenceUtil.correctResults(
inferredArgs, expressionTree, methodType.getUnderlyingType(), typeFactory);
} catch (Throwable ex) {
// Ignore any exceptions
return inferredArgs;
}
}
/**
* If one of the inferredArgs are NullType, then re-run inference ignoring null method arguments.
* Then lub the result of the second inference with the NullType and put the new result back into
* inferredArgs.
*
* @param typeFactory type factory
* @param methodElem element of the method
* @param methodType annotated type of the method
* @param argTypes annotated types of arguments to the method
* @param assignedTo annotated type to which the result of the method invocation is assigned
* @param targets set of type variables to infer
* @param inferredArgs map of type variables to the annotated types of their type arguments
*/
private void handleNullTypeArguments(
AnnotatedTypeFactory typeFactory,
ExecutableElement methodElem,
AnnotatedExecutableType methodType,
List<AnnotatedTypeMirror> argTypes,
AnnotatedTypeMirror assignedTo,
Set<TypeVariable> targets,
Map<TypeVariable, AnnotatedTypeMirror> inferredArgs) {
if (!hasNullType(inferredArgs)) {
return;
}
final Map<TypeVariable, AnnotatedTypeMirror> inferredArgsWithoutNull =
infer(typeFactory, argTypes, assignedTo, methodElem, methodType, targets, false);
for (AnnotatedTypeVariable atv : methodType.getTypeVariables()) {
TypeVariable typeVar = atv.getUnderlyingType();
AnnotatedTypeMirror result = inferredArgs.get(typeVar);
if (result == null) {
AnnotatedTypeMirror withoutNullResult = inferredArgsWithoutNull.get(typeVar);
if (withoutNullResult != null) {
inferredArgs.put(typeVar, withoutNullResult);
}
} else if (result.getKind() == TypeKind.NULL) {
AnnotatedTypeMirror withoutNullResult = inferredArgsWithoutNull.get(typeVar);
if (withoutNullResult == null) {
// withoutNullResult is null when the only constraint on a type argument is
// where a method argument is null.
withoutNullResult = atv.getUpperBound().deepCopy();
}
AnnotatedTypeMirror lub =
AnnotatedTypes.leastUpperBound(typeFactory, withoutNullResult, result);
inferredArgs.put(typeVar, lub);
}
}
}
private boolean hasNullType(Map<TypeVariable, AnnotatedTypeMirror> inferredArgs) {
for (AnnotatedTypeMirror atm : inferredArgs.values()) {
if (atm.getKind() == TypeKind.NULL) {
return true;
}
}
return false;
}
/**
* This algorithm works as follows:
*
* <ul>
* <!-- ul rather than ol because of many cross-references within the text -->
* <li>1. Build Argument Constraints -- create a set of constraints using the arguments to the
* type parameter declarations, the formal parameters, and the arguments to the method call
* <li>2. Solve Argument Constraints -- Create two solutions from the arguments.
* <ol>
* <li>Equality Arg Solution: Solution inferred from arguments used in an invariant
* position (i.e. from equality constraints)
* <li>Supertypes Arg Solution: Solution inferred from constraints in which the parameter
* is a supertype of argument types. These are kept separate and merged later.
* </ol>
* Note: If there is NO assignment context we just combine the results from 2.a and 2.b,
* giving preference to those in 2.a, and return the result.
* <li>3. Build and Solve Initial Assignment Constraints -- Create a set of constraints from the
* assignment context WITHOUT substituting either solution from step 2.
* <li>4. Combine the solutions from steps 2.b and 3. This handles cases like the following:
* <pre>{@code
* <T> List<T> method(T t1) {}
* List<@Nullable String> nl = method("");
* }</pre>
* If we use just the arguments to infer T we will infer @NonNull String (since the lub of
* all arguments would be @NonNull String). However, this would cause the assignment to
* fail. Instead, since {@literal @NonNull String <: @Nullable String}, we can safely infer
* T to be @Nullable String and both the argument types and the assignment types are
* compatible. In step 4, we combine the results of Step 2.b (which came from lubbing
* argument and argument component types) with the solution from equality constraints via
* the assignment context.
* <p>Note, we always give preference to the results inferred from method arguments if there
* is a conflict between the steps 2 and 4. For example:
* <pre>{@code
* <T> List<T> method(T t1) {}
* List<@NonNull String> nl = method(null);
* }</pre>
* In the above example, the null argument requires that T must be @Nullable String. But the
* assignment context requires that the T must be @NonNull String. But, in this case if we
* use @NonNull String the argument "null" is invalid. In this case, we use @Nullable String
* and report an assignment because we ALWAYS favor the arguments over the assignment
* context.
* <li>5. Combine the result from 2.a and step 4, if there is a conflict use the result from
* step 2.a
* <p>Suppose we have the following:
* <pre>{@code
* <T> void method(List<@NonNull T> t, @Initialized Tt) { ... }
* List<@FBCBottom String> lBottom = ...;
* method( lbBottom, "nonNullString" );
* }</pre>
* From the first argument we can infer that T must be exactly @FBCBottom String but we
* cannot infer anything for the Nullness hierarchy. For the second argument we can infer
* that T is at most @NonNull String but we can infer nothing in the initialization
* hierarchy. In this step we combine these two results, always favoring the equality
* constraints if there is a conflict. For the above example we would infer the following:
* <pre>{@code
* T => @FBCBottom @NonNull String
* }</pre>
* Another case covered in this step is:
* <pre>{@code
* <T> List<T> method(List<T> t1) {}
* List<@NonNull String> nonNullList = new ArrayList<>();
* List<@Nullable String> nl = method(nonNullList);
* }</pre>
* The above assignment should fail because T is forced to be both @NonNull and @Nullable.
* In cases like these, we use @NonNull String becasue we always favor constraints from the
* arguments over the assignment context.
* <li>6. Infer from Assignment Context Finally, the JLS states that we should substitute the
* types we have inferred up until this point back into the original argument constraints.
* We should then combine the constraints we get from the assignment context and solve using
* the greatest lower bounds of all of the constraints of the form: {@literal F :> U} (these
* are referred to as "subtypes" in the ConstraintMap.TargetConstraints).
* <li>7. Merge the result from steps 5 and 6 giving preference to 5 (the argument constraints).
* Return the result.
* </ul>
*/
private Map<TypeVariable, AnnotatedTypeMirror> infer(
final AnnotatedTypeFactory typeFactory,
final List<AnnotatedTypeMirror> argumentTypes,
final AnnotatedTypeMirror assignedTo,
final ExecutableElement methodElem,
final AnnotatedExecutableType methodType,
final Set<TypeVariable> targets,
final boolean useNullArguments) {
// 1. Step 1 - Build up argument constraints
// The AFConstraints for arguments are used also in the
Set<AFConstraint> afArgumentConstraints =
createArgumentAFConstraints(
typeFactory, argumentTypes, methodType, targets, useNullArguments);
// 2. Step 2 - Solve the constraints.
Pair<InferenceResult, InferenceResult> argInference =
inferFromArguments(typeFactory, afArgumentConstraints, targets);
final InferenceResult fromArgEqualities = argInference.first; // result 2.a
final InferenceResult fromArgSubandSupers = argInference.second; // result 2.b
clampToLowerBound(fromArgSubandSupers, methodType.getTypeVariables(), typeFactory);
// if this method invocation's has a return type and it is assigned/pseudo-assigned to
// a variable, assignedTo is the type of that variable
if (assignedTo == null) {
fromArgEqualities.mergeSubordinate(fromArgSubandSupers);
return fromArgEqualities.toAtmMap();
} // else
final AnnotatedTypeMirror declaredReturnType = methodType.getReturnType();
final AnnotatedTypeMirror boxedReturnType;
if (declaredReturnType == null) {
boxedReturnType = null;
} else if (declaredReturnType.getKind().isPrimitive()) {
boxedReturnType = typeFactory.getBoxedType((AnnotatedPrimitiveType) declaredReturnType);
} else {
boxedReturnType = declaredReturnType;
}
final InferenceResult fromArguments = fromArgEqualities;
if (!((MethodSymbol) methodElem).isConstructor()) {
// Step 3 - Infer a solution from the equality constraints in the assignment context
InferenceResult fromAssignmentEqualities =
inferFromAssignmentEqualities(assignedTo, boxedReturnType, targets, typeFactory);
// Step 4 - Combine the results from 2.b and step 3
InferenceResult combinedSupertypesAndAssignment =
combineSupertypeAndAssignmentResults(
targets, typeFactory, fromAssignmentEqualities, fromArgSubandSupers);
// Step 5 - Combine the result from 2.a and step 4, if there is a conflict use the
// result from step 2.a
fromArgEqualities.mergeSubordinate(combinedSupertypesAndAssignment);
// if we don't have a result for all type arguments
// Step 6 - Infer the type arguments from the greatest-lower-bounds of all "subtype"
// constraints
if (!fromArguments.isComplete(targets)) {
InferenceResult fromAssignment =
inferFromAssignment(
assignedTo,
boxedReturnType,
methodType,
afArgumentConstraints,
fromArguments,
targets,
typeFactory);
// Step 7 - Merge the argument and the assignment constraints
fromArguments.mergeSubordinate(fromAssignment);
}
} else {
fromArguments.mergeSubordinate(fromArgSubandSupers);
}
return fromArguments.toAtmMap();
}
/**
* If we have inferred a type argument from the supertype constraints and this type argument is
* BELOW the lower bound, make it AT the lower bound.
*
* <p>e.g.
*
* <pre>{@code
* <@Initialized T extends @Initialized Object> void id(T t) { return t; }
* id(null);
*
* // The invocation of id will result in a type argument with primary annotations of @FBCBottom @Nullable
* // but this is below the lower bound of T in the initialization hierarchy so instead replace
* // @FBCBottom with @Initialized
*
* // This should happen ONLY with supertype constraints because raising the primary annotation would still
* // be valid for these constraints (since we just LUB the arguments involved) but would violate any
* // equality constraints
* }</pre>
*
* TODO: NOTE WE ONLY DO THIS FOR InferredType results for now but we should probably include
* targest as well
*
* @param fromArgSupertypes types inferred from LUBbing types from the arguments to the formal
* parameters
* @param targetDeclarations the declared types of the type parameters whose arguments are being
* inferred
*/
private void clampToLowerBound(
InferenceResult fromArgSupertypes,
List<AnnotatedTypeVariable> targetDeclarations,
AnnotatedTypeFactory typeFactory) {
final QualifierHierarchy qualifierHierarchy = typeFactory.getQualifierHierarchy();
final AnnotationMirrorSet tops =
new AnnotationMirrorSet(qualifierHierarchy.getTopAnnotations());
for (AnnotatedTypeVariable targetDecl : targetDeclarations) {
InferredValue inferred = fromArgSupertypes.get(targetDecl.getUnderlyingType());
if (inferred instanceof InferredType) {
final AnnotatedTypeMirror lowerBoundAsArgument = targetDecl.getLowerBound();
for (AnnotationMirror top : tops) {
final AnnotationMirror lowerBoundAnno =
lowerBoundAsArgument.getEffectiveAnnotationInHierarchy(top);
final AnnotationMirror argAnno =
((InferredType) inferred).type.getEffectiveAnnotationInHierarchy(top);
if (qualifierHierarchy.isSubtype(argAnno, lowerBoundAnno)) {
((InferredType) inferred).type.replaceAnnotation(lowerBoundAnno);
}
}
}
}
}
/**
* Step 1: Create a constraint {@code Ai << Fi} for each Argument(Ai) to formal parameter(Fi).
* Remove any constraint that does not involve a type parameter to be inferred. Reduce the
* remaining constraints so that Fi = Tj where Tj is a type parameter with an argument to be
* inferred. Return the resulting constraint set.
*
* @param typeFactory AnnotatedTypeFactory
* @param argTypes list of annotated types corresponding to the arguments to the method
* @param methodType annotated type of the method
* @param targets type variables to be inferred
* @param useNullArguments whether or not null method arguments should be considered
* @return a set of argument constraints
*/
protected Set<AFConstraint> createArgumentAFConstraints(
final AnnotatedTypeFactory typeFactory,
final List<AnnotatedTypeMirror> argTypes,
final AnnotatedExecutableType methodType,
final Set<TypeVariable> targets,
boolean useNullArguments) {
final List<AnnotatedTypeMirror> paramTypes =
AnnotatedTypes.expandVarArgsFromTypes(methodType, argTypes);
if (argTypes.size() != paramTypes.size()) {
throw new BugInCF(
StringsPlume.joinLines(
"Mismatch between formal parameter count and argument count.",
"paramTypes=" + StringsPlume.join(",", paramTypes),
"argTypes=" + StringsPlume.join(",", argTypes)));
}
final int numberOfParams = paramTypes.size();
final ArrayDeque<AFConstraint> afConstraints = new ArrayDeque<>(numberOfParams);
for (int i = 0; i < numberOfParams; i++) {
if (!useNullArguments && argTypes.get(i).getKind() == TypeKind.NULL) {
continue;
}
afConstraints.add(new A2F(argTypes.get(i), paramTypes.get(i)));
}
final Set<AFConstraint> reducedConstraints = new LinkedHashSet<>();
reduceAfConstraints(typeFactory, reducedConstraints, afConstraints, targets);
return reducedConstraints;
}
/**
* Step 2. Infer type arguments from the equality (TisU) and the supertype (TSuperU) constraints
* of the methods arguments.
*/
private Pair<InferenceResult, InferenceResult> inferFromArguments(
final AnnotatedTypeFactory typeFactory,
final Set<AFConstraint> afArgumentConstraints,
final Set<TypeVariable> targets) {
Set<TUConstraint> tuArgConstraints = afToTuConstraints(afArgumentConstraints, targets);
addConstraintsBetweenTargets(tuArgConstraints, targets, false, typeFactory);
ConstraintMap argConstraints =
constraintMapBuilder.build(targets, tuArgConstraints, typeFactory);
InferenceResult inferredFromArgEqualities =
equalitiesSolver.solveEqualities(targets, argConstraints, typeFactory);
Set<TypeVariable> remainingTargets =
inferredFromArgEqualities.getRemainingTargets(targets, true);
InferenceResult fromSupertypes =
supertypesSolver.solveFromSupertypes(remainingTargets, argConstraints, typeFactory);
InferenceResult fromSubtypes =
subtypesSolver.solveFromSubtypes(remainingTargets, argConstraints, typeFactory);
fromSupertypes.mergeSubordinate(fromSubtypes);
return Pair.of(inferredFromArgEqualities, fromSupertypes);
}
/** Step 3. Infer type arguments from the equality constraints of the assignment context. */
private InferenceResult inferFromAssignmentEqualities(
final AnnotatedTypeMirror assignedTo,
final AnnotatedTypeMirror boxedReturnType,
final Set<TypeVariable> targets,
final AnnotatedTypeFactory typeFactory) {
Set<FIsA> afInitialAssignmentConstraints =
createInitialAssignmentConstraints(assignedTo, boxedReturnType, typeFactory, targets);
Set<TUConstraint> tuInitialAssignmentConstraints =
afToTuConstraints(afInitialAssignmentConstraints, targets);
ConstraintMap initialAssignmentConstraints =
constraintMapBuilder.build(targets, tuInitialAssignmentConstraints, typeFactory);
return equalitiesSolver.solveEqualities(targets, initialAssignmentConstraints, typeFactory);
}
/**
* Create a set of constraints between return type and any type to which it is assigned. Reduce
* these set of constraints and remove any that is not an equality (FIsA) constraint.
*/
protected Set<FIsA> createInitialAssignmentConstraints(
final AnnotatedTypeMirror assignedTo,
final AnnotatedTypeMirror boxedReturnType,
final AnnotatedTypeFactory typeFactory,
final Set<TypeVariable> targets) {
final Set<FIsA> result = new LinkedHashSet<>();
if (assignedTo != null) {
final Set<AFConstraint> reducedConstraints = new LinkedHashSet<>();
final Queue<AFConstraint> constraints = new ArrayDeque<>();
constraints.add(new F2A(boxedReturnType, assignedTo));
reduceAfConstraints(typeFactory, reducedConstraints, constraints, targets);
for (final AFConstraint reducedConstraint : reducedConstraints) {
if (reducedConstraint instanceof FIsA) {
result.add((FIsA) reducedConstraint);
}
}
}
return result;
}
/**
* The first half of Step 6.
*
* <p>This method creates constraints:
*
* <ul>
* <li>between the bounds of types that are already inferred and their inferred arguments
* <li>between the assignment context and the return type of the method (with the previously
* inferred arguments substituted into these constraints)
* </ul>
*/
public ConstraintMap createAssignmentConstraints(
final AnnotatedTypeMirror assignedTo,
final AnnotatedTypeMirror boxedReturnType,
final AnnotatedExecutableType methodType,
final Set<AFConstraint> afArgumentConstraints,
final Map<TypeVariable, AnnotatedTypeMirror> inferredArgs,
final Set<TypeVariable> targets,
final AnnotatedTypeFactory typeFactory) {
final ArrayDeque<AFConstraint> assignmentAfs =
new ArrayDeque<>(2 * methodType.getTypeVariables().size() + afArgumentConstraints.size());
for (AnnotatedTypeVariable typeParam : methodType.getTypeVariables()) {
final TypeVariable target = typeParam.getUnderlyingType();
final AnnotatedTypeMirror inferredType = inferredArgs.get(target);
// for all inferred types Ti: Ti >> Bi where Bi is upper bound and Ti << Li where Li is
// the lower bound for all uninferred types Tu: Tu >> Bi and Lu >> Tu
if (inferredType != null) {
assignmentAfs.add(new A2F(inferredType, typeParam.getUpperBound()));
assignmentAfs.add(new F2A(typeParam.getLowerBound(), inferredType));
} else {
assignmentAfs.add(new F2A(typeParam, typeParam.getUpperBound()));
assignmentAfs.add(new A2F(typeParam.getLowerBound(), typeParam));
}
}
for (AFConstraint argConstraint : afArgumentConstraints) {
if (argConstraint instanceof F2A) {
assignmentAfs.add(argConstraint);
}
}
ArrayDeque<AFConstraint> substitutedAssignmentConstraints =
new ArrayDeque<>(assignmentAfs.size() + 1);
for (AFConstraint afConstraint : assignmentAfs) {
substitutedAssignmentConstraints.add(afConstraint.substitute(inferredArgs));
}
final AnnotatedTypeMirror substitutedReturnType =
TypeArgInferenceUtil.substitute(inferredArgs, boxedReturnType);
substitutedAssignmentConstraints.add(new F2A(substitutedReturnType, assignedTo));
final Set<AFConstraint> reducedConstraints = new LinkedHashSet<>();
reduceAfConstraints(typeFactory, reducedConstraints, substitutedAssignmentConstraints, targets);
final Set<TUConstraint> tuAssignmentConstraints =
afToTuConstraints(reducedConstraints, targets);
addConstraintsBetweenTargets(tuAssignmentConstraints, targets, true, typeFactory);
return constraintMapBuilder.build(targets, tuAssignmentConstraints, typeFactory);
}
/** The Second half of step 6. Use the assignment context to infer a result. */
private InferenceResult inferFromAssignment(
final AnnotatedTypeMirror assignedTo,
final AnnotatedTypeMirror boxedReturnType,
final AnnotatedExecutableType methodType,
final Set<AFConstraint> afArgumentConstraints,
final InferenceResult inferredArgs,
final Set<TypeVariable> targets,
final AnnotatedTypeFactory typeFactory) {
ConstraintMap assignmentConstraints =
createAssignmentConstraints(
assignedTo,
boxedReturnType,
methodType,
afArgumentConstraints,
inferredArgs.toAtmMap(),
targets,
typeFactory);
InferenceResult equalitiesResult =
equalitiesSolver.solveEqualities(targets, assignmentConstraints, typeFactory);
Set<TypeVariable> remainingTargets = equalitiesResult.getRemainingTargets(targets, true);
InferenceResult subtypesResult =
subtypesSolver.solveFromSubtypes(remainingTargets, assignmentConstraints, typeFactory);
equalitiesResult.mergeSubordinate(subtypesResult);
return equalitiesResult;
}
/**
* Step 4. Combine the results from using the Supertype constraints the Equality constraints from
* the assignment context.
*/
private InferenceResult combineSupertypeAndAssignmentResults(
Set<TypeVariable> targets,
AnnotatedTypeFactory typeFactory,
InferenceResult equalityResult,
InferenceResult supertypeResult) {
final TypeHierarchy typeHierarchy = typeFactory.getTypeHierarchy();
final InferenceResult result = new InferenceResult();
for (final TypeVariable target : targets) {
final InferredValue equalityInferred = equalityResult.get(target);
final InferredValue supertypeInferred = supertypeResult.get(target);
final InferredValue outputValue;
if (equalityInferred instanceof InferredType) {
if (supertypeInferred instanceof InferredType) {
AnnotatedTypeMirror superATM = ((InferredType) supertypeInferred).type;
AnnotatedTypeMirror equalityATM = ((InferredType) equalityInferred).type;
if (TypesUtils.isErasedSubtype(
equalityATM.getUnderlyingType(),
superATM.getUnderlyingType(),
typeFactory.getChecker().getTypeUtils())) {
// If the underlying type of equalityATM is a subtype of the underlying
// type of superATM, then the call to isSubtype below will issue an error.
// So call asSuper so that the isSubtype call below works correctly.
equalityATM = AnnotatedTypes.asSuper(typeFactory, equalityATM, superATM);
}
if (typeHierarchy.isSubtype(superATM, equalityATM)) {
outputValue = equalityInferred;
} else {
outputValue = supertypeInferred;
}
} else {
outputValue = equalityInferred;
}
} else {
if (supertypeInferred != null) {
outputValue = supertypeInferred;
} else {
outputValue = null;
}
}
if (outputValue != null) {
result.put(target, outputValue);
}
}
return result;
}
/**
* For any types we have not inferred, use a wildcard with the bounds from the original type
* parameter.
*/
private void handleUninferredTypeVariables(
AnnotatedTypeFactory typeFactory,
AnnotatedExecutableType methodType,
Set<TypeVariable> targets,
Map<TypeVariable, AnnotatedTypeMirror> inferredArgs) {
for (AnnotatedTypeVariable atv : methodType.getTypeVariables()) {
final TypeVariable typeVar = atv.getUnderlyingType();
if (targets.contains((TypeVariable) TypeAnnotationUtils.unannotatedType(typeVar))) {
final AnnotatedTypeMirror inferredType = inferredArgs.get(typeVar);
if (inferredType == null) {
AnnotatedTypeMirror dummy = typeFactory.getUninferredWildcardType(atv);
inferredArgs.put(atv.getUnderlyingType(), dummy);
} else {
typeFactory.addDefaultAnnotations(inferredType);
}
}
}
}
/**
* Given a set of AFConstraints, remove all constraints that are not relevant to inference and
* return a set of AFConstraints in which the F is a use of one of the type parameters to infer.
*/
protected void reduceAfConstraints(
final AnnotatedTypeFactory typeFactory,
final Set<AFConstraint> outgoing,
final Queue<AFConstraint> toProcess,
final Set<TypeVariable> targets) {
final Set<AFConstraint> visited = new HashSet<>();
List<AFReducer> reducers =
Arrays.asList(
new A2FReducer(typeFactory), new F2AReducer(typeFactory), new FIsAReducer(typeFactory));
Set<AFConstraint> newConstraints = new HashSet<>(10);
while (!toProcess.isEmpty()) {
newConstraints.clear();
AFConstraint constraint = toProcess.remove();
if (!visited.contains(constraint)) {
if (constraint.isIrreducible(targets)) {
outgoing.add(constraint);
} else {
final Iterator<AFReducer> reducerIterator = reducers.iterator();
boolean handled = false;
while (!handled && reducerIterator.hasNext()) {
handled = reducerIterator.next().reduce(constraint, newConstraints);
}
if (!handled) {
throw new BugInCF("Unhandled constraint type: " + constraint);
}
toProcess.addAll(newConstraints);
}
visited.add(constraint);
}
}
}
/** Convert AFConstraints to TUConstraints. */
protected Set<TUConstraint> afToTuConstraints(
Set<? extends AFConstraint> afConstraints, Set<TypeVariable> targets) {
final Set<TUConstraint> outgoing = new LinkedHashSet<>();
for (final AFConstraint afConstraint : afConstraints) {
if (!afConstraint.isIrreducible(targets)) {
throw new BugInCF(
StringsPlume.joinLines(
"All afConstraints should be irreducible before conversion.",
"afConstraints=[ " + StringsPlume.join(", ", afConstraints) + " ]",
"targets=[ " + StringsPlume.join(", ", targets) + "]"));
}
outgoing.add(afConstraint.toTUConstraint());
}
return outgoing;
}
/**
* Declarations of the form: {@code <A, B extends A>} implies a TUConstraint of {@code B <: A}.
* Add these to the constraint list.
*/
public void addConstraintsBetweenTargets(
Set<TUConstraint> constraints,
Set<TypeVariable> targets,
boolean asSubtype,
AnnotatedTypeFactory typeFactory) {
final Types types = typeFactory.getProcessingEnv().getTypeUtils();
final List<TypeVariable> targetList = new ArrayList<>(targets);
final Map<TypeVariable, AnnotatedTypeVariable> paramDeclarations = new HashMap<>();
for (int i = 0; i < targetList.size(); i++) {
final TypeVariable earlierTarget = targetList.get(i);
for (int j = i + 1; j < targetList.size(); j++) {
final TypeVariable laterTarget = targetList.get(j);
if (types.isSameType(earlierTarget.getUpperBound(), laterTarget)) {
final AnnotatedTypeVariable headDecl =
addOrGetDeclarations(earlierTarget, typeFactory, paramDeclarations);
final AnnotatedTypeVariable nextDecl =
addOrGetDeclarations(laterTarget, typeFactory, paramDeclarations);
if (asSubtype) {
constraints.add(new TSubU(headDecl, nextDecl));
} else {
constraints.add(new TSuperU(nextDecl, headDecl));
}
} else if (types.isSameType(laterTarget.getUpperBound(), earlierTarget)) {
final AnnotatedTypeVariable headDecl =
addOrGetDeclarations(earlierTarget, typeFactory, paramDeclarations);
final AnnotatedTypeVariable nextDecl =
addOrGetDeclarations(laterTarget, typeFactory, paramDeclarations);
if (asSubtype) {
constraints.add(new TSubU(nextDecl, headDecl));
} else {
constraints.add(new TSuperU(headDecl, nextDecl));
}
}
}
}
}
public AnnotatedTypeVariable addOrGetDeclarations(
TypeVariable target,
AnnotatedTypeFactory typeFactory,
Map<TypeVariable, AnnotatedTypeVariable> declarations) {
AnnotatedTypeVariable atv =
declarations.computeIfAbsent(
target, __ -> (AnnotatedTypeVariable) typeFactory.getAnnotatedType(target.asElement()));
return atv;
}
}