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 Nullability for lambda expression parameters for Generic Types #852

Merged
merged 9 commits into from
Oct 25, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import static com.uber.nullaway.Nullness.NONNULL;
import static com.uber.nullaway.Nullness.NULLABLE;

import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.VariableTree;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Types;
import com.sun.tools.javac.util.Context;
import com.uber.nullaway.CodeAnnotationInfo;
Expand Down Expand Up @@ -92,13 +94,27 @@ private static NullnessStore lambdaInitialStore(
Symbol.MethodSymbol fiMethodSymbol = NullabilityUtil.getFunctionalInterfaceMethod(code, types);
com.sun.tools.javac.util.List<Symbol.VarSymbol> fiMethodParameters =
fiMethodSymbol.getParameters();
// This obtains the types of the functional interface method parameters with preserved
// annotations in case of generic type arguments. Only used in JSpecify mode.
List<Type> overridenMethodParamTypeList =
types.memberType(ASTHelpers.getType(code), fiMethodSymbol).getParameterTypes();
// If fiArgumentPositionNullness[i] == null, parameter position i is unannotated
Nullness[] fiArgumentPositionNullness = new Nullness[fiMethodParameters.size()];
final boolean isFIAnnotated = !codeAnnotationInfo.isSymbolUnannotated(fiMethodSymbol, config);
if (isFIAnnotated) {
for (int i = 0; i < fiMethodParameters.size(); i++) {
fiArgumentPositionNullness[i] =
Nullness.hasNullableAnnotation(fiMethodParameters.get(i), config) ? NULLABLE : NONNULL;
if (Nullness.hasNullableAnnotation(fiMethodParameters.get(i), config)) {
// Get the Nullness if the Annotation is directly written with the parameter
fiArgumentPositionNullness[i] = NULLABLE;
} else if (config.isJSpecifyMode()
&& Nullness.hasNullableAnnotation(
overridenMethodParamTypeList.get(i).getAnnotationMirrors().stream(), config)) {
// Get the Nullness if the Annotation is indirectly applied through a generic type if we
// are in JSpecify mode
fiArgumentPositionNullness[i] = NULLABLE;
} else {
fiArgumentPositionNullness[i] = NONNULL;
}
}
}
fiArgumentPositionNullness =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -596,8 +596,29 @@ public void testForLambdasInAnAssignment() {
" String function(T1 o);",
" }",
" static void testPositive() {",
" // TODO: we should report an error here, since the lambda cannot take",
" // a @Nullable parameter. we don't catch this yet",
" // BUG: Diagnostic contains: dereferenced expression o is @Nullable",
" A<@Nullable Object> p = o -> o.toString();",
" }",
" static void testNegative() {",
" A<Object> p = o -> o.toString();",
" }",
"}")
.doTest();
}

@Test
public void testForLambdasInAnAssignmentWithoutJSpecifyMode() {
makeHelperWithoutJSpecifyMode()
.addSourceLines(
"Test.java",
"package com.uber;",
"import org.jspecify.annotations.Nullable;",
"class Test {",
" interface A<T1 extends @Nullable Object> {",
" String function(T1 o);",
" }",
" static void testPositive() {",
" // Using outside JSpecify Mode So we don't get a bug here",
" A<@Nullable Object> p = o -> o.toString();",
" }",
" static void testNegative() {",
Expand Down Expand Up @@ -1427,4 +1448,8 @@ private CompilationTestHelper makeHelper() {
Arrays.asList(
"-XepOpt:NullAway:AnnotatedPackages=com.uber", "-XepOpt:NullAway:JSpecifyMode=true"));
}

private CompilationTestHelper makeHelperWithoutJSpecifyMode() {
return makeTestHelperWithArgs(Arrays.asList("-XepOpt:NullAway:AnnotatedPackages=com.uber"));
}
}