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