All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.checkerframework.framework.util.typeinference.DefaultTypeArgumentInference Maven / Gradle / Ivy

Go to download

The Checker Framework enhances Java's type system to make it more powerful and useful. This lets software developers detect and prevent errors in their Java programs. The Checker Framework includes compiler plug-ins ("checkers") that find bugs or verify their absence. It also permits you to write your own compiler plug-ins.

There is a newer version: 3.43.0
Show newest version
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.ExecutableType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
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.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.AnnotationMirrorSet;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.TreePathUtil;
import org.checkerframework.javacutil.TypeAnnotationUtils;
import org.checkerframework.javacutil.TypesUtils;
import org.plumelib.util.IPair;
import org.plumelib.util.StringsPlume;

/**
 * An implementation of TypeArgumentInference that mostly follows the process outlined in JLS7 See
 * the JLS 7: JLS
 * §5.12.2.7
 *
 * 

Note, there are some deviations JLS 7 for the following cases: * *

    *
  • 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. *
  • 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. *
  • 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. *
* * TODO: The following limitations need to be fixed, as at the time of this writing we do not have * the time to handle them: * *
    *
  • The GlbUtil does not correctly handled wildcards/typevars when the glb result should be a * wildcard or typevar *
  • 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. *
    {@code
     *  T get()
     *  void set(S s)
     * set(get())
     * }
    * Presumably, we want to detect these situations and combine the set of constraints with * {@code T <: S}. *
*/ 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 inferTypeArgs( AnnotatedTypeFactory typeFactory, ExpressionTree expressionTree, ExecutableElement methodElem, AnnotatedExecutableType methodType) { List argTypes = TypeArgInferenceUtil.getArgumentTypes(expressionTree, typeFactory); TreePath pathToExpression = typeFactory.getPath(expressionTree); assert pathToExpression != null; AnnotatedTypeMirror assignedTo = TypeArgInferenceUtil.assignedTo(typeFactory, pathToExpression); SourceChecker checker = typeFactory.getChecker(); if (showInferenceSteps) { checker.message( Diagnostic.Kind.NOTE, "DTAI: expression: %s%n argTypes: %s%n assignedTo: %s", expressionTree.toString().replace(System.lineSeparator(), " "), argTypes, assignedTo); } Set 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 outMethod() // 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 inferredArgs = new LinkedHashMap<>(); handleUninferredTypeVariables(typeFactory, methodType, targets, inferredArgs); return inferredArgs; } if (assignedTo == null) { assignedTo = typeFactory.getDummyAssignedTo(expressionTree); } Map inferredArgs; try { inferredArgs = infer(typeFactory, argTypes, assignedTo, methodElem, methodType, targets, true); if (showInferenceSteps) { checker.message(Diagnostic.Kind.NOTE, " after infer: %s", inferredArgs); } handleNullTypeArguments( typeFactory, methodElem, methodType, argTypes, assignedTo, targets, inferredArgs); if (showInferenceSteps) { checker.message(Diagnostic.Kind.NOTE, " after handleNull: %s", inferredArgs); } } catch (Exception ex) { // Catch any errors thrown by inference. inferredArgs = new LinkedHashMap<>(); if (showInferenceSteps) { checker.message(Diagnostic.Kind.NOTE, " exception: %s", ex.getLocalizedMessage()); } } handleUninferredTypeVariables(typeFactory, methodType, targets, inferredArgs); if (showInferenceSteps) { checker.message(Diagnostic.Kind.NOTE, " results: %s", inferredArgs); } try { return TypeArgInferenceUtil.correctResults( inferredArgs, expressionTree, (ExecutableType) methodElem.asType(), 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 argTypes, AnnotatedTypeMirror assignedTo, Set targets, Map inferredArgs) { if (!hasNullType(inferredArgs)) { return; } Map 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 inferredArgs) { for (AnnotatedTypeMirror atm : inferredArgs.values()) { if (atm.getKind() == TypeKind.NULL) { return true; } } return false; } /** * This algorithm works as follows: * *
    * *
  • 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 *
  • 2. Solve Argument Constraints -- Create two solutions from the arguments. *
      *
    1. Equality Arg Solution: Solution inferred from arguments used in an invariant * position (i.e. from equality constraints) *
    2. Supertypes Arg Solution: Solution inferred from constraints in which the parameter * is a supertype of argument types. These are kept separate and merged later. *
    * 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. *
  • 3. Build and Solve Initial Assignment Constraints -- Create a set of constraints from the * assignment context WITHOUT substituting either solution from step 2. *
  • 4. Combine the solutions from steps 2.b and 3. This handles cases like the following: *
    {@code
       *  List method(T t1) {}
       * List<@Nullable String> nl = method("");
       * }
    * 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. *

    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: *

    {@code
       *  List method(T t1) {}
       * List<@NonNull String> nl = method(null);
       * }
    * 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. *
  • 5. Combine the result from 2.a and step 4, if there is a conflict use the result from * step 2.a *

    Suppose we have the following: *

    {@code
       *  void method(List<@NonNull T> t, @Initialized Tt) { ... }
       * List<@FBCBottom String> lBottom = ...;
       * method( lbBottom, "nonNullString" );
       * }
    * 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: *
    {@code
       * T => @FBCBottom @NonNull String
       * }
    * Another case covered in this step is: *
    {@code
       *  List method(List t1) {}
       * List<@NonNull String> nonNullList = new ArrayList<>();
       * List<@Nullable String> nl = method(nonNullList);
       * }
    * 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. *
  • 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). *
  • 7. Merge the result from steps 5 and 6 giving preference to 5 (the argument constraints). * Return the result. *
*/ private Map infer( AnnotatedTypeFactory typeFactory, List argumentTypes, AnnotatedTypeMirror assignedTo, ExecutableElement methodElem, AnnotatedExecutableType methodType, Set targets, boolean useNullArguments) { // 1. Step 1 - Build up argument constraints // The AFConstraints for arguments are used also in the Set afArgumentConstraints = createArgumentAFConstraints( typeFactory, argumentTypes, methodType, targets, useNullArguments); // 2. Step 2 - Solve the constraints. IPair argInference = inferFromArguments(typeFactory, afArgumentConstraints, targets); InferenceResult fromArgEqualities = argInference.first; // result 2.a 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 AnnotatedTypeMirror declaredReturnType = methodType.getReturnType(); AnnotatedTypeMirror boxedReturnType; if (declaredReturnType == null) { boxedReturnType = null; } else if (declaredReturnType.getKind().isPrimitive()) { boxedReturnType = typeFactory.getBoxedType((AnnotatedPrimitiveType) declaredReturnType); } else { boxedReturnType = declaredReturnType; } 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. * *

e.g. * *

{@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
   * }
* * 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 targetDeclarations, AnnotatedTypeFactory typeFactory) { QualifierHierarchy qualHierarchy = typeFactory.getQualifierHierarchy(); AnnotationMirrorSet tops = new AnnotationMirrorSet(qualHierarchy.getTopAnnotations()); for (AnnotatedTypeVariable targetDecl : targetDeclarations) { InferredValue inferred = fromArgSupertypes.get(targetDecl.getUnderlyingType()); if (inferred instanceof InferredType) { AnnotatedTypeMirror lowerBoundAsArgument = targetDecl.getLowerBound(); for (AnnotationMirror top : tops) { AnnotationMirror lowerBoundAnno = lowerBoundAsArgument.getEffectiveAnnotationInHierarchy(top); AnnotatedTypeMirror inferredType = ((InferredType) inferred).type; AnnotationMirror argAnno = inferredType.getEffectiveAnnotationInHierarchy(top); if (qualHierarchy.isSubtypeQualifiersOnly(argAnno, lowerBoundAnno)) { inferredType.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 the type factory * @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 createArgumentAFConstraints( AnnotatedTypeFactory typeFactory, List argTypes, AnnotatedExecutableType methodType, Set targets, boolean useNullArguments) { List paramTypes = AnnotatedTypes.expandVarArgsParametersFromTypes(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))); } int numberOfParams = paramTypes.size(); ArrayDeque 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))); } Set 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 IPair inferFromArguments( AnnotatedTypeFactory typeFactory, Set afArgumentConstraints, Set targets) { Set tuArgConstraints = afToTuConstraints(afArgumentConstraints, targets); addConstraintsBetweenTargets(tuArgConstraints, targets, false, typeFactory); ConstraintMap argConstraints = constraintMapBuilder.build(targets, tuArgConstraints, typeFactory); InferenceResult inferredFromArgEqualities = equalitiesSolver.solveEqualities(targets, argConstraints, typeFactory); Set remainingTargets = inferredFromArgEqualities.getRemainingTargets(targets, true); InferenceResult fromSupertypes = supertypesSolver.solveFromSupertypes(remainingTargets, argConstraints, typeFactory); InferenceResult fromSubtypes = subtypesSolver.solveFromSubtypes(remainingTargets, argConstraints, typeFactory); fromSupertypes.mergeSubordinate(fromSubtypes); return IPair.of(inferredFromArgEqualities, fromSupertypes); } /** Step 3. Infer type arguments from the equality constraints of the assignment context. */ private InferenceResult inferFromAssignmentEqualities( AnnotatedTypeMirror assignedTo, AnnotatedTypeMirror boxedReturnType, Set targets, AnnotatedTypeFactory typeFactory) { Set afInitialAssignmentConstraints = createInitialAssignmentConstraints(assignedTo, boxedReturnType, typeFactory, targets); Set 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 createInitialAssignmentConstraints( AnnotatedTypeMirror assignedTo, AnnotatedTypeMirror boxedReturnType, AnnotatedTypeFactory typeFactory, Set targets) { Set result = new LinkedHashSet<>(); if (assignedTo != null) { Set reducedConstraints = new LinkedHashSet<>(); Queue constraints = new ArrayDeque<>(); constraints.add(new F2A(boxedReturnType, assignedTo)); reduceAfConstraints(typeFactory, reducedConstraints, constraints, targets); for (AFConstraint reducedConstraint : reducedConstraints) { if (reducedConstraint instanceof FIsA) { result.add((FIsA) reducedConstraint); } } } return result; } /** * The first half of Step 6. * *

This method creates constraints: * *

    *
  • between the bounds of types that are already inferred and their inferred arguments *
  • between the assignment context and the return type of the method (with the previously * inferred arguments substituted into these constraints) *
*/ public ConstraintMap createAssignmentConstraints( AnnotatedTypeMirror assignedTo, AnnotatedTypeMirror boxedReturnType, AnnotatedExecutableType methodType, Set afArgumentConstraints, Map inferredArgs, Set targets, AnnotatedTypeFactory typeFactory) { ArrayDeque assignmentAfs = new ArrayDeque<>(2 * methodType.getTypeVariables().size() + afArgumentConstraints.size()); for (AnnotatedTypeVariable typeParam : methodType.getTypeVariables()) { TypeVariable target = typeParam.getUnderlyingType(); 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 substitutedAssignmentConstraints = new ArrayDeque<>(assignmentAfs.size() + 1); for (AFConstraint afConstraint : assignmentAfs) { substitutedAssignmentConstraints.add(afConstraint.substitute(inferredArgs)); } AnnotatedTypeMirror substitutedReturnType = TypeArgInferenceUtil.substitute(inferredArgs, boxedReturnType); substitutedAssignmentConstraints.add(new F2A(substitutedReturnType, assignedTo)); Set reducedConstraints = new LinkedHashSet<>(); reduceAfConstraints(typeFactory, reducedConstraints, substitutedAssignmentConstraints, targets); Set 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( AnnotatedTypeMirror assignedTo, AnnotatedTypeMirror boxedReturnType, AnnotatedExecutableType methodType, Set afArgumentConstraints, InferenceResult inferredArgs, Set targets, AnnotatedTypeFactory typeFactory) { ConstraintMap assignmentConstraints = createAssignmentConstraints( assignedTo, boxedReturnType, methodType, afArgumentConstraints, inferredArgs.toAtmMap(), targets, typeFactory); InferenceResult equalitiesResult = equalitiesSolver.solveEqualities(targets, assignmentConstraints, typeFactory); Set 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 targets, AnnotatedTypeFactory typeFactory, InferenceResult equalityResult, InferenceResult supertypeResult) { TypeHierarchy typeHierarchy = typeFactory.getTypeHierarchy(); InferenceResult result = new InferenceResult(); for (TypeVariable target : targets) { InferredValue equalityInferred = equalityResult.get(target); InferredValue supertypeInferred = supertypeResult.get(target); 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 targets, Map inferredArgs) { for (AnnotatedTypeVariable atv : methodType.getTypeVariables()) { TypeVariable typeVar = atv.getUnderlyingType(); if (targets.contains((TypeVariable) TypeAnnotationUtils.unannotatedType(typeVar))) { 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( AnnotatedTypeFactory typeFactory, Set outgoing, Queue toProcess, Set targets) { Set visited = new HashSet<>(); List reducers = Arrays.asList( new A2FReducer(typeFactory), new F2AReducer(typeFactory), new FIsAReducer(typeFactory)); Set 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 { Iterator 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 afToTuConstraints( Set afConstraints, Set targets) { Set outgoing = new LinkedHashSet<>(); for (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 } implies a TUConstraint of {@code B <: A}. * Add these to the constraint list. */ public void addConstraintsBetweenTargets( Set constraints, Set targets, boolean asSubtype, AnnotatedTypeFactory typeFactory) { Types types = typeFactory.getProcessingEnv().getTypeUtils(); List targetList = new ArrayList<>(targets); Map paramDeclarations = new HashMap<>(); for (int i = 0; i < targetList.size(); i++) { TypeVariable earlierTarget = targetList.get(i); for (int j = i + 1; j < targetList.size(); j++) { TypeVariable laterTarget = targetList.get(j); if (types.isSameType(earlierTarget.getUpperBound(), laterTarget)) { AnnotatedTypeVariable headDecl = addOrGetDeclarations(earlierTarget, typeFactory, paramDeclarations); 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)) { AnnotatedTypeVariable headDecl = addOrGetDeclarations(earlierTarget, typeFactory, paramDeclarations); 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 declarations) { AnnotatedTypeVariable atv = declarations.computeIfAbsent( target, __ -> (AnnotatedTypeVariable) typeFactory.getAnnotatedType(target.asElement())); return atv; } }