diff --git a/nullaway/src/main/java/com/uber/nullaway/GenericsChecks.java b/nullaway/src/main/java/com/uber/nullaway/GenericsChecks.java index ce31aebbef..045890d7d2 100644 --- a/nullaway/src/main/java/com/uber/nullaway/GenericsChecks.java +++ b/nullaway/src/main/java/com/uber/nullaway/GenericsChecks.java @@ -879,11 +879,6 @@ public static Nullness getGenericMethodParameterNullness( VisitorState state, Config config) { Type enclosingType = getTypeForSymbol(enclosingSymbol, state); - 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; - } return getGenericMethodParameterNullness(parameterIndex, method, enclosingType, state, config); } @@ -904,9 +899,14 @@ public static Nullness getGenericMethodParameterNullness( public static Nullness getGenericMethodParameterNullness( int parameterIndex, Symbol.MethodSymbol method, - Type enclosingType, + @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); diff --git a/nullaway/src/main/java/com/uber/nullaway/NullAway.java b/nullaway/src/main/java/com/uber/nullaway/NullAway.java index 79c7c8ca34..2da65c8eeb 100644 --- a/nullaway/src/main/java/com/uber/nullaway/NullAway.java +++ b/nullaway/src/main/java/com/uber/nullaway/NullAway.java @@ -731,17 +731,28 @@ private Description checkParamOverriding( // -XepOpt:NullAway:AcknowledgeRestrictiveAnnotations flag and its handler). if (isOverriddenMethodAnnotated) { for (int i = 0; i < superParamSymbols.size(); i++) { - overriddenMethodArgNullnessMap[i] = - Nullness.paramHasNullableAnnotation(overriddenMethod, i, config) - ? Nullness.NULLABLE - : (config.isJSpecifyMode() - ? GenericsChecks.getGenericMethodParameterNullness( - i, - overriddenMethod, - overridingParamSymbols.get(i).owner.owner, - state, - config) - : Nullness.NONNULL); + Nullness paramNullness; + if (Nullness.paramHasNullableAnnotation(overriddenMethod, i, config)) { + paramNullness = Nullness.NULLABLE; + } else if (config.isJSpecifyMode()) { + // Check if the parameter type is a type variable and the corresponding generic type + // argument is @Nullable + if (memberReferenceTree != null) { + // For a method reference, we get generic type arguments from the javac's inferred type + // for the tree, which seems to properly preserve type-use annotations + paramNullness = + GenericsChecks.getGenericMethodParameterNullness( + i, overriddenMethod, ASTHelpers.getType(memberReferenceTree), state, config); + } else { + // Use the enclosing class of the overriding method to find generic type arguments + paramNullness = + GenericsChecks.getGenericMethodParameterNullness( + i, overriddenMethod, overridingParamSymbols.get(i).owner.owner, state, config); + } + } else { + paramNullness = Nullness.NONNULL; + } + overriddenMethodArgNullnessMap[i] = paramNullness; } } diff --git a/nullaway/src/test/java/com/uber/nullaway/NullAwayJSpecifyGenericsTests.java b/nullaway/src/test/java/com/uber/nullaway/NullAwayJSpecifyGenericsTests.java index 7f236cacfd..f0e8827041 100644 --- a/nullaway/src/test/java/com/uber/nullaway/NullAwayJSpecifyGenericsTests.java +++ b/nullaway/src/test/java/com/uber/nullaway/NullAwayJSpecifyGenericsTests.java @@ -429,8 +429,7 @@ public void testForMethodReferenceInAnAssignment() { " return o.toString();", " }", " static void testPositive() {", - " // TODO: we should report an error here, since Test::foo cannot take", - " // a @Nullable parameter. we don't catch this yet", + " // BUG: Diagnostic contains: parameter o of referenced method is @NonNull", " A<@Nullable Object> p = Test::foo;", " }", " static void testNegative() {",