Repository navigation
Add tests for the JSpecify nullness operator - #1767
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review. WalkthroughAdded a JSpecify-focused NullAway regression suite for the Merge Risk: ⚪ Minimal · up to This PR adds nullness regression tests without changing production behavior, dependencies, or deployment configuration. No actionable merge-blocking risk remains after normal checks and review. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Record the current behavior before implementing uber#1727: which diagnostics NullAway already gets right through `? extends T`, and which false positives remain. The regression guards exist because a fix can easily suppress valid diagnostics for explicitly @nullable members and library-modeled nullable methods. Co-authored-by: GPT-5.6 Sol Medium <noreply@openai.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
02181e4 to
03f96ae
Compare
msridhar
left a comment
There was a problem hiding this comment.
Thanks for these tests! I think I would like them in a separate test file though. Maybe NoChangeTests, with Javadoc on the class explaining what is being tested?
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #1767 +/- ##
=========================================
Coverage 87.63% 87.63%
Complexity 3276 3276
=========================================
Files 109 109
Lines 11043 11043
Branches 2235 2235
=========================================
Hits 9678 9678
Misses 641 641
Partials 724 724 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
What does "NoChangeTests" means? Let me think what could be the better class name or class names. |
|
Ah, I thought all the tests related to JSpecify's |
|
Will do, and the class comment is a good call — it forces the set to state what it guards. On the name: I would lean toward What movesAll 24 tests, including the seven that guard explicitly Those seven are tempting to leave behind, since they sit next to
Two tests get a cross-reference on the way out, so they do not read as copies of what stays behind: I would also drop the Class comment/**
* Fails when a value whose type is a type variable, or a wildcard captured against one, changes how
* it is treated at an assignment sink or at a dereference.
*
* <p>Such a value carries the JSpecify {@code NO_CHANGE} nullness operator. It satisfies the same
* type variable at a return, an argument, or a field assignment, and it stays unsafe to dereference
* while the type variable has a {@code @Nullable} upper bound. An explicit {@code @Nullable},
* declared on the member or supplied by a library model, overrides that and is reported at every
* sink.
*
* <p>The green tests pin the second half of that rule, which a fix for the first half can break
* without failing anything else. The tests annotated {@code @Ignore} describe the false positives
* from issue #1727, where a value obtained through {@code ? extends T} is rejected at a sink that
* requires {@code T}. A fix turns those green and leaves the rest of this class alone.
*
* @see <a href="https://jspecify.dev/docs/spec/#nullness-operator">JSpecify: nullness operator</a>
*/Two things I would keep out of this PRThree tests already in Splitting the rest of |
|
Sounds great to me! I like the name Could you also include this test as ignored, to reinforce that the issues go beyond wildcards: @NullMarked
class Test {
static class Foo<T extends @Nullable Object> {
int test(T t) {
return t.hashCode(); // there should be a warning here!
}
}
}(from #1727 (comment)) |
Rename the sixteen passing tests after the property each establishes, now that the class name carries the issue context. The nine ignored ones keep the issue prefix, since their names double as the checklist for removing the annotations. Add an ignored test for a dereference of a type variable with a nullable upper bound, which reproduces the defect without a wildcard. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Done, and thanks for the extra case — it is a good one, because it makes the two halves of the class disagree with each other. NullAway reports It is in as Also in this push:
I retitled the PR, since "wildcard" no longer covers the set. Verification: The A/B/C question in the description is still open whenever you get to it. The new test sits outside all three, since it is about a dereference rather than about a sink. |
This PR adds tests only. It changes no production code.
Why
A fix for #1727 has a failure mode that is easy to miss: while making the reported false positives go away, it can also stop reporting values that really are nullable. While prototyping, I hit exactly that: an explicitly
@Nullablemember and a library-modeledMap.getinvoked through? extends Tboth stopped producing diagnostics, and the existing suite stayed green.As @msridhar notes in the issue, the fix needs NullAway to model the JSpecify
NO_CHANGEnullness operator rather than treating expressions as either@Nullableor@NonNull. These tests pin what must not change while that work happens, so a later implementation PR has something to fail against.What
The tests live in a new
NullnessOperatorTestsclass. Sixteen of them record behavior that is already correct today:@Nullablemember reached throughNBox<? extends T>is rejected at return, argument, and field-assignment sinks, and throughNBox<T>as a control.@Nullableresult ofMap.getis rejected throughMap<String, ? extends T>and throughMap<String, T>as a control.Box<? extends T>is rejected when dereferenced, directly or through a local.Tparameter is rejected once the local has been overwritten withnull.Box<? extends @Nullable T>and a@NonNull Treturn target are both rejected.Box<T>, a nonnull-boundedT, a concrete wildcard bound, and a nested generic bound are all accepted.Nine tests describe the defects and carry
@Ignore("https://github.com/uber/NullAway/issues/1727"). Eight are false positives, where a value obtained through? extends Tis rejected at a sink that requiresT:issue1727WildcardReturnFalsePositiveissue1727WildcardLocalArgumentFalsePositiveissue1727WildcardDirectArgumentFalsePositiveissue1727WildcardFieldAssignmentFalsePositiveissue1727IteratorFieldAssignmentFalsePositiveissue1727AggregateFalsePositiveissue1727LambdaReturnFalsePositiveissue1727ArrayStoreFalsePositiveThe ninth is a false negative, and it involves no wildcard:
issue1727NullableBoundDereferenceFalseNegative— dereferencing aTparameter goes unreported whileT extends @Nullable Object. NullAway does report the same dereference when the value arrives throughBox<? extends T>, so the two halves of this class disagree with each other today.The two field-assignment tests also cover the cascading
initializer method does not guarantee @NonNull fieldwarning:masterreports both assignment diagnostics and the cascading initializer diagnostic in each test, and a fix should leave zero.JUnit 4 does not fail when an ignored test starts passing, so that list doubles as the checklist for removing the annotations in a later PR.
Open question
Would this split work, so that A can merge on its own?
NullnessStorealone.T value = box.get(); sink(value).issue1727NullableBoundDereferenceFalseNegativesits outside all three, since it is about a dereference rather than a sink.How to verify
./gradlew --quiet :nullaway:test --tests "com.uber.nullaway.jspecify.NullnessOperatorTests"25 tests, 9 skipped, no failures.
./gradlew --quiet :nullaway:test --tests "com.uber.nullaway.jspecify.WildcardTests"45 tests, no skips, no failures. This is the class the tests moved out of.
996 tests, 20 skipped, no failures.
Compiles NullAway with NullAway.
Removing the nine
@Ignoreannotations produces exactly nine failures, one per method, so none of them passes by accident.Summary by CodeRabbit
#1727cases as ignored tests pending resolution.