Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JSpecify: handle return types of method references in Java Generics #847

Merged
14 changes: 7 additions & 7 deletions nullaway/src/main/java/com/uber/nullaway/GenericsChecks.java
Original file line number Diff line number Diff line change
Expand Up @@ -699,11 +699,6 @@ public static void checkTypeParameterNullnessForMethodOverriding(
public static Nullness getGenericMethodReturnTypeNullness(
Symbol.MethodSymbol method, Symbol enclosingSymbol, 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 getGenericMethodReturnTypeNullness(method, enclosingType, state, config);
}

Expand Down Expand Up @@ -735,8 +730,13 @@ private static Type getTypeForSymbol(Symbol symbol, VisitorState state) {
}
}

private static Nullness getGenericMethodReturnTypeNullness(
Symbol.MethodSymbol method, Type enclosingType, VisitorState state, Config config) {
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;
msridhar marked this conversation as resolved.
Show resolved Hide resolved
}
Type overriddenMethodType = state.getTypes().memberType(enclosingType, method);
verify(
overriddenMethodType instanceof ExecutableType,
Expand Down
19 changes: 14 additions & 5 deletions nullaway/src/main/java/com/uber/nullaway/NullAway.java
Original file line number Diff line number Diff line change
Expand Up @@ -955,7 +955,8 @@ private Description checkOverriding(
// if the super method returns nonnull, overriding method better not return nullable
// Note that, for the overriding method, the permissive default is non-null,
// but it's nullable for the overridden one.
if (overriddenMethodReturnsNonNull(overriddenMethod, overridingMethod.owner, state)
if (overriddenMethodReturnsNonNull(
overriddenMethod, overridingMethod.owner, memberReferenceTree, state)
&& getMethodReturnNullness(overridingMethod, state, Nullness.NONNULL)
.equals(Nullness.NULLABLE)
&& (memberReferenceTree == null
Expand Down Expand Up @@ -994,7 +995,10 @@ && getMethodReturnNullness(overridingMethod, state, Nullness.NONNULL)
}

private boolean overriddenMethodReturnsNonNull(
Symbol.MethodSymbol overriddenMethod, Symbol enclosingSymbol, VisitorState state) {
Symbol.MethodSymbol overriddenMethod,
Symbol enclosingSymbol,
@Nullable MemberReferenceTree memberReferenceTree,
VisitorState state) {
Nullness methodReturnNullness =
getMethodReturnNullness(overriddenMethod, state, Nullness.NULLABLE);
if (!methodReturnNullness.equals(Nullness.NONNULL)) {
Expand All @@ -1003,9 +1007,14 @@ private boolean overriddenMethodReturnsNonNull(
// In JSpecify mode, for generic methods, we additionally need to check the return nullness
// using the type parameters from the type enclosing the overriding method
if (config.isJSpecifyMode()) {
return GenericsChecks.getGenericMethodReturnTypeNullness(
overriddenMethod, enclosingSymbol, state, config)
.equals(Nullness.NONNULL);

return (memberReferenceTree != null)
? GenericsChecks.getGenericMethodReturnTypeNullness(
overriddenMethod, ASTHelpers.getType(memberReferenceTree), state, config)
.equals(Nullness.NONNULL)
: GenericsChecks.getGenericMethodReturnTypeNullness(
overriddenMethod, enclosingSymbol, state, config)
.equals(Nullness.NONNULL);
akulk022 marked this conversation as resolved.
Show resolved Hide resolved
}
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,31 @@ public void testForMethodReferenceInAnAssignment() {
.doTest();
}

@Test
public void testForMethodReferenceReturnType() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As noted in an offline discussion, this test looks good but let's add tests for more (pseudo-)assignments, like passing a method reference as a function parameter and returning a method reference

makeHelper()
.addSourceLines(
"Test.java",
"package com.uber;",
"import org.jspecify.annotations.Nullable;",
"class Test {",
" interface A<T1 extends @Nullable Object> {",
" T1 function(Object o);",
" }",
" static @Nullable String foo(Object o) {",
" return o.toString();",
" }",
" static void testPositive() {",
" // BUG: Diagnostic contains: referenced method returns @Nullable",
" A<String> p = Test::foo;",
" }",
" static void testNegative() {",
" A<@Nullable String> p = Test::foo;",
" }",
"}")
.doTest();
}

@Test
public void testForLambdasInAnAssignment() {
makeHelper()
Expand Down