Skip to content

Commit b84feb7

Browse files
authored
Don't treat @ParametricNullness as @Nullable in JSpecify mode (#1174)
Fixes #1173 In JSpecify mode, we should rely on our checking of generic types and not treat `@ParametricNullness` as a `@Nullable` annotation.
1 parent 3da2c82 commit b84feb7

File tree

4 files changed

+104
-8
lines changed

4 files changed

+104
-8
lines changed

guava-recent-unit-tests/build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ dependencies {
3333
exclude group: "junit", module: "junit"
3434
}
3535
testImplementation deps.test.jsr305Annotations
36-
testImplementation "com.google.guava:guava:31.1-jre"
36+
testImplementation "com.google.guava:guava:33.4.5-jre"
3737

3838
errorProneOldest deps.build.errorProneCheckApiOld
3939
errorProneOldest(deps.build.errorProneTestHelpersOld) {

guava-recent-unit-tests/src/test/java/com/uber/nullaway/guava/NullAwayGuavaParametricNullnessTests.java

+92
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@
1919
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2020
* THE SOFTWARE.
2121
*/
22+
package com.uber.nullaway.guava;
2223

2324
import com.google.errorprone.CompilationTestHelper;
2425
import com.uber.nullaway.NullAway;
2526
import java.util.Arrays;
27+
import org.junit.Assume;
2628
import org.junit.Before;
2729
import org.junit.Rule;
2830
import org.junit.Test;
@@ -33,6 +35,8 @@ public class NullAwayGuavaParametricNullnessTests {
3335

3436
private CompilationTestHelper defaultCompilationHelper;
3537

38+
private CompilationTestHelper jspecifyCompilationHelper;
39+
3640
@Before
3741
public void setup() {
3842
defaultCompilationHelper =
@@ -43,6 +47,14 @@ public void setup() {
4347
temporaryFolder.getRoot().getAbsolutePath(),
4448
"-XepOpt:NullAway:AnnotatedPackages=com.uber,com.google.common",
4549
"-XepOpt:NullAway:UnannotatedSubPackages=com.uber.nullaway.[a-zA-Z0-9.]+.unannotated"));
50+
jspecifyCompilationHelper =
51+
CompilationTestHelper.newInstance(NullAway.class, getClass())
52+
.setArgs(
53+
Arrays.asList(
54+
"-d",
55+
temporaryFolder.getRoot().getAbsolutePath(),
56+
"-XepOpt:NullAway:OnlyNullMarked=true",
57+
"-XepOpt:NullAway:JSpecifyMode=true"));
4658
}
4759

4860
@Test
@@ -70,6 +82,33 @@ public void testFutureCallbackParametricNullness() {
7082
.doTest();
7183
}
7284

85+
@Test
86+
public void jspecifyFutureCallback() {
87+
// to ensure javac reads proper generic types from the Guava jar
88+
Assume.assumeTrue(Runtime.version().feature() >= 23);
89+
jspecifyCompilationHelper
90+
.addSourceLines(
91+
"Test.java",
92+
"import com.google.common.util.concurrent.FutureCallback;",
93+
"import org.jspecify.annotations.*;",
94+
"@NullMarked",
95+
"class Test {",
96+
" public static <T> FutureCallback<@Nullable T> wrapFutureCallback(FutureCallback<@Nullable T> futureCallback) {",
97+
" return new FutureCallback<@Nullable T>() {",
98+
" @Override",
99+
" public void onSuccess(@Nullable T result) {",
100+
" futureCallback.onSuccess(result);",
101+
" }",
102+
" @Override",
103+
" public void onFailure(Throwable throwable) {",
104+
" futureCallback.onFailure(throwable);",
105+
" }",
106+
" };",
107+
" }",
108+
"}")
109+
.doTest();
110+
}
111+
73112
@Test
74113
public void testIterableParametricNullness() {
75114
defaultCompilationHelper
@@ -91,6 +130,59 @@ public void testIterableParametricNullness() {
91130
.doTest();
92131
}
93132

133+
@Test
134+
public void jspecifyIterables() {
135+
// to ensure javac reads proper generic types from the Guava jar
136+
Assume.assumeTrue(Runtime.version().feature() >= 23);
137+
jspecifyCompilationHelper
138+
.addSourceLines(
139+
"Test.java",
140+
"import com.google.common.collect.ImmutableList;",
141+
"import com.google.common.collect.Iterables;",
142+
"import org.jspecify.annotations.*;",
143+
"@NullMarked",
144+
"class Test {",
145+
" public static String test1() {",
146+
" // BUG: Diagnostic contains: returning @Nullable expression",
147+
" return Iterables.<@Nullable String>getFirst(ImmutableList.<String>of(), null);",
148+
" }",
149+
" public static @Nullable String test2() {",
150+
" return Iterables.<@Nullable String>getFirst(ImmutableList.<String>of(), null);",
151+
" }",
152+
" public static String test3() {",
153+
" return Iterables.getOnlyElement(ImmutableList.of(\"hi\"));",
154+
" }",
155+
" public static String test4() {",
156+
" // BUG: Diagnostic contains: returning @Nullable expression",
157+
" return Iterables.<@Nullable String>getOnlyElement(ImmutableList.of(\"hi\"));",
158+
" }",
159+
"}")
160+
.doTest();
161+
}
162+
163+
@Test
164+
public void jspecifyComparators() {
165+
// to ensure javac reads proper generic types from the Guava jar
166+
Assume.assumeTrue(Runtime.version().feature() >= 23);
167+
jspecifyCompilationHelper
168+
.addSourceLines(
169+
"Test.java",
170+
"import com.google.common.collect.Comparators;",
171+
"import java.util.Comparator;",
172+
"import org.jspecify.annotations.*;",
173+
"@NullMarked",
174+
"class Test {",
175+
" public static String test1(String t1, String t2, Comparator<? super String> cmp) {",
176+
" return Comparators.min(t1, t2, cmp);",
177+
" }",
178+
" public static String test2(@Nullable String t1, @Nullable String t2, Comparator<? super @Nullable String> cmp) {",
179+
" // BUG: Diagnostic contains: returning @Nullable expression",
180+
" return Comparators.<@Nullable String>min(t1, t2, cmp);",
181+
" }",
182+
"}")
183+
.doTest();
184+
}
185+
94186
@Test
95187
public void testCloserParametricNullness() {
96188
defaultCompilationHelper

nullaway/src/main/java/com/uber/nullaway/Nullness.java

+8-6
Original file line numberDiff line numberDiff line change
@@ -183,12 +183,14 @@ public static boolean isNullableAnnotation(String annotName, Config config) {
183183
|| annotName.endsWith(".checkerframework.checker.nullness.compatqual.NullableDecl")
184184
// matches javax.annotation.CheckForNull and edu.umd.cs.findbugs.annotations.CheckForNull
185185
|| annotName.endsWith(".CheckForNull")
186-
// matches any of the multiple @ParametricNullness annotations used within Guava
187-
// (see https://github.com/google/guava/issues/6126)
188-
// We check the simple name first and the package prefix second for boolean short
189-
// circuiting, as Guava includes
190-
// many annotations
191-
|| (annotName.endsWith(".ParametricNullness") && annotName.startsWith("com.google.common."))
186+
// matches any of the multiple @ParametricNullness annotations used within Guava (see
187+
// https://github.com/google/guava/issues/6126). We check the simple name first and the
188+
// package prefix second for boolean short circuiting, as Guava includes many annotations.
189+
// We do _not_ match @ParametricNullness annotations in JSpecify mode and rely on generics
190+
// checking instead.
191+
|| (!config.isJSpecifyMode()
192+
&& annotName.endsWith(".ParametricNullness")
193+
&& annotName.startsWith("com.google.common."))
192194
|| (config.acknowledgeAndroidRecent()
193195
&& annotName.equals("androidx.annotation.RecentlyNullable"))
194196
|| config.isCustomNullableAnnotation(annotName);

nullaway/src/main/java/com/uber/nullaway/handlers/StreamNullabilityPropagator.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
* THE SOFTWARE.
2323
*/
2424

25+
import static com.uber.nullaway.NullabilityUtil.castToNonNull;
26+
2527
import com.google.auto.value.AutoValue;
2628
import com.google.common.collect.ImmutableList;
2729
import com.google.common.collect.ImmutableMap;
@@ -377,7 +379,7 @@ private void handleChainFromFilter(
377379
} else if (collectCallToRecordsAndInnerMethodsOrLambdas.containsKey(outerCallInChain)) {
378380
// Update mapOrCollectRecordToFilterMap for all relevant methods / lambdas
379381
for (CollectRecordAndInnerMethod collectRecordAndInnerMethod :
380-
collectCallToRecordsAndInnerMethodsOrLambdas.get(outerCallInChain)) {
382+
collectCallToRecordsAndInnerMethodsOrLambdas.get(castToNonNull(outerCallInChain))) {
381383
MapOrCollectMethodToFilterInstanceRecord record =
382384
new MapOrCollectMethodToFilterInstanceRecord(
383385
collectRecordAndInnerMethod.getCollectLikeMethodRecord(), filterMethodOrLambda);

0 commit comments

Comments
 (0)