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.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.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeVariable;
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.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.ErrorReporter;
import org.checkerframework.javacutil.InternalUtils;
import org.checkerframework.javacutil.TreeUtils;
/** Miscellaneous utilities to help in type argument inference. */
public class TypeArgInferenceUtil {
/**
* Takes an expression tree that must be either a MethodInovcationTree or a NewClassTree
* (constructor invocation) and returns the arguments to its formal parameters. An
* IllegalArgumentException will be thrown if it is neither
*
* @param expression a MethodInvocationTree or a NewClassTree
* @return the list of arguments to Expression
*/
public static List<? extends ExpressionTree> expressionToArgTrees(
final ExpressionTree expression) {
final List<? extends ExpressionTree> argTrees;
if (expression.getKind() == Kind.METHOD_INVOCATION) {
argTrees = ((MethodInvocationTree) expression).getArguments();
} else if (expression.getKind() == Kind.NEW_CLASS) {
argTrees = ((NewClassTree) expression).getArguments();
} else {
argTrees = null;
}
if (argTrees == null) {
throw new IllegalArgumentException(
"TypeArgumentInference.relationsFromMethodArguments:\n"
+ "couldn't determine arguments from tree: "
+ expression);
}
return argTrees;
}
/** Calls get annotated types on a List of trees using the given type factory. */
public static List<AnnotatedTypeMirror> treesToTypes(
final List<? extends ExpressionTree> argTrees, final AnnotatedTypeFactory typeFactory) {
final List<AnnotatedTypeMirror> argTypes = new ArrayList<>(argTrees.size());
for (Tree arg : argTrees) {
argTypes.add(typeFactory.getAnnotatedType(arg));
}
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(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(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.
*
* @return type that it path leaf is assigned to
*/
public static AnnotatedTypeMirror assignedTo(AnnotatedTypeFactory atypeFactory, TreePath path) {
Tree assignmentContext = TreeUtils.getAssignmentContext(path);
if (assignmentContext == null) {
return null;
} else if (assignmentContext instanceof AssignmentTree) {
ExpressionTree variable = ((AssignmentTree) assignmentContext).getVariable();
return atypeFactory.getAnnotatedType(variable);
} else if (assignmentContext instanceof CompoundAssignmentTree) {
ExpressionTree variable = ((CompoundAssignmentTree) assignmentContext).getVariable();
return 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);
return assignedToExecutable(
atypeFactory, path, methodElt, receiver, methodInvocation.getArguments());
} else if (assignmentContext instanceof NewArrayTree) {
//TODO: I left the previous implementation below, it definitely caused infinite loops if you
//TODO: called it from places like the TreeAnnotator
return null;
// FIXME: 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;
ExecutableElement constructorElt = InternalUtils.constructor(newClassTree);
AnnotatedTypeMirror receiver = atypeFactory.fromNewClass(newClassTree);
return 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 = TreeUtils.enclosingOfKind(path, kinds);
if (enclosing.getKind() == Kind.METHOD) {
return (atypeFactory.getAnnotatedType((MethodTree) enclosing)).getReturnType();
} else {
return atypeFactory.getFnInterfaceFromTree((LambdaExpressionTree) enclosing).first;
}
} else if (assignmentContext instanceof VariableTree) {
return assignedToVariable(atypeFactory, assignmentContext);
}
ErrorReporter.errorAbort("AnnotatedTypes.assignedTo: shouldn't be here!");
return null; // dead code
}
private static AnnotatedTypeMirror assignedToExecutable(
AnnotatedTypeFactory atypeFactory,
TreePath path,
ExecutableElement methodElt,
AnnotatedTypeMirror receiver,
List<? extends ExpressionTree> arguments) {
AnnotatedExecutableType method =
AnnotatedTypes.asMemberOf(
atypeFactory.getContext().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;
}
}
assert treeIndex != -1
: "Could not find path in MethodInvocationTree.\n" + "treePath=" + path.toString();
final AnnotatedTypeMirror paramType;
if (treeIndex >= method.getParameterTypes().size() && methodElt.isVarArgs()) {
paramType = method.getParameterTypes().get(method.getParameterTypes().size() - 1);
} 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.
*/
private static boolean isArgument(TreePath path, ExpressionTree argumentTree) {
argumentTree = TreeUtils.skipParens(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);
}
}
/** @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 = new ArrayList<>(annotatedTypeVars.size());
for (AnnotatedTypeVariable annotatedTypeVar : annotatedTypeVars) {
typeVars.add(annotatedTypeVar.getUnderlyingType());
}
// note NULL values creep in because the underlying visitor uses them in various places
final Boolean result = type.accept(new TypeVariableFinder(), typeVars);
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;
}
/**
* Used to detect if the visited type contains one of the type variables in the typeVars
* parameter
*/
private static class TypeVariableFinder
extends AnnotatedTypeScanner<Boolean, List<TypeVariable>> {
@Override
protected Boolean scan(
Iterable<? extends AnnotatedTypeMirror> types, List<TypeVariable> typeVars) {
if (types == null) {
return false;
}
Boolean result = false;
Boolean first = true;
for (AnnotatedTypeMirror type : types) {
result = (first ? scan(type, typeVars) : scanAndReduce(type, typeVars, result));
first = false;
}
return result;
}
@Override
protected Boolean reduce(Boolean r1, Boolean r2) {
if (r1 == null) {
return r2 != null && r2;
} else if (r2 == null) {
return r1;
}
return r1 || r2;
}
@Override
public Boolean visitTypeVariable(AnnotatedTypeVariable type, List<TypeVariable> typeVars) {
if (typeVars.contains(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, (@see TypeVariableSubstitutor).Return the copy
*/
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 = substitutions.get(toModify.getUnderlyingType());
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()) {
ErrorReporter.errorAbort("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;
}
}