| package org.checkerframework.framework.util.typeinference; |
| |
| import com.sun.source.tree.AssignmentTree; |
| import com.sun.source.tree.CompoundAssignmentTree; |
| import com.sun.source.tree.ConditionalExpressionTree; |
| import com.sun.source.tree.ExpressionTree; |
| import com.sun.source.tree.LambdaExpressionTree; |
| import com.sun.source.tree.MemberSelectTree; |
| import com.sun.source.tree.MethodInvocationTree; |
| import com.sun.source.tree.MethodTree; |
| import com.sun.source.tree.NewArrayTree; |
| import com.sun.source.tree.NewClassTree; |
| import com.sun.source.tree.ReturnTree; |
| import com.sun.source.tree.Tree; |
| import com.sun.source.tree.Tree.Kind; |
| import com.sun.source.tree.VariableTree; |
| import com.sun.source.util.TreePath; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import javax.annotation.processing.ProcessingEnvironment; |
| import javax.lang.model.element.AnnotationMirror; |
| import javax.lang.model.element.ExecutableElement; |
| import javax.lang.model.type.ArrayType; |
| import javax.lang.model.type.DeclaredType; |
| import javax.lang.model.type.ErrorType; |
| import javax.lang.model.type.ExecutableType; |
| import javax.lang.model.type.IntersectionType; |
| import javax.lang.model.type.NoType; |
| import javax.lang.model.type.NullType; |
| import javax.lang.model.type.PrimitiveType; |
| import javax.lang.model.type.TypeKind; |
| import javax.lang.model.type.TypeMirror; |
| import javax.lang.model.type.TypeVariable; |
| import javax.lang.model.type.TypeVisitor; |
| import javax.lang.model.type.UnionType; |
| import javax.lang.model.util.Types; |
| import org.checkerframework.checker.interning.qual.FindDistinct; |
| import org.checkerframework.framework.type.AnnotatedTypeFactory; |
| import org.checkerframework.framework.type.AnnotatedTypeMirror; |
| import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedArrayType; |
| 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.AnnotatedTypeMirror.AnnotatedWildcardType; |
| import org.checkerframework.framework.type.GenericAnnotatedTypeFactory; |
| import org.checkerframework.framework.type.QualifierHierarchy; |
| import org.checkerframework.framework.type.TypeVariableSubstitutor; |
| import org.checkerframework.framework.type.visitor.AnnotatedTypeScanner; |
| import org.checkerframework.framework.util.AnnotatedTypes; |
| import org.checkerframework.framework.util.AnnotationMirrorMap; |
| import org.checkerframework.framework.util.AnnotationMirrorSet; |
| import org.checkerframework.javacutil.BugInCF; |
| import org.checkerframework.javacutil.TreePathUtil; |
| import org.checkerframework.javacutil.TreeUtils; |
| import org.checkerframework.javacutil.TypeAnnotationUtils; |
| import org.checkerframework.javacutil.TypesUtils; |
| import org.plumelib.util.CollectionsPlume; |
| |
| /** Miscellaneous utilities to help in type argument inference. */ |
| public class TypeArgInferenceUtil { |
| |
| /** |
| * Returns a list of boxed annotated types corresponding to the arguments in {@code |
| * methodInvocation}. |
| * |
| * @param methodInvocation {@link MethodInvocationTree} or {@link NewClassTree} |
| * @param typeFactory type factory |
| * @return a list of boxed annotated types corresponding to the arguments in {@code |
| * methodInvocation}. |
| */ |
| public static List<AnnotatedTypeMirror> getArgumentTypes( |
| final ExpressionTree methodInvocation, final AnnotatedTypeFactory typeFactory) { |
| final List<? extends ExpressionTree> argTrees; |
| |
| if (methodInvocation.getKind() == Kind.METHOD_INVOCATION) { |
| argTrees = ((MethodInvocationTree) methodInvocation).getArguments(); |
| |
| } else if (methodInvocation.getKind() == Kind.NEW_CLASS) { |
| argTrees = ((NewClassTree) methodInvocation).getArguments(); |
| |
| } else { |
| throw new BugInCF( |
| "TypeArgumentInference.relationsFromMethodArguments:%n" |
| + "couldn't determine arguments from tree: %s", |
| methodInvocation); |
| } |
| |
| List<AnnotatedTypeMirror> argTypes = |
| CollectionsPlume.mapList( |
| (Tree arg) -> { |
| AnnotatedTypeMirror argType = typeFactory.getAnnotatedType(arg); |
| if (TypesUtils.isPrimitive(argType.getUnderlyingType())) { |
| return typeFactory.getBoxedType((AnnotatedPrimitiveType) argType); |
| } else { |
| return argType; |
| } |
| }, |
| argTrees); |
| return argTypes; |
| } |
| |
| /** |
| * Given a set of type variables for which we are inferring a type, returns true if type is a use |
| * of a type variable in the list of targetTypeVars. |
| */ |
| public static boolean isATarget( |
| final AnnotatedTypeMirror type, final Set<TypeVariable> targetTypeVars) { |
| return type.getKind() == TypeKind.TYPEVAR |
| && targetTypeVars.contains( |
| (TypeVariable) TypeAnnotationUtils.unannotatedType(type.getUnderlyingType())); |
| } |
| |
| /** |
| * Given an AnnotatedExecutableType return a set of type variables that represents the generic |
| * type parameters of that method. |
| */ |
| public static Set<TypeVariable> methodTypeToTargets(final AnnotatedExecutableType methodType) { |
| final List<AnnotatedTypeVariable> annotatedTypeVars = methodType.getTypeVariables(); |
| final Set<TypeVariable> targets = new LinkedHashSet<>(annotatedTypeVars.size()); |
| |
| for (final AnnotatedTypeVariable atv : annotatedTypeVars) { |
| targets.add((TypeVariable) TypeAnnotationUtils.unannotatedType(atv.getUnderlyingType())); |
| } |
| |
| return targets; |
| } |
| |
| /** |
| * Returns the annotated type that the leaf of path is assigned to, if it is within an assignment |
| * context. Returns the annotated type that the method invocation at the leaf is assigned to. If |
| * the result is a primitive, return the boxed version. |
| * |
| * @param atypeFactory the type factory, for looking up types |
| * @param path the path whole leaf to look up a type for |
| * @return the type of path's leaf |
| */ |
| @SuppressWarnings("interning:not.interned") // AST node comparisons |
| public static AnnotatedTypeMirror assignedTo(AnnotatedTypeFactory atypeFactory, TreePath path) { |
| Tree assignmentContext = TreePathUtil.getAssignmentContext(path); |
| AnnotatedTypeMirror res; |
| if (assignmentContext == null) { |
| res = null; |
| } else if (assignmentContext instanceof AssignmentTree) { |
| ExpressionTree variable = ((AssignmentTree) assignmentContext).getVariable(); |
| res = atypeFactory.getAnnotatedType(variable); |
| } else if (assignmentContext instanceof CompoundAssignmentTree) { |
| ExpressionTree variable = ((CompoundAssignmentTree) assignmentContext).getVariable(); |
| res = atypeFactory.getAnnotatedType(variable); |
| } else if (assignmentContext instanceof MethodInvocationTree) { |
| MethodInvocationTree methodInvocation = (MethodInvocationTree) assignmentContext; |
| // TODO move to getAssignmentContext |
| if (methodInvocation.getMethodSelect() instanceof MemberSelectTree |
| && ((MemberSelectTree) methodInvocation.getMethodSelect()).getExpression() |
| == path.getLeaf()) { |
| return null; |
| } |
| ExecutableElement methodElt = TreeUtils.elementFromUse(methodInvocation); |
| AnnotatedTypeMirror receiver = atypeFactory.getReceiverType(methodInvocation); |
| res = |
| assignedToExecutable( |
| atypeFactory, path, methodElt, receiver, methodInvocation.getArguments()); |
| } else if (assignmentContext instanceof NewArrayTree) { |
| // TODO: I left the previous implementation below, it definitely caused infinite loops |
| // TODO: if you called it from places like the TreeAnnotator. |
| res = null; |
| |
| // TODO: This may cause infinite loop |
| // AnnotatedTypeMirror type = |
| // atypeFactory.getAnnotatedType((NewArrayTree)assignmentContext); |
| // type = AnnotatedTypes.innerMostType(type); |
| // return type; |
| |
| } else if (assignmentContext instanceof NewClassTree) { |
| // This need to be basically like MethodTree |
| NewClassTree newClassTree = (NewClassTree) assignmentContext; |
| if (newClassTree.getEnclosingExpression() instanceof NewClassTree |
| && (newClassTree.getEnclosingExpression() == path.getLeaf())) { |
| return null; |
| } |
| ExecutableElement constructorElt = TreeUtils.constructor(newClassTree); |
| AnnotatedTypeMirror receiver = atypeFactory.fromNewClass(newClassTree); |
| res = |
| assignedToExecutable( |
| atypeFactory, path, constructorElt, receiver, newClassTree.getArguments()); |
| } else if (assignmentContext instanceof ReturnTree) { |
| HashSet<Kind> kinds = new HashSet<>(Arrays.asList(Kind.LAMBDA_EXPRESSION, Kind.METHOD)); |
| Tree enclosing = TreePathUtil.enclosingOfKind(path, kinds); |
| |
| if (enclosing.getKind() == Kind.METHOD) { |
| res = atypeFactory.getAnnotatedType((MethodTree) enclosing).getReturnType(); |
| } else { |
| AnnotatedExecutableType fninf = |
| atypeFactory.getFunctionTypeFromTree((LambdaExpressionTree) enclosing); |
| res = fninf.getReturnType(); |
| } |
| |
| } else if (assignmentContext instanceof VariableTree) { |
| res = assignedToVariable(atypeFactory, assignmentContext); |
| } else { |
| throw new BugInCF("AnnotatedTypes.assignedTo: shouldn't be here"); |
| } |
| |
| if (res != null && TypesUtils.isPrimitive(res.getUnderlyingType())) { |
| return atypeFactory.getBoxedType((AnnotatedPrimitiveType) res); |
| } else { |
| return res; |
| } |
| } |
| |
| private static AnnotatedTypeMirror assignedToExecutable( |
| AnnotatedTypeFactory atypeFactory, |
| TreePath path, |
| ExecutableElement methodElt, |
| AnnotatedTypeMirror receiver, |
| List<? extends ExpressionTree> arguments) { |
| AnnotatedExecutableType method = |
| AnnotatedTypes.asMemberOf( |
| atypeFactory.getChecker().getTypeUtils(), atypeFactory, receiver, methodElt); |
| int treeIndex = -1; |
| for (int i = 0; i < arguments.size(); ++i) { |
| ExpressionTree argumentTree = arguments.get(i); |
| if (isArgument(path, argumentTree)) { |
| treeIndex = i; |
| break; |
| } |
| } |
| final AnnotatedTypeMirror paramType; |
| if (treeIndex == -1) { |
| // The tree wasn't found as an argument, so it has to be the receiver. |
| // This can happen for inner class constructors that take an outer class argument. |
| paramType = method.getReceiverType(); |
| } else if (treeIndex + 1 >= method.getParameterTypes().size() && methodElt.isVarArgs()) { |
| AnnotatedTypeMirror varArgsType = |
| method.getParameterTypes().get(method.getParameterTypes().size() - 1); |
| paramType = ((AnnotatedArrayType) varArgsType).getComponentType(); |
| } else { |
| paramType = method.getParameterTypes().get(treeIndex); |
| } |
| |
| // Examples like this: |
| // <T> T outMethod() |
| // <U> void inMethod(U u); |
| // inMethod(outMethod()) |
| // would require solving the constraints for both type argument inferences simultaneously |
| if (paramType == null || containsUninferredTypeParameter(paramType, method)) { |
| return null; |
| } |
| |
| return paramType; |
| } |
| |
| /** |
| * Returns whether argumentTree is the tree at the leaf of path. If tree is a conditional |
| * expression, isArgument is called recursively on the true and false expressions. |
| * |
| * @param path the path whose leaf to test |
| * @param argumentTree the expression that might be path's leaf |
| * @return true if {@code argumentTree} is the leaf of {@code path} |
| */ |
| private static boolean isArgument(TreePath path, @FindDistinct ExpressionTree argumentTree) { |
| argumentTree = TreeUtils.withoutParens(argumentTree); |
| if (argumentTree == path.getLeaf()) { |
| return true; |
| } else if (argumentTree.getKind() == Kind.CONDITIONAL_EXPRESSION) { |
| ConditionalExpressionTree conditionalExpressionTree = |
| (ConditionalExpressionTree) argumentTree; |
| return isArgument(path, conditionalExpressionTree.getTrueExpression()) |
| || isArgument(path, conditionalExpressionTree.getFalseExpression()); |
| } |
| return false; |
| } |
| |
| /** |
| * If the variable's type is a type variable, return getAnnotatedTypeLhsNoTypeVarDefault(tree). |
| * Rational: |
| * |
| * <p>For example: |
| * |
| * <pre>{@code |
| * <S> S bar () {...} |
| * |
| * <T> T foo(T p) { |
| * T local = bar(); |
| * return local; |
| * } |
| * }</pre> |
| * |
| * During type argument inference of {@code bar}, the assignment context is {@code local}. If the |
| * local variable default is used, then the type of assignment context type is {@code @Nullable T} |
| * and the type argument inferred for {@code bar()} is {@code @Nullable T}. And an incompatible |
| * types in return error is issued. |
| * |
| * <p>If instead, the local variable default is not applied, then the assignment context type is |
| * {@code T} (with lower bound {@code @NonNull Void} and upper bound {@code @Nullable Object}) and |
| * the type argument inferred for {@code bar()} is {@code T}. During dataflow, the type of {@code |
| * local} is refined to {@code T} and the return is legal. |
| * |
| * <p>If the assignment context type was a declared type, for example: |
| * |
| * <pre>{@code |
| * <S> S bar () {...} |
| * Object foo() { |
| * Object local = bar(); |
| * return local; |
| * } |
| * }</pre> |
| * |
| * The local variable default must be used or else the assignment context type is missing an |
| * annotation. So, an incompatible types in return error is issued in the above code. We could |
| * improve type argument inference in this case and by using the lower bound of {@code S} instead |
| * of the local variable default. |
| * |
| * @param atypeFactory AnnotatedTypeFactory |
| * @param assignmentContext VariableTree |
| * @return AnnotatedTypeMirror of Assignment context |
| */ |
| public static AnnotatedTypeMirror assignedToVariable( |
| AnnotatedTypeFactory atypeFactory, Tree assignmentContext) { |
| if (atypeFactory instanceof GenericAnnotatedTypeFactory<?, ?, ?, ?>) { |
| final GenericAnnotatedTypeFactory<?, ?, ?, ?> gatf = |
| ((GenericAnnotatedTypeFactory<?, ?, ?, ?>) atypeFactory); |
| return gatf.getAnnotatedTypeLhsNoTypeVarDefault(assignmentContext); |
| } else { |
| return atypeFactory.getAnnotatedType(assignmentContext); |
| } |
| } |
| |
| /** |
| * Returns true if the type contains a use of a type variable from methodType. |
| * |
| * @return true if the type contains a use of a type variable from methodType |
| */ |
| private static boolean containsUninferredTypeParameter( |
| AnnotatedTypeMirror type, AnnotatedExecutableType methodType) { |
| final List<AnnotatedTypeVariable> annotatedTypeVars = methodType.getTypeVariables(); |
| final List<TypeVariable> typeVars = |
| CollectionsPlume.mapList( |
| (AnnotatedTypeVariable annotatedTypeVar) -> |
| (TypeVariable) |
| TypeAnnotationUtils.unannotatedType(annotatedTypeVar.getUnderlyingType()), |
| annotatedTypeVars); |
| |
| return containsTypeParameter(type, typeVars); |
| } |
| |
| /** |
| * Returns true if {@code type} contains a use of a type variable in {@code typeVariables}. |
| * |
| * @param type type to search |
| * @param typeVariables collection of type variables |
| * @return true if {@code type} contains a use of a type variable in {@code typeVariables} |
| */ |
| public static boolean containsTypeParameter( |
| AnnotatedTypeMirror type, Collection<TypeVariable> typeVariables) { |
| // note NULL values creep in because the underlying visitor uses them in various places |
| final Boolean result = type.accept(new TypeVariableFinder(), typeVariables); |
| return result != null && result; |
| } |
| |
| /** |
| * Take a set of annotations and separate them into a mapping of {@code hierarchy top => |
| * annotations in hierarchy}. |
| */ |
| public static AnnotationMirrorMap<AnnotationMirror> createHierarchyMap( |
| final AnnotationMirrorSet annos, final QualifierHierarchy qualifierHierarchy) { |
| AnnotationMirrorMap<AnnotationMirror> result = new AnnotationMirrorMap<>(); |
| |
| for (AnnotationMirror anno : annos) { |
| result.put(qualifierHierarchy.getTopAnnotation(anno), anno); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Throws an exception if the type is an uninferred type argument. |
| * |
| * <p>The error will be caught in DefaultTypeArgumentInference#infer and inference will be |
| * aborted, but type-checking will continue. |
| */ |
| public static void checkForUninferredTypes(AnnotatedTypeMirror type) { |
| if (type.getKind() != TypeKind.WILDCARD) { |
| return; |
| } |
| if (((AnnotatedWildcardType) type).isUninferredTypeArgument()) { |
| throw new BugInCF("Can't make a constraint that includes an uninferred type argument."); |
| } |
| } |
| |
| /** |
| * Used to detect if the visited type contains one of the type variables in the typeVars |
| * parameter. |
| */ |
| private static class TypeVariableFinder |
| extends AnnotatedTypeScanner<Boolean, Collection<TypeVariable>> { |
| |
| /** Create TypeVariableFinder. */ |
| protected TypeVariableFinder() { |
| super(Boolean::logicalOr, false); |
| } |
| |
| @Override |
| public Boolean visitTypeVariable( |
| AnnotatedTypeVariable type, Collection<TypeVariable> typeVars) { |
| if (typeVars.contains( |
| (TypeVariable) TypeAnnotationUtils.unannotatedType(type.getUnderlyingType()))) { |
| return true; |
| } else { |
| return super.visitTypeVariable(type, typeVars); |
| } |
| } |
| } |
| |
| /* |
| * Various TypeArgumentInference steps require substituting types for type arguments that have already been |
| * inferred into constraints that are used infer other type arguments. Substituter is used in |
| * the utility methods to do this. |
| */ |
| private static final TypeVariableSubstitutor substitutor = new TypeVariableSubstitutor(); |
| |
| // Substituter requires an input map that the substitute methods build. We just reuse the same |
| // map rather than recreate it each time. |
| private static final Map<TypeVariable, AnnotatedTypeMirror> substituteMap = new HashMap<>(5); |
| |
| /** |
| * Replace all uses of typeVariable with substitution in a copy of toModify using the normal |
| * substitution rules. Return the copy |
| * |
| * @see TypeVariableSubstitutor |
| */ |
| public static AnnotatedTypeMirror substitute( |
| final TypeVariable typeVariable, |
| final AnnotatedTypeMirror substitution, |
| final AnnotatedTypeMirror toModify) { |
| substituteMap.clear(); |
| substituteMap.put(typeVariable, substitution.deepCopy()); |
| |
| final AnnotatedTypeMirror toModifyCopy = toModify.deepCopy(); |
| substitutor.substitute(substituteMap, toModifyCopy); |
| return toModifyCopy; |
| } |
| |
| /** |
| * Create a copy of toModify. In the copy, for each pair {@code typeVariable => annotated type} |
| * replace uses of typeVariable with the corresponding annotated type using normal substitution |
| * rules (@see TypeVariableSubstitutor). Return the copy. |
| */ |
| public static AnnotatedTypeMirror substitute( |
| Map<TypeVariable, AnnotatedTypeMirror> substitutions, final AnnotatedTypeMirror toModify) { |
| final AnnotatedTypeMirror substitution; |
| if (toModify.getKind() == TypeKind.TYPEVAR) { |
| substitution = |
| substitutions.get( |
| (TypeVariable) TypeAnnotationUtils.unannotatedType(toModify.getUnderlyingType())); |
| } else { |
| substitution = null; |
| } |
| if (substitution != null) { |
| return substitution.deepCopy(); |
| } |
| |
| final AnnotatedTypeMirror toModifyCopy = toModify.deepCopy(); |
| substitutor.substitute(substitutions, toModifyCopy); |
| return toModifyCopy; |
| } |
| |
| /** |
| * Successively calls least upper bound on the elements of types. Unlike leastUpperBound, this |
| * method will box primitives if necessary |
| */ |
| public static AnnotatedTypeMirror leastUpperBound( |
| final AnnotatedTypeFactory typeFactory, final Iterable<AnnotatedTypeMirror> types) { |
| final Iterator<AnnotatedTypeMirror> typesIter = types.iterator(); |
| if (!typesIter.hasNext()) { |
| throw new BugInCF("Calling LUB on empty list"); |
| } |
| AnnotatedTypeMirror lubType = typesIter.next(); |
| AnnotatedTypeMirror nextType = null; |
| while (typesIter.hasNext()) { |
| nextType = typesIter.next(); |
| |
| if (lubType.getKind().isPrimitive()) { |
| if (!nextType.getKind().isPrimitive()) { |
| lubType = typeFactory.getBoxedType((AnnotatedPrimitiveType) lubType); |
| } |
| } else if (nextType.getKind().isPrimitive()) { |
| if (!lubType.getKind().isPrimitive()) { |
| nextType = typeFactory.getBoxedType((AnnotatedPrimitiveType) nextType); |
| } |
| } |
| lubType = AnnotatedTypes.leastUpperBound(typeFactory, lubType, nextType); |
| } |
| |
| return lubType; |
| } |
| |
| /** |
| * If the type arguments computed by DefaultTypeArgumentInference don't match the return type |
| * mirror of {@code invocation}, then replace those type arguments with an uninferred wildcard. |
| */ |
| protected static Map<TypeVariable, AnnotatedTypeMirror> correctResults( |
| Map<TypeVariable, AnnotatedTypeMirror> result, |
| ExpressionTree invocation, |
| ExecutableType methodType, |
| AnnotatedTypeFactory factory) { |
| ProcessingEnvironment env = factory.getProcessingEnv(); |
| Types types = env.getTypeUtils(); |
| Map<TypeVariable, TypeMirror> fromReturn = |
| getMappingFromReturnType(invocation, methodType, env); |
| for (Map.Entry<TypeVariable, AnnotatedTypeMirror> entry : |
| // result is side-effected by this loop, so iterate over a copy |
| new ArrayList<>(result.entrySet())) { |
| TypeVariable typeVariable = entry.getKey(); |
| if (!fromReturn.containsKey(typeVariable)) { |
| continue; |
| } |
| TypeMirror correctType = fromReturn.get(typeVariable); |
| TypeMirror inferredType = entry.getValue().getUnderlyingType(); |
| if (types.isSameType(types.erasure(correctType), types.erasure(inferredType))) { |
| if (areSameCapture(correctType, inferredType, types)) { |
| continue; |
| } |
| } |
| if (!types.isSameType(correctType, inferredType)) { |
| AnnotatedWildcardType wt = |
| factory.getUninferredWildcardType( |
| (AnnotatedTypeVariable) |
| AnnotatedTypeMirror.createType(typeVariable, factory, false)); |
| wt.replaceAnnotations(entry.getValue().getAnnotations()); |
| result.put(typeVariable, wt); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Returns true if actual and inferred are captures of the same wildcard or declared type. |
| * |
| * @return true if actual and inferred are captures of the same wildcard or declared type |
| */ |
| private static boolean areSameCapture(TypeMirror actual, TypeMirror inferred, Types types) { |
| if (TypesUtils.isCaptured(actual) && TypesUtils.isCaptured(inferred)) { |
| return true; |
| } else if (TypesUtils.isCaptured(actual) && inferred.getKind() == TypeKind.WILDCARD) { |
| return true; |
| } else if (actual.getKind() == TypeKind.DECLARED && inferred.getKind() == TypeKind.DECLARED) { |
| DeclaredType actualDT = (DeclaredType) actual; |
| DeclaredType inferredDT = (DeclaredType) inferred; |
| if (actualDT.getTypeArguments().size() == inferredDT.getTypeArguments().size()) { |
| for (int i = 0; i < actualDT.getTypeArguments().size(); i++) { |
| if (!areSameCapture( |
| actualDT.getTypeArguments().get(i), inferredDT.getTypeArguments().get(i), types)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Returns a mapping of type variable to type argument computed using the type of {@code |
| * methodInvocationTree} and the return type of {@code methodType}. |
| */ |
| private static Map<TypeVariable, TypeMirror> getMappingFromReturnType( |
| ExpressionTree methodInvocationTree, ExecutableType methodType, ProcessingEnvironment env) { |
| TypeMirror methodCallType = TreeUtils.typeOf(methodInvocationTree); |
| Types types = env.getTypeUtils(); |
| GetMapping mapping = new GetMapping(methodType.getTypeVariables(), types); |
| mapping.visit(methodType.getReturnType(), methodCallType); |
| return mapping.subs; |
| } |
| |
| /** |
| * Helper class for {@link #getMappingFromReturnType(ExpressionTree, ExecutableType, |
| * ProcessingEnvironment)} |
| */ |
| private static class GetMapping implements TypeVisitor<Void, TypeMirror> { |
| |
| final Map<TypeVariable, TypeMirror> subs = new HashMap<>(); |
| final List<? extends TypeVariable> typeVariables; |
| final Types types; |
| |
| private GetMapping(List<? extends TypeVariable> typeVariables, Types types) { |
| this.typeVariables = typeVariables; |
| this.types = types; |
| } |
| |
| @Override |
| public Void visit(TypeMirror t, TypeMirror mirror) { |
| if (t == null || mirror == null) { |
| return null; |
| } |
| return t.accept(this, mirror); |
| } |
| |
| @Override |
| public Void visit(TypeMirror t) { |
| return null; |
| } |
| |
| @Override |
| public Void visitPrimitive(PrimitiveType t, TypeMirror mirror) { |
| return null; |
| } |
| |
| @Override |
| public Void visitNull(NullType t, TypeMirror mirror) { |
| return null; |
| } |
| |
| @Override |
| public Void visitArray(ArrayType t, TypeMirror mirror) { |
| assert mirror.getKind() == TypeKind.ARRAY : mirror; |
| return visit(t.getComponentType(), ((ArrayType) mirror).getComponentType()); |
| } |
| |
| @Override |
| public Void visitDeclared(DeclaredType t, TypeMirror mirror) { |
| assert mirror.getKind() == TypeKind.DECLARED : mirror; |
| DeclaredType param = (DeclaredType) mirror; |
| if (types.isSubtype(mirror, param)) { |
| // param = (DeclaredType) types.asSuper((Type) mirror, ((Type) |
| // param).asElement()); |
| } |
| if (t.getTypeArguments().size() == param.getTypeArguments().size()) { |
| for (int i = 0; i < t.getTypeArguments().size(); i++) { |
| visit(t.getTypeArguments().get(i), param.getTypeArguments().get(i)); |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public Void visitError(ErrorType t, TypeMirror mirror) { |
| return null; |
| } |
| |
| @Override |
| public Void visitTypeVariable(TypeVariable t, TypeMirror mirror) { |
| if (typeVariables.contains(t)) { |
| subs.put(t, mirror); |
| } else if (mirror.getKind() == TypeKind.TYPEVAR) { |
| TypeVariable param = (TypeVariable) mirror; |
| visit(t.getUpperBound(), param.getUpperBound()); |
| visit(t.getLowerBound(), param.getLowerBound()); |
| } |
| // else it's not a method type variable |
| return null; |
| } |
| |
| @Override |
| public Void visitWildcard(javax.lang.model.type.WildcardType t, TypeMirror mirror) { |
| if (mirror.getKind() == TypeKind.WILDCARD) { |
| javax.lang.model.type.WildcardType param = (javax.lang.model.type.WildcardType) mirror; |
| visit(t.getExtendsBound(), param.getExtendsBound()); |
| visit(t.getSuperBound(), param.getSuperBound()); |
| } else if (mirror.getKind() == TypeKind.TYPEVAR) { |
| TypeVariable param = (TypeVariable) mirror; |
| visit(t.getExtendsBound(), param.getUpperBound()); |
| visit(t.getSuperBound(), param.getLowerBound()); |
| } else { |
| assert false : mirror; |
| } |
| return null; |
| } |
| |
| @Override |
| public Void visitExecutable(ExecutableType t, TypeMirror mirror) { |
| return null; |
| } |
| |
| @Override |
| public Void visitNoType(NoType t, TypeMirror mirror) { |
| return null; |
| } |
| |
| @Override |
| public Void visitUnknown(TypeMirror t, TypeMirror mirror) { |
| return null; |
| } |
| |
| @Override |
| public Void visitUnion(UnionType t, TypeMirror mirror) { |
| return null; |
| } |
| |
| @Override |
| public Void visitIntersection(IntersectionType t, TypeMirror mirror) { |
| assert mirror.getKind() == TypeKind.INTERSECTION : mirror; |
| IntersectionType param = (IntersectionType) mirror; |
| assert t.getBounds().size() == param.getBounds().size(); |
| |
| for (int i = 0; i < t.getBounds().size(); i++) { |
| visit(t.getBounds().get(i), param.getBounds().get(i)); |
| } |
| |
| return null; |
| } |
| } |
| } |