aboutsummaryrefslogtreecommitdiff
path: root/nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java
diff options
context:
space:
mode:
Diffstat (limited to 'nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java')
-rw-r--r--nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java841
1 files changed, 841 insertions, 0 deletions
diff --git a/nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java b/nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java
new file mode 100644
index 0000000..23affc4
--- /dev/null
+++ b/nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java
@@ -0,0 +1,841 @@
+package com.uber.nullaway.generics;
+
+import static com.google.common.base.Verify.verify;
+import static com.uber.nullaway.NullabilityUtil.castToNonNull;
+
+import com.google.common.base.Preconditions;
+import com.google.errorprone.VisitorState;
+import com.google.errorprone.suppliers.Supplier;
+import com.google.errorprone.suppliers.Suppliers;
+import com.google.errorprone.util.ASTHelpers;
+import com.sun.source.tree.AnnotatedTypeTree;
+import com.sun.source.tree.AnnotationTree;
+import com.sun.source.tree.AssignmentTree;
+import com.sun.source.tree.ConditionalExpressionTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.ParameterizedTypeTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.util.TreePath;
+import com.sun.tools.javac.code.Attribute;
+import com.sun.tools.javac.code.Symbol;
+import com.sun.tools.javac.code.Type;
+import com.uber.nullaway.Config;
+import com.uber.nullaway.ErrorBuilder;
+import com.uber.nullaway.ErrorMessage;
+import com.uber.nullaway.NullAway;
+import com.uber.nullaway.Nullness;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.Nullable;
+import javax.lang.model.type.ExecutableType;
+
+/** Methods for performing checks related to generic types and nullability. */
+public final class GenericsChecks {
+
+ /**
+ * Supplier for the JSpecify {@code @Nullable} annotation. Required since for now, certain checks
+ * related to generics specifically look for {@code @org.jspecify.annotations.Nullable}
+ * annotations and do not apply to other {@code @Nullable} annotations.
+ */
+ static final Supplier<Type> JSPECIFY_NULLABLE_TYPE_SUPPLIER =
+ Suppliers.typeFromString("org.jspecify.annotations.Nullable");
+
+ /** Do not instantiate; all methods should be static */
+ private GenericsChecks() {}
+
+ /**
+ * Checks that for an instantiated generic type, {@code @Nullable} types are only used for type
+ * variables that have a {@code @Nullable} upper bound.
+ *
+ * @param tree the tree representing the instantiated type
+ * @param state visitor state
+ * @param analysis the analysis object
+ * @param config the analysis config
+ */
+ public static void checkInstantiationForParameterizedTypedTree(
+ ParameterizedTypeTree tree, VisitorState state, NullAway analysis, Config config) {
+ if (!config.isJSpecifyMode()) {
+ return;
+ }
+ List<? extends Tree> typeArguments = tree.getTypeArguments();
+ if (typeArguments.isEmpty()) {
+ return;
+ }
+ Map<Integer, Tree> nullableTypeArguments = new HashMap<>();
+ for (int i = 0; i < typeArguments.size(); i++) {
+ Tree curTypeArg = typeArguments.get(i);
+ if (curTypeArg instanceof AnnotatedTypeTree) {
+ AnnotatedTypeTree annotatedType = (AnnotatedTypeTree) curTypeArg;
+ for (AnnotationTree annotation : annotatedType.getAnnotations()) {
+ Type annotationType = ASTHelpers.getType(annotation);
+ if (annotationType != null
+ && Nullness.isNullableAnnotation(annotationType.toString(), config)) {
+ nullableTypeArguments.put(i, curTypeArg);
+ break;
+ }
+ }
+ }
+ }
+ // base type that is being instantiated
+ Type baseType = ASTHelpers.getType(tree);
+ if (baseType == null) {
+ return;
+ }
+ com.sun.tools.javac.util.List<Type> baseTypeArgs = baseType.tsym.type.getTypeArguments();
+ for (int i = 0; i < baseTypeArgs.size(); i++) {
+ if (nullableTypeArguments.containsKey(i)) {
+
+ Type typeVariable = baseTypeArgs.get(i);
+ Type upperBound = typeVariable.getUpperBound();
+ com.sun.tools.javac.util.List<Attribute.TypeCompound> annotationMirrors =
+ upperBound.getAnnotationMirrors();
+ boolean hasNullableAnnotation =
+ Nullness.hasNullableAnnotation(annotationMirrors.stream(), config);
+ // if base type argument does not have @Nullable annotation then the instantiation is
+ // invalid
+ if (!hasNullableAnnotation) {
+ reportInvalidInstantiationError(
+ nullableTypeArguments.get(i), baseType, typeVariable, state, analysis);
+ }
+ }
+ }
+ }
+
+ private static void reportInvalidInstantiationError(
+ Tree tree, Type baseType, Type baseTypeVariable, VisitorState state, NullAway analysis) {
+ ErrorBuilder errorBuilder = analysis.getErrorBuilder();
+ ErrorMessage errorMessage =
+ new ErrorMessage(
+ ErrorMessage.MessageTypes.TYPE_PARAMETER_CANNOT_BE_NULLABLE,
+ String.format(
+ "Generic type parameter cannot be @Nullable, as type variable %s of type %s does not have a @Nullable upper bound",
+ baseTypeVariable.tsym.toString(), baseType.tsym.toString()));
+ state.reportMatch(
+ errorBuilder.createErrorDescription(
+ errorMessage, analysis.buildDescription(tree), state, null));
+ }
+
+ private static void reportInvalidAssignmentInstantiationError(
+ Tree tree, Type lhsType, Type rhsType, VisitorState state, NullAway analysis) {
+ ErrorBuilder errorBuilder = analysis.getErrorBuilder();
+ ErrorMessage errorMessage =
+ new ErrorMessage(
+ ErrorMessage.MessageTypes.ASSIGN_GENERIC_NULLABLE,
+ String.format(
+ "Cannot assign from type "
+ + prettyTypeForError(rhsType, state)
+ + " to type "
+ + prettyTypeForError(lhsType, state)
+ + " due to mismatched nullability of type parameters"));
+ state.reportMatch(
+ errorBuilder.createErrorDescription(
+ errorMessage, analysis.buildDescription(tree), state, null));
+ }
+
+ private static void reportInvalidReturnTypeError(
+ Tree tree, Type methodType, Type returnType, VisitorState state, NullAway analysis) {
+ ErrorBuilder errorBuilder = analysis.getErrorBuilder();
+ ErrorMessage errorMessage =
+ new ErrorMessage(
+ ErrorMessage.MessageTypes.RETURN_NULLABLE_GENERIC,
+ String.format(
+ "Cannot return expression of type "
+ + prettyTypeForError(returnType, state)
+ + " from method with return type "
+ + prettyTypeForError(methodType, state)
+ + " due to mismatched nullability of type parameters"));
+ state.reportMatch(
+ errorBuilder.createErrorDescription(
+ errorMessage, analysis.buildDescription(tree), state, null));
+ }
+
+ private static void reportMismatchedTypeForTernaryOperator(
+ Tree tree, Type expressionType, Type subPartType, VisitorState state, NullAway analysis) {
+ ErrorBuilder errorBuilder = analysis.getErrorBuilder();
+ ErrorMessage errorMessage =
+ new ErrorMessage(
+ ErrorMessage.MessageTypes.ASSIGN_GENERIC_NULLABLE,
+ String.format(
+ "Conditional expression must have type "
+ + prettyTypeForError(expressionType, state)
+ + " but the sub-expression has type "
+ + prettyTypeForError(subPartType, state)
+ + ", which has mismatched nullability of type parameters"));
+ state.reportMatch(
+ errorBuilder.createErrorDescription(
+ errorMessage, analysis.buildDescription(tree), state, null));
+ }
+
+ private static void reportInvalidParametersNullabilityError(
+ Type formalParameterType,
+ Type actualParameterType,
+ ExpressionTree paramExpression,
+ VisitorState state,
+ NullAway analysis) {
+ ErrorBuilder errorBuilder = analysis.getErrorBuilder();
+ ErrorMessage errorMessage =
+ new ErrorMessage(
+ ErrorMessage.MessageTypes.PASS_NULLABLE_GENERIC,
+ "Cannot pass parameter of type "
+ + prettyTypeForError(actualParameterType, state)
+ + ", as formal parameter has type "
+ + prettyTypeForError(formalParameterType, state)
+ + ", which has mismatched type parameter nullability");
+ state.reportMatch(
+ errorBuilder.createErrorDescription(
+ errorMessage, analysis.buildDescription(paramExpression), state, null));
+ }
+
+ private static void reportInvalidOverridingMethodReturnTypeError(
+ Tree methodTree,
+ Type overriddenMethodReturnType,
+ Type overridingMethodReturnType,
+ NullAway analysis,
+ VisitorState state) {
+ ErrorBuilder errorBuilder = analysis.getErrorBuilder();
+ ErrorMessage errorMessage =
+ new ErrorMessage(
+ ErrorMessage.MessageTypes.WRONG_OVERRIDE_RETURN_GENERIC,
+ "Method returns "
+ + prettyTypeForError(overridingMethodReturnType, state)
+ + ", but overridden method returns "
+ + prettyTypeForError(overriddenMethodReturnType, state)
+ + ", which has mismatched type parameter nullability");
+ state.reportMatch(
+ errorBuilder.createErrorDescription(
+ errorMessage, analysis.buildDescription(methodTree), state, null));
+ }
+
+ private static void reportInvalidOverridingMethodParamTypeError(
+ Tree formalParameterTree,
+ Type typeParameterType,
+ Type methodParamType,
+ NullAway analysis,
+ VisitorState state) {
+ ErrorBuilder errorBuilder = analysis.getErrorBuilder();
+ ErrorMessage errorMessage =
+ new ErrorMessage(
+ ErrorMessage.MessageTypes.WRONG_OVERRIDE_PARAM_GENERIC,
+ "Parameter has type "
+ + prettyTypeForError(methodParamType, state)
+ + ", but overridden method has parameter type "
+ + prettyTypeForError(typeParameterType, state)
+ + ", which has mismatched type parameter nullability");
+ state.reportMatch(
+ errorBuilder.createErrorDescription(
+ errorMessage, analysis.buildDescription(formalParameterTree), state, null));
+ }
+
+ /**
+ * This method returns the type of the given tree, including any type use annotations.
+ *
+ * <p>This method is required because in some cases, the type returned by {@link
+ * com.google.errorprone.util.ASTHelpers#getType(Tree)} fails to preserve type use annotations,
+ * particularly when dealing with {@link com.sun.source.tree.NewClassTree} (e.g., {@code new
+ * Foo<@Nullable A>}).
+ *
+ * @param tree A tree for which we need the type with preserved annotations.
+ * @param state the visitor state
+ * @return Type of the tree with preserved annotations.
+ */
+ @Nullable
+ private static Type getTreeType(Tree tree, VisitorState state) {
+ if (tree instanceof NewClassTree
+ && ((NewClassTree) tree).getIdentifier() instanceof ParameterizedTypeTree) {
+ ParameterizedTypeTree paramTypedTree =
+ (ParameterizedTypeTree) ((NewClassTree) tree).getIdentifier();
+ if (paramTypedTree.getTypeArguments().isEmpty()) {
+ // diamond operator, which we do not yet support; for now, return null
+ // TODO: support diamond operators
+ return null;
+ }
+ return typeWithPreservedAnnotations(paramTypedTree, state);
+ } else {
+ Type result = ASTHelpers.getType(tree);
+ if (result != null && result.isRaw()) {
+ // bail out of any checking involving raw types for now
+ return null;
+ }
+ return result;
+ }
+ }
+
+ /**
+ * For a tree representing an assignment, ensures that from the perspective of type parameter
+ * nullability, the type of the right-hand side is assignable to (a subtype of) the type of the
+ * left-hand side. This check ensures that for every parameterized type nested in each of the
+ * types, the type parameters have identical nullability.
+ *
+ * @param tree the tree to check, which must be either an {@link AssignmentTree} or a {@link
+ * VariableTree}
+ * @param analysis the analysis object
+ * @param state the visitor state
+ */
+ public static void checkTypeParameterNullnessForAssignability(
+ Tree tree, NullAway analysis, VisitorState state) {
+ if (!analysis.getConfig().isJSpecifyMode()) {
+ return;
+ }
+ Tree lhsTree;
+ Tree rhsTree;
+ if (tree instanceof VariableTree) {
+ VariableTree varTree = (VariableTree) tree;
+ lhsTree = varTree.getType();
+ rhsTree = varTree.getInitializer();
+ } else {
+ AssignmentTree assignmentTree = (AssignmentTree) tree;
+ lhsTree = assignmentTree.getVariable();
+ rhsTree = assignmentTree.getExpression();
+ }
+ // rhsTree can be null for a VariableTree. Also, we don't need to do a check
+ // if rhsTree is the null literal
+ if (rhsTree == null || rhsTree.getKind().equals(Tree.Kind.NULL_LITERAL)) {
+ return;
+ }
+ Type lhsType = getTreeType(lhsTree, state);
+ Type rhsType = getTreeType(rhsTree, state);
+
+ if (lhsType instanceof Type.ClassType && rhsType instanceof Type.ClassType) {
+ boolean isAssignmentValid = compareNullabilityAnnotations(lhsType, rhsType, state);
+ if (!isAssignmentValid) {
+ reportInvalidAssignmentInstantiationError(tree, lhsType, rhsType, state, analysis);
+ }
+ }
+ }
+
+ /**
+ * Checks that the nullability of type parameters for a returned expression matches that of the
+ * type parameters of the enclosing method's return type.
+ *
+ * @param retExpr the returned expression
+ * @param methodSymbol symbol for enclosing method
+ * @param analysis the analysis object
+ * @param state the visitor state
+ */
+ public static void checkTypeParameterNullnessForFunctionReturnType(
+ ExpressionTree retExpr,
+ Symbol.MethodSymbol methodSymbol,
+ NullAway analysis,
+ VisitorState state) {
+ if (!analysis.getConfig().isJSpecifyMode()) {
+ return;
+ }
+
+ Type formalReturnType = methodSymbol.getReturnType();
+ // check nullability of parameters only for generics
+ if (formalReturnType.getTypeArguments().isEmpty()) {
+ return;
+ }
+ Type returnExpressionType = getTreeType(retExpr, state);
+ if (formalReturnType instanceof Type.ClassType
+ && returnExpressionType instanceof Type.ClassType) {
+ boolean isReturnTypeValid =
+ compareNullabilityAnnotations(formalReturnType, returnExpressionType, state);
+ if (!isReturnTypeValid) {
+ reportInvalidReturnTypeError(
+ retExpr, formalReturnType, returnExpressionType, state, analysis);
+ }
+ }
+ }
+
+ /**
+ * Compare two types from an assignment for identical type parameter nullability, recursively
+ * checking nested generic types. See <a
+ * href="https://jspecify.dev/docs/spec/#nullness-delegating-subtyping">the JSpecify
+ * specification</a> and <a
+ * href="https://docs.oracle.com/javase/specs/jls/se14/html/jls-4.html#jls-4.10.2">the JLS
+ * subtyping rules for class and interface types</a>.
+ *
+ * @param lhsType type for the lhs of the assignment
+ * @param rhsType type for the rhs of the assignment
+ * @param state the visitor state
+ */
+ private static boolean compareNullabilityAnnotations(
+ Type lhsType, Type rhsType, VisitorState state) {
+ // it is fair to assume rhyType should be the same as lhsType as the Java compiler has passed
+ // before NullAway.
+ return lhsType.accept(new CompareNullabilityVisitor(state), rhsType);
+ }
+
+ /**
+ * For the Parameterized typed trees, ASTHelpers.getType(tree) does not return a Type with
+ * preserved annotations. This method takes a Parameterized typed tree as an input and returns the
+ * Type of the tree with the annotations.
+ *
+ * @param tree A parameterized typed tree for which we need class type with preserved annotations.
+ * @param state the visitor state
+ * @return A Type with preserved annotations.
+ */
+ private static Type.ClassType typeWithPreservedAnnotations(
+ ParameterizedTypeTree tree, VisitorState state) {
+ return (Type.ClassType) tree.accept(new PreservedAnnotationTreeVisitor(state), null);
+ }
+
+ /**
+ * For a conditional expression <em>c</em>, check whether the type parameter nullability for each
+ * sub-expression of <em>c</em> matches the type parameter nullability of <em>c</em> itself.
+ *
+ * <p>Note that the type parameter nullability for <em>c</em> is computed by javac and reflects
+ * what is required of the surrounding context (an assignment, parameter pass, etc.). It is
+ * possible that both sub-expressions of <em>c</em> will have identical type parameter
+ * nullability, but will still not match the type parameter nullability of <em>c</em> itself, due
+ * to requirements from the surrounding context. In such a case, our error messages may be
+ * somewhat confusing; we may want to improve this in the future.
+ *
+ * @param tree A conditional expression tree to check
+ * @param analysis the analysis object
+ * @param state the visitor state
+ */
+ public static void checkTypeParameterNullnessForConditionalExpression(
+ ConditionalExpressionTree tree, NullAway analysis, VisitorState state) {
+ if (!analysis.getConfig().isJSpecifyMode()) {
+ return;
+ }
+
+ Tree truePartTree = tree.getTrueExpression();
+ Tree falsePartTree = tree.getFalseExpression();
+
+ Type condExprType = getTreeType(tree, state);
+ Type truePartType = getTreeType(truePartTree, state);
+ Type falsePartType = getTreeType(falsePartTree, state);
+ // The condExpr type should be the least-upper bound of the true and false part types. To check
+ // the nullability annotations, we check that the true and false parts are assignable to the
+ // type of the whole expression
+ if (condExprType instanceof Type.ClassType) {
+ if (truePartType instanceof Type.ClassType) {
+ if (!compareNullabilityAnnotations(condExprType, truePartType, state)) {
+ reportMismatchedTypeForTernaryOperator(
+ truePartTree, condExprType, truePartType, state, analysis);
+ }
+ }
+ if (falsePartType instanceof Type.ClassType) {
+ if (!compareNullabilityAnnotations(condExprType, falsePartType, state)) {
+ reportMismatchedTypeForTernaryOperator(
+ falsePartTree, condExprType, falsePartType, state, analysis);
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks that for each parameter p at a call, the type parameter nullability for p's type matches
+ * that of the corresponding formal parameter. If a mismatch is found, report an error.
+ *
+ * @param formalParams the formal parameters
+ * @param actualParams the actual parameters
+ * @param isVarArgs true if the call is to a varargs method
+ * @param analysis the analysis object
+ * @param state the visitor state
+ */
+ public static void compareGenericTypeParameterNullabilityForCall(
+ List<Symbol.VarSymbol> formalParams,
+ List<? extends ExpressionTree> actualParams,
+ boolean isVarArgs,
+ NullAway analysis,
+ VisitorState state) {
+ if (!analysis.getConfig().isJSpecifyMode()) {
+ return;
+ }
+ int n = formalParams.size();
+ if (isVarArgs) {
+ // If the last argument is var args, don't check it now, it will be checked against
+ // all remaining actual arguments in the next loop.
+ n = n - 1;
+ }
+ for (int i = 0; i < n; i++) {
+ Type formalParameter = formalParams.get(i).type;
+ if (!formalParameter.getTypeArguments().isEmpty()) {
+ Type actualParameter = getTreeType(actualParams.get(i), state);
+ if (formalParameter instanceof Type.ClassType
+ && actualParameter instanceof Type.ClassType) {
+ if (!compareNullabilityAnnotations(formalParameter, actualParameter, state)) {
+ reportInvalidParametersNullabilityError(
+ formalParameter, actualParameter, actualParams.get(i), state, analysis);
+ }
+ }
+ }
+ }
+ if (isVarArgs && !formalParams.isEmpty()) {
+ Type.ArrayType varargsArrayType =
+ (Type.ArrayType) formalParams.get(formalParams.size() - 1).type;
+ Type varargsElementType = varargsArrayType.elemtype;
+ if (!varargsElementType.getTypeArguments().isEmpty()) {
+ for (int i = formalParams.size() - 1; i < actualParams.size(); i++) {
+ Type actualParameter = getTreeType(actualParams.get(i), state);
+ if (varargsElementType instanceof Type.ClassType
+ && actualParameter instanceof Type.ClassType) {
+ if (!compareNullabilityAnnotations(varargsElementType, actualParameter, state)) {
+ reportInvalidParametersNullabilityError(
+ varargsElementType, actualParameter, actualParams.get(i), state, analysis);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks that type parameter nullability is consistent between an overriding method and the
+ * corresponding overridden method.
+ *
+ * @param tree A method tree to check
+ * @param overridingMethod A symbol of the overriding method
+ * @param overriddenMethod A symbol of the overridden method
+ * @param analysis the analysis object
+ * @param state the visitor state
+ */
+ public static void checkTypeParameterNullnessForMethodOverriding(
+ MethodTree tree,
+ Symbol.MethodSymbol overridingMethod,
+ Symbol.MethodSymbol overriddenMethod,
+ NullAway analysis,
+ VisitorState state) {
+ if (!analysis.getConfig().isJSpecifyMode()) {
+ return;
+ }
+ // Obtain type parameters for the overridden method within the context of the overriding
+ // method's class
+ Type methodWithTypeParams =
+ state.getTypes().memberType(overridingMethod.owner.type, overriddenMethod);
+
+ checkTypeParameterNullnessForOverridingMethodReturnType(
+ tree, methodWithTypeParams, analysis, state);
+ checkTypeParameterNullnessForOverridingMethodParameterType(
+ tree, methodWithTypeParams, analysis, state);
+ }
+
+ /**
+ * Computes the nullability of the return type of some generic method when seen as a member of
+ * some class {@code C}, based on type parameter nullability within {@code C}.
+ *
+ * <p>Consider the following example:
+ *
+ * <pre>
+ * interface Fn<P extends @Nullable Object, R extends @Nullable Object> {
+ * R apply(P p);
+ * }
+ * class C implements Fn<String, @Nullable String> {
+ * public @Nullable String apply(String p) {
+ * return null;
+ * }
+ * }
+ * </pre>
+ *
+ * Within the context of class {@code C}, the method {@code Fn.apply} has a return type of
+ * {@code @Nullable String}, since {@code @Nullable String} is passed as the type parameter for
+ * {@code R}. Hence, it is valid for overriding method {@code C.apply} to return {@code @Nullable
+ * String}.
+ *
+ * @param method the generic method
+ * @param enclosingSymbol the enclosing class in which we want to know {@code method}'s return
+ * type nullability
+ * @param state Visitor state
+ * @param config The analysis config
+ * @return nullability of the return type of {@code method} in the context of {@code
+ * enclosingType}
+ */
+ public static Nullness getGenericMethodReturnTypeNullness(
+ Symbol.MethodSymbol method, Symbol enclosingSymbol, VisitorState state, Config config) {
+ Type enclosingType = getTypeForSymbol(enclosingSymbol, state);
+ return getGenericMethodReturnTypeNullness(method, enclosingType, state, config);
+ }
+
+ /**
+ * Get the type for the symbol, accounting for anonymous classes
+ *
+ * @param symbol the symbol
+ * @param state the visitor state
+ * @return the type for {@code symbol}
+ */
+ @Nullable
+ private static Type getTypeForSymbol(Symbol symbol, VisitorState state) {
+ if (symbol.isAnonymous()) {
+ // For anonymous classes, symbol.type does not contain annotations on generic type parameters.
+ // So, we get a correct type from the enclosing NewClassTree.
+ TreePath path = state.getPath();
+ NewClassTree newClassTree = ASTHelpers.findEnclosingNode(path, NewClassTree.class);
+ if (newClassTree == null) {
+ throw new RuntimeException(
+ "method should be inside a NewClassTree " + state.getSourceForNode(path.getLeaf()));
+ }
+ Type typeFromTree = getTreeType(newClassTree, state);
+ if (typeFromTree != null) {
+ verify(state.getTypes().isAssignable(symbol.type, typeFromTree));
+ }
+ return typeFromTree;
+ } else {
+ return symbol.type;
+ }
+ }
+
+ public static Nullness getGenericMethodReturnTypeNullness(
+ Symbol.MethodSymbol method, @Nullable Type enclosingType, VisitorState state, Config config) {
+ if (enclosingType == null) {
+ // we have no additional information from generics, so return NONNULL (presence of a @Nullable
+ // annotation should have been handled by the caller)
+ return Nullness.NONNULL;
+ }
+ Type overriddenMethodType = state.getTypes().memberType(enclosingType, method);
+ verify(
+ overriddenMethodType instanceof ExecutableType,
+ "expected ExecutableType but instead got %s",
+ overriddenMethodType.getClass());
+ return getTypeNullness(overriddenMethodType.getReturnType(), config);
+ }
+
+ /**
+ * Computes the nullness of the return of a generic method at an invocation, in the context of the
+ * declared type of its receiver argument. If the return type is a type variable, its nullness
+ * depends on the nullability of the corresponding type parameter in the receiver's type.
+ *
+ * <p>Consider the following example:
+ *
+ * <pre>
+ * interface Fn<P extends @Nullable Object, R extends @Nullable Object> {
+ * R apply(P p);
+ * }
+ * class C implements Fn<String, @Nullable String> {
+ * public @Nullable String apply(String p) {
+ * return null;
+ * }
+ * }
+ * static void m() {
+ * Fn<String, @Nullable String> f = new C();
+ * f.apply("hello").hashCode(); // NPE
+ * }
+ * </pre>
+ *
+ * The declared type of {@code f} passes {@code Nullable String} as the type parameter for type
+ * variable {@code R}. So, the call {@code f.apply("hello")} returns {@code @Nullable} and an
+ * error should be reported.
+ *
+ * @param invokedMethodSymbol symbol for the invoked method
+ * @param tree the tree for the invocation
+ * @return Nullness of invocation's return type, or {@code NONNULL} if the call does not invoke an
+ * instance method
+ */
+ public static Nullness getGenericReturnNullnessAtInvocation(
+ Symbol.MethodSymbol invokedMethodSymbol,
+ MethodInvocationTree tree,
+ VisitorState state,
+ Config config) {
+ if (!(tree.getMethodSelect() instanceof MemberSelectTree) || invokedMethodSymbol.isStatic()) {
+ return Nullness.NONNULL;
+ }
+ Type methodReceiverType =
+ getTreeType(((MemberSelectTree) tree.getMethodSelect()).getExpression(), state);
+ if (methodReceiverType == null) {
+ return Nullness.NONNULL;
+ } else {
+ return getGenericMethodReturnTypeNullness(
+ invokedMethodSymbol, methodReceiverType, state, config);
+ }
+ }
+
+ /**
+ * Computes the nullness of a formal parameter of a generic method at an invocation, in the
+ * context of the declared type of its receiver argument. If the formal parameter's type is a type
+ * variable, its nullness depends on the nullability of the corresponding type parameter in the
+ * receiver's type.
+ *
+ * <p>Consider the following example:
+ *
+ * <pre>
+ * interface Fn<P extends @Nullable Object, R extends @Nullable Object> {
+ * R apply(P p);
+ * }
+ * class C implements Fn<@Nullable String, String> {
+ * public String apply(@Nullable String p) {
+ * return "";
+ * }
+ * }
+ * static void m() {
+ * Fn<@Nullable String, String> f = new C();
+ * f.apply(null);
+ * }
+ * </pre>
+ *
+ * The declared type of {@code f} passes {@code Nullable String} as the type parameter for type
+ * variable {@code P}. So, it is legal to pass {@code null} as a parameter to {@code f.apply}.
+ *
+ * @param paramIndex parameter index
+ * @param invokedMethodSymbol symbol for the invoked method
+ * @param tree the tree for the invocation
+ * @param state the visitor state
+ * @param config the analysis config
+ * @return Nullness of parameter at {@code paramIndex}, or {@code NONNULL} if the call does not
+ * invoke an instance method
+ */
+ public static Nullness getGenericParameterNullnessAtInvocation(
+ int paramIndex,
+ Symbol.MethodSymbol invokedMethodSymbol,
+ MethodInvocationTree tree,
+ VisitorState state,
+ Config config) {
+ if (!(tree.getMethodSelect() instanceof MemberSelectTree) || invokedMethodSymbol.isStatic()) {
+ return Nullness.NONNULL;
+ }
+ Type enclosingType =
+ castToNonNull(
+ getTreeType(((MemberSelectTree) tree.getMethodSelect()).getExpression(), state));
+ return getGenericMethodParameterNullness(
+ paramIndex, invokedMethodSymbol, enclosingType, state, config);
+ }
+
+ /**
+ * Computes the nullability of a parameter type of some generic method when seen as a member of
+ * some class {@code C}, based on type parameter nullability within {@code C}.
+ *
+ * <p>Consider the following example:
+ *
+ * <pre>
+ * interface Fn<P extends @Nullable Object, R extends @Nullable Object> {
+ * R apply(P p);
+ * }
+ * class C implements Fn<@Nullable String, String> {
+ * public String apply(@Nullable String p) {
+ * return "";
+ * }
+ * }
+ * </pre>
+ *
+ * Within the context of class {@code C}, the method {@code Fn.apply} has a parameter type of
+ * {@code @Nullable String}, since {@code @Nullable String} is passed as the type parameter for
+ * {@code P}. Hence, overriding method {@code C.apply} must take a {@code @Nullable String} as a
+ * parameter.
+ *
+ * @param parameterIndex index of the parameter
+ * @param method the generic method
+ * @param enclosingSymbol the enclosing symbol in which we want to know {@code method}'s parameter
+ * type nullability
+ * @param state the visitor state
+ * @param config the config
+ * @return nullability of the relevant parameter type of {@code method} in the context of {@code
+ * enclosingSymbol}
+ */
+ public static Nullness getGenericMethodParameterNullness(
+ int parameterIndex,
+ Symbol.MethodSymbol method,
+ Symbol enclosingSymbol,
+ VisitorState state,
+ Config config) {
+ Type enclosingType = getTypeForSymbol(enclosingSymbol, state);
+ return getGenericMethodParameterNullness(parameterIndex, method, enclosingType, state, config);
+ }
+
+ /**
+ * Just like {@link #getGenericMethodParameterNullness(int, Symbol.MethodSymbol, Symbol,
+ * VisitorState, Config)}, but takes the enclosing {@code Type} rather than the enclosing {@code
+ * Symbol}.
+ *
+ * @param parameterIndex index of the parameter
+ * @param method the generic method
+ * @param enclosingType the enclosing type in which we want to know {@code method}'s parameter
+ * type nullability
+ * @param state the visitor state
+ * @param config the analysis config
+ * @return nullability of the relevant parameter type of {@code method} in the context of {@code
+ * enclosingType}
+ */
+ public static Nullness getGenericMethodParameterNullness(
+ int parameterIndex,
+ Symbol.MethodSymbol method,
+ @Nullable Type enclosingType,
+ VisitorState state,
+ Config config) {
+ if (enclosingType == null) {
+ // we have no additional information from generics, so return NONNULL (presence of a top-level
+ // @Nullable annotation is handled elsewhere)
+ return Nullness.NONNULL;
+ }
+ Type methodType = state.getTypes().memberType(enclosingType, method);
+ Type paramType = methodType.getParameterTypes().get(parameterIndex);
+ return getTypeNullness(paramType, config);
+ }
+
+ /**
+ * This method compares the type parameter annotations for overriding method parameters with
+ * corresponding type parameters for the overridden method and reports an error if they don't
+ * match
+ *
+ * @param tree tree for overriding method
+ * @param overriddenMethodType type of the overridden method
+ * @param analysis the analysis object
+ * @param state the visitor state
+ */
+ private static void checkTypeParameterNullnessForOverridingMethodParameterType(
+ MethodTree tree, Type overriddenMethodType, NullAway analysis, VisitorState state) {
+ List<? extends VariableTree> methodParameters = tree.getParameters();
+ List<Type> overriddenMethodParameterTypes = overriddenMethodType.getParameterTypes();
+ // TODO handle varargs; they are not handled for now
+ for (int i = 0; i < methodParameters.size(); i++) {
+ Type overridingMethodParameterType = ASTHelpers.getType(methodParameters.get(i));
+ Type overriddenMethodParameterType = overriddenMethodParameterTypes.get(i);
+ if (overriddenMethodParameterType instanceof Type.ClassType
+ && overridingMethodParameterType instanceof Type.ClassType) {
+ if (!compareNullabilityAnnotations(
+ overriddenMethodParameterType, overridingMethodParameterType, state)) {
+ reportInvalidOverridingMethodParamTypeError(
+ methodParameters.get(i),
+ overriddenMethodParameterType,
+ overridingMethodParameterType,
+ analysis,
+ state);
+ }
+ }
+ }
+ }
+
+ /**
+ * This method compares the type parameter annotations for an overriding method's return type with
+ * corresponding type parameters for the overridden method and reports an error if they don't
+ * match
+ *
+ * @param tree tree for overriding method
+ * @param overriddenMethodType type of the overridden method
+ * @param analysis the analysis object
+ * @param state the visitor state
+ */
+ private static void checkTypeParameterNullnessForOverridingMethodReturnType(
+ MethodTree tree, Type overriddenMethodType, NullAway analysis, VisitorState state) {
+ Type overriddenMethodReturnType = overriddenMethodType.getReturnType();
+ Type overridingMethodReturnType = ASTHelpers.getType(tree.getReturnType());
+ if (!(overriddenMethodReturnType instanceof Type.ClassType)) {
+ return;
+ }
+ Preconditions.checkArgument(overridingMethodReturnType instanceof Type.ClassType);
+ if (!compareNullabilityAnnotations(
+ overriddenMethodReturnType, overridingMethodReturnType, state)) {
+ reportInvalidOverridingMethodReturnTypeError(
+ tree, overriddenMethodReturnType, overridingMethodReturnType, analysis, state);
+ }
+ }
+
+ /**
+ * @param type A type for which we need the Nullness.
+ * @param config The analysis config
+ * @return Returns the Nullness of the type based on the Nullability annotation.
+ */
+ private static Nullness getTypeNullness(Type type, Config config) {
+ boolean hasNullableAnnotation =
+ Nullness.hasNullableAnnotation(type.getAnnotationMirrors().stream(), config);
+ if (hasNullableAnnotation) {
+ return Nullness.NULLABLE;
+ }
+ return Nullness.NONNULL;
+ }
+
+ /**
+ * Returns a pretty-printed representation of type suitable for error messages. The representation
+ * uses simple names rather than fully-qualified names, and retains all type-use annotations.
+ */
+ public static String prettyTypeForError(Type type, VisitorState state) {
+ return type.accept(new GenericTypePrettyPrintingVisitor(state), null);
+ }
+}