Skip to content

Checker to ensure explicit null marking of every class#1323

Merged
msridhar merged 10 commits intomasterfrom
new-force-annotation-checker
Nov 6, 2025
Merged

Checker to ensure explicit null marking of every class#1323
msridhar merged 10 commits intomasterfrom
new-force-annotation-checker

Conversation

@msridhar
Copy link
Copy Markdown
Collaborator

@msridhar msridhar commented Nov 2, 2025

NullAway has long supported the AnnotatedPackages setting to indicate that all packages within a namespace should be treated as "annotated" (i.e., "null-marked"), with packages treated as hierarchical. JSpecify instead requires writing @NullMarked annotations on every class / package / module to achieve the same effect, with packages not treated as hierarchical (matching Java). While using AnnotatedPackages is more succinct, it is not standard, and has the disadvantage that for a library, client code will not see the code as null-marked if it lacks the JSpecify marking annotations. However, using JSpecify marking annotations with NullAway has another disadvantage: if you forget to annotate a class / package / module as @NullMarked you won't get any warning; NullAway will simply treat the code as @NullUnmarked and not check it for nullness issues.

This PR adds a RequireExplicitNullMarking Error Prone checker to address the last problem. It reports an issue for any top-level class that does not have an explicit @NullMarked or @NullUnmarked annotation on itself, its containing package, and its containing module (if present). So, classes being compiled can no longer be defaulted into being @NullUnmarked; they need to be explicitly marked as such. This checker is opt-in and off by default, so there is no backward compatibility issue. This check will be helpful for code bases that want to transition from using the AnnotatedPackages setting to using standard JSpecify annotations.

Note there is the related AddNullMarkedToPackageInfo check built into Error Prone, which is complementary to this one.

Summary by CodeRabbit

  • New Features

    • Enforces explicit null-marking on top-level classes; package- or module-level null-marking satisfies the requirement and nested classes are not flagged when an enclosing scope is annotated.
  • Tests

    • Added unit tests validating missing annotations, direct null-marking and null-unmarked annotations, and package-level annotation inheritance across scenarios.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Nov 2, 2025

Walkthrough

Adds a new Error Prone BugChecker RequireExplicitNullMarking that enforces explicit null-marking on top-level classes by requiring a direct NullMarked or NullUnmarked annotation on the class or any enclosing symbol (package/module). The checker inspects top-level class declarations, walks enclosing symbols to detect those annotations, and reports a diagnostic when none are present. Also adds unit tests verifying missing annotations, direct NullMarked/NullUnmarked on classes, and package-level annotation behavior.

Suggested reviewers

  • lazaroclapp
  • yuxincs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "Checker to ensure explicit null marking of every class" accurately reflects the main change in the changeset. The PR introduces a new Error Prone BugChecker named RequireExplicitNullMarking that enforces explicit @NullMarked or @NullUnmarked annotations on top-level classes, along with corresponding test coverage. The title is clear, specific, and concise without vague language or unnecessary detail, directly capturing the primary objective of the changeset.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch new-force-annotation-checker

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d96ce1a and 6c49417.

📒 Files selected for processing (1)
  • nullaway/src/main/java/com/uber/nullaway/RequireExplicitNullMarking.java (1 hunks)
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: msridhar
Repo: uber/NullAway PR: 1248
File: nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java:847-857
Timestamp: 2025-08-28T04:54:20.953Z
Learning: In NullAway's GenericsChecks.java, NewClassTree support for explicit type argument substitution requires more extensive changes beyond just modifying the conditional in compareGenericTypeParameterNullabilityForCall. The maintainers prefer to handle NewClassTree support in a separate follow-up rather than expanding the scope of PRs focused on specific issues like super constructor calls.
Learnt from: msridhar
Repo: uber/NullAway PR: 1259
File: jdk-recent-unit-tests/src/test/java/com/uber/nullaway/jdk17/SwitchTests.java:318-321
Timestamp: 2025-08-29T18:41:43.584Z
Learning: Classes annotated with NullMarked are analyzed by NullAway even if they are not in packages specified by the AnnotatedPackages configuration. The NullMarked annotation guarantees NullAway analysis.
Learnt from: msridhar
Repo: uber/NullAway PR: 1245
File: guava-recent-unit-tests/src/test/java/com/uber/nullaway/guava/NullAwayGuavaParametricNullnessTests.java:101-102
Timestamp: 2025-08-14T18:50:06.159Z
Learning: In NullAway JSpecify tests, when JDK version requirements exist due to bytecode annotation reading capabilities, prefer failing tests over skipping them on unsupported versions to ensure CI catches regressions and enforces proper JDK version usage for developers.
📚 Learning: 2025-08-29T18:41:43.584Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1259
File: jdk-recent-unit-tests/src/test/java/com/uber/nullaway/jdk17/SwitchTests.java:318-321
Timestamp: 2025-08-29T18:41:43.584Z
Learning: Classes annotated with NullMarked are analyzed by NullAway even if they are not in packages specified by the AnnotatedPackages configuration. The NullMarked annotation guarantees NullAway analysis.

Applied to files:

  • nullaway/src/main/java/com/uber/nullaway/RequireExplicitNullMarking.java
📚 Learning: 2025-08-28T04:54:20.953Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1248
File: nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java:847-857
Timestamp: 2025-08-28T04:54:20.953Z
Learning: In NullAway's GenericsChecks.java, NewClassTree support for explicit type argument substitution requires more extensive changes beyond just modifying the conditional in compareGenericTypeParameterNullabilityForCall. The maintainers prefer to handle NewClassTree support in a separate follow-up rather than expanding the scope of PRs focused on specific issues like super constructor calls.

Applied to files:

  • nullaway/src/main/java/com/uber/nullaway/RequireExplicitNullMarking.java
📚 Learning: 2025-08-14T18:50:06.159Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1245
File: guava-recent-unit-tests/src/test/java/com/uber/nullaway/guava/NullAwayGuavaParametricNullnessTests.java:101-102
Timestamp: 2025-08-14T18:50:06.159Z
Learning: In NullAway JSpecify tests, when JDK version requirements exist due to bytecode annotation reading capabilities, prefer failing tests over skipping them on unsupported versions to ensure CI catches regressions and enforces proper JDK version usage for developers.

Applied to files:

  • nullaway/src/main/java/com/uber/nullaway/RequireExplicitNullMarking.java
📚 Learning: 2025-10-29T23:56:18.236Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1316
File: jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java:261-293
Timestamp: 2025-10-29T23:56:18.236Z
Learning: In NullAway's jdk-javac-plugin NullnessAnnotationSerializer, type variable bounds with annotations (e.g., `T extends Nullable Object`) are checked at their declaration sites by the typeParamHasAnnotation method for both class-level and method-level type parameters. The hasJSpecifyAnnotationDeep method is designed to check type uses (return types, parameters, etc.) and does not need a TYPEVAR case because type variable declaration bounds are already handled separately.

Applied to files:

  • nullaway/src/main/java/com/uber/nullaway/RequireExplicitNullMarking.java
🔇 Additional comments (5)
nullaway/src/main/java/com/uber/nullaway/RequireExplicitNullMarking.java (5)

1-20: LGTM! Copyright header is correct.

The previous issues with the duplicate comment opener and the copyright year have been resolved.


22-33: LGTM! Imports are appropriate.

All necessary Error Prone and compiler APIs are imported for implementing the checker.


35-46: LGTM! Well-structured BugChecker declaration.

The SUGGESTION severity is appropriate for an opt-in checker, and the summary clearly communicates the requirement to users.


48-63: LGTM! Correct implementation of the matching logic.

The method correctly:

  • Filters to top-level classes only (line 54-56)
  • Walks the enclosing symbol hierarchy starting from the class itself (line 57)
  • Returns NO_MATCH if any enclosing element has the required annotation (lines 58-60)
  • Reports a match only when no annotation is found anywhere in the hierarchy (line 62)

65-68: Simple name matching is flexible and appropriate here.

The helper uses simple name matching for the annotations, which will match @NullMarked or @NullUnmarked regardless of the package. This provides flexibility across different JSpecify versions or annotation packages, which aligns well with the checker's intent.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
nullaway/src/test/java/com/uber/nullaway/RequireExplicitNullMarkingTest.java (1)

58-66: Consider adding test coverage for module-level annotations.

The test correctly verifies package-level annotation behavior. However, the PR description mentions that the checker also supports module-level annotations ("its containing package, and its containing module (if present)"), but there's no test case for this scenario.

Additionally, consider adding test coverage for:

  • Enums, interfaces, and records (to ensure getNestingKind() == TOP_LEVEL handles all top-level type declarations)
  • Module-level @NullMarked annotation (via module-info.java)

Example test case for module-level annotation:

@Test
public void moduleLevelAnnotationAllowsMissingClassAnnotation() {
  compilationHelper
      .addSourceLines(
          "module-info.java",
          "@org.jspecify.annotations.NullMarked",
          "module test.module {",
          "  requires org.jspecify;",
          "}")
      .addSourceLines(
          "test/ModuleMarkedClass.java",
          "package test;",
          "class ModuleMarkedClass {}")
      .doTest();
}
nullaway/src/main/java/com/uber/nullaway/RequireExplicitNullMarking.java (1)

65-68: Use NullabilityUtil constants instead of string literals for consistency.

The original concern about simple name matching is incorrect. Simple name matching for these annotations is intentional and consistently used throughout the codebase. No custom annotations with conflicting names exist in the repository.

However, RequireExplicitNullMarking should follow the established pattern used everywhere else in the codebase: replace the hardcoded strings with NullabilityUtil.NULLMARKED_SIMPLE_NAME and NullabilityUtil.NULLUNMARKED_SIMPLE_NAME constants (lines 66–67).

private static boolean hasNullMarkedOrNullUnmarkedAnnotation(Symbol symbol) {
  return ASTHelpers.hasDirectAnnotationWithSimpleName(symbol, NullabilityUtil.NULLMARKED_SIMPLE_NAME)
      || ASTHelpers.hasDirectAnnotationWithSimpleName(symbol, NullabilityUtil.NULLUNMARKED_SIMPLE_NAME);
}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7923ea2 and f8518fe.

📒 Files selected for processing (2)
  • nullaway/src/main/java/com/uber/nullaway/RequireExplicitNullMarking.java (1 hunks)
  • nullaway/src/test/java/com/uber/nullaway/RequireExplicitNullMarkingTest.java (1 hunks)
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: msridhar
Repo: uber/NullAway PR: 1259
File: jdk-recent-unit-tests/src/test/java/com/uber/nullaway/jdk17/SwitchTests.java:318-321
Timestamp: 2025-08-29T18:41:43.584Z
Learning: Classes annotated with NullMarked are analyzed by NullAway even if they are not in packages specified by the AnnotatedPackages configuration. The NullMarked annotation guarantees NullAway analysis.
Learnt from: msridhar
Repo: uber/NullAway PR: 1248
File: nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java:847-857
Timestamp: 2025-08-28T04:54:20.953Z
Learning: In NullAway's GenericsChecks.java, NewClassTree support for explicit type argument substitution requires more extensive changes beyond just modifying the conditional in compareGenericTypeParameterNullabilityForCall. The maintainers prefer to handle NewClassTree support in a separate follow-up rather than expanding the scope of PRs focused on specific issues like super constructor calls.
Learnt from: msridhar
Repo: uber/NullAway PR: 1245
File: guava-recent-unit-tests/src/test/java/com/uber/nullaway/guava/NullAwayGuavaParametricNullnessTests.java:101-102
Timestamp: 2025-08-14T18:50:06.159Z
Learning: In NullAway JSpecify tests, when JDK version requirements exist due to bytecode annotation reading capabilities, prefer failing tests over skipping them on unsupported versions to ensure CI catches regressions and enforces proper JDK version usage for developers.
📚 Learning: 2025-08-29T18:41:43.584Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1259
File: jdk-recent-unit-tests/src/test/java/com/uber/nullaway/jdk17/SwitchTests.java:318-321
Timestamp: 2025-08-29T18:41:43.584Z
Learning: Classes annotated with NullMarked are analyzed by NullAway even if they are not in packages specified by the AnnotatedPackages configuration. The NullMarked annotation guarantees NullAway analysis.

Applied to files:

  • nullaway/src/main/java/com/uber/nullaway/RequireExplicitNullMarking.java
  • nullaway/src/test/java/com/uber/nullaway/RequireExplicitNullMarkingTest.java
📚 Learning: 2025-08-28T04:54:20.953Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1248
File: nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java:847-857
Timestamp: 2025-08-28T04:54:20.953Z
Learning: In NullAway's GenericsChecks.java, NewClassTree support for explicit type argument substitution requires more extensive changes beyond just modifying the conditional in compareGenericTypeParameterNullabilityForCall. The maintainers prefer to handle NewClassTree support in a separate follow-up rather than expanding the scope of PRs focused on specific issues like super constructor calls.

Applied to files:

  • nullaway/src/main/java/com/uber/nullaway/RequireExplicitNullMarking.java
  • nullaway/src/test/java/com/uber/nullaway/RequireExplicitNullMarkingTest.java
📚 Learning: 2025-08-14T18:50:06.159Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1245
File: guava-recent-unit-tests/src/test/java/com/uber/nullaway/guava/NullAwayGuavaParametricNullnessTests.java:101-102
Timestamp: 2025-08-14T18:50:06.159Z
Learning: In NullAway JSpecify tests, when JDK version requirements exist due to bytecode annotation reading capabilities, prefer failing tests over skipping them on unsupported versions to ensure CI catches regressions and enforces proper JDK version usage for developers.

Applied to files:

  • nullaway/src/main/java/com/uber/nullaway/RequireExplicitNullMarking.java
  • nullaway/src/test/java/com/uber/nullaway/RequireExplicitNullMarkingTest.java
📚 Learning: 2025-10-29T23:56:18.236Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1316
File: jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java:261-293
Timestamp: 2025-10-29T23:56:18.236Z
Learning: In NullAway's jdk-javac-plugin NullnessAnnotationSerializer, type variable bounds with annotations (e.g., `T extends Nullable Object`) are checked at their declaration sites by the typeParamHasAnnotation method for both class-level and method-level type parameters. The hasJSpecifyAnnotationDeep method is designed to check type uses (return types, parameters, etc.) and does not need a TYPEVAR case because type variable declaration bounds are already handled separately.

Applied to files:

  • nullaway/src/main/java/com/uber/nullaway/RequireExplicitNullMarking.java
  • nullaway/src/test/java/com/uber/nullaway/RequireExplicitNullMarkingTest.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Build spring-framework with snapshot
  • GitHub Check: Build caffeine with snapshot
  • GitHub Check: Build and test on macos-latest
  • GitHub Check: Build and test on ubuntu-latest
  • GitHub Check: Build and test on windows-latest
🔇 Additional comments (7)
nullaway/src/main/java/com/uber/nullaway/RequireExplicitNullMarking.java (3)

22-34: LGTM!

Package and imports are appropriate for an Error Prone checker implementation.


35-46: LGTM!

The class structure and annotations follow Error Prone conventions correctly. The SUGGESTION severity is appropriate for an opt-in checker intended for migration purposes.


48-63: LGTM!

The logic correctly:

  • Filters to only top-level classes using getNestingKind()
  • Walks up the enclosing symbol hierarchy (class → package → module) to find null-marking annotations
  • Short-circuits appropriately when annotations are found

The implementation aligns with the PR objectives to enforce explicit marking at any level of the containment hierarchy.

nullaway/src/test/java/com/uber/nullaway/RequireExplicitNullMarkingTest.java (4)

14-18: LGTM!

Standard Error Prone test setup is correct.


20-32: LGTM!

Good test coverage for the core requirement. The nested class check ensures the TOP_LEVEL filtering works correctly.


34-44: LGTM!

Correctly verifies that direct @NullMarked annotation satisfies the requirement.


46-56: LGTM!

Correctly verifies that direct @NullUnmarked annotation also satisfies the requirement.

Comment thread nullaway/src/main/java/com/uber/nullaway/RequireExplicitNullMarking.java Outdated
…rking.java

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f8518fe and d96ce1a.

📒 Files selected for processing (1)
  • nullaway/src/main/java/com/uber/nullaway/RequireExplicitNullMarking.java (1 hunks)
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: msridhar
Repo: uber/NullAway PR: 1259
File: jdk-recent-unit-tests/src/test/java/com/uber/nullaway/jdk17/SwitchTests.java:318-321
Timestamp: 2025-08-29T18:41:43.584Z
Learning: Classes annotated with NullMarked are analyzed by NullAway even if they are not in packages specified by the AnnotatedPackages configuration. The NullMarked annotation guarantees NullAway analysis.
Learnt from: msridhar
Repo: uber/NullAway PR: 1248
File: nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java:847-857
Timestamp: 2025-08-28T04:54:20.953Z
Learning: In NullAway's GenericsChecks.java, NewClassTree support for explicit type argument substitution requires more extensive changes beyond just modifying the conditional in compareGenericTypeParameterNullabilityForCall. The maintainers prefer to handle NewClassTree support in a separate follow-up rather than expanding the scope of PRs focused on specific issues like super constructor calls.
Learnt from: msridhar
Repo: uber/NullAway PR: 1245
File: guava-recent-unit-tests/src/test/java/com/uber/nullaway/guava/NullAwayGuavaParametricNullnessTests.java:101-102
Timestamp: 2025-08-14T18:50:06.159Z
Learning: In NullAway JSpecify tests, when JDK version requirements exist due to bytecode annotation reading capabilities, prefer failing tests over skipping them on unsupported versions to ensure CI catches regressions and enforces proper JDK version usage for developers.
Learnt from: msridhar
Repo: uber/NullAway PR: 1316
File: jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java:261-293
Timestamp: 2025-10-29T23:56:18.236Z
Learning: In NullAway's jdk-javac-plugin NullnessAnnotationSerializer, type variable bounds with annotations (e.g., `T extends Nullable Object`) are checked at their declaration sites by the typeParamHasAnnotation method for both class-level and method-level type parameters. The hasJSpecifyAnnotationDeep method is designed to check type uses (return types, parameters, etc.) and does not need a TYPEVAR case because type variable declaration bounds are already handled separately.
📚 Learning: 2025-08-29T18:41:43.584Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1259
File: jdk-recent-unit-tests/src/test/java/com/uber/nullaway/jdk17/SwitchTests.java:318-321
Timestamp: 2025-08-29T18:41:43.584Z
Learning: Classes annotated with NullMarked are analyzed by NullAway even if they are not in packages specified by the AnnotatedPackages configuration. The NullMarked annotation guarantees NullAway analysis.

Applied to files:

  • nullaway/src/main/java/com/uber/nullaway/RequireExplicitNullMarking.java
📚 Learning: 2025-08-28T04:54:20.953Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1248
File: nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java:847-857
Timestamp: 2025-08-28T04:54:20.953Z
Learning: In NullAway's GenericsChecks.java, NewClassTree support for explicit type argument substitution requires more extensive changes beyond just modifying the conditional in compareGenericTypeParameterNullabilityForCall. The maintainers prefer to handle NewClassTree support in a separate follow-up rather than expanding the scope of PRs focused on specific issues like super constructor calls.

Applied to files:

  • nullaway/src/main/java/com/uber/nullaway/RequireExplicitNullMarking.java
📚 Learning: 2025-08-14T18:50:06.159Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1245
File: guava-recent-unit-tests/src/test/java/com/uber/nullaway/guava/NullAwayGuavaParametricNullnessTests.java:101-102
Timestamp: 2025-08-14T18:50:06.159Z
Learning: In NullAway JSpecify tests, when JDK version requirements exist due to bytecode annotation reading capabilities, prefer failing tests over skipping them on unsupported versions to ensure CI catches regressions and enforces proper JDK version usage for developers.

Applied to files:

  • nullaway/src/main/java/com/uber/nullaway/RequireExplicitNullMarking.java
📚 Learning: 2025-10-29T23:56:18.236Z
Learnt from: msridhar
Repo: uber/NullAway PR: 1316
File: jdk-javac-plugin/src/main/java/com/uber/nullaway/javacplugin/NullnessAnnotationSerializer.java:261-293
Timestamp: 2025-10-29T23:56:18.236Z
Learning: In NullAway's jdk-javac-plugin NullnessAnnotationSerializer, type variable bounds with annotations (e.g., `T extends Nullable Object`) are checked at their declaration sites by the typeParamHasAnnotation method for both class-level and method-level type parameters. The hasJSpecifyAnnotationDeep method is designed to check type uses (return types, parameters, etc.) and does not need a TYPEVAR case because type variable declaration bounds are already handled separately.

Applied to files:

  • nullaway/src/main/java/com/uber/nullaway/RequireExplicitNullMarking.java
🔇 Additional comments (4)
nullaway/src/main/java/com/uber/nullaway/RequireExplicitNullMarking.java (4)

23-34: LGTM!

Imports are appropriate and necessary for the checker implementation.


36-47: LGTM!

The checker declaration is well-structured with appropriate severity level (SUGGESTION for opt-in) and clear documentation.


49-64: LGTM!

The matchClass logic correctly implements the checker's contract by filtering to top-level classes and walking enclosing symbols (package, module) to detect explicit null-marking annotations.


66-69: LGTM!

The simple name matching approach for annotations is consistent with Error Prone and NullAway patterns, providing flexibility without hard dependencies on specific annotation packages.

Comment thread nullaway/src/main/java/com/uber/nullaway/RequireExplicitNullMarking.java Outdated
msridhar and others added 3 commits November 2, 2025 13:15
@msridhar
Copy link
Copy Markdown
Collaborator Author

msridhar commented Nov 3, 2025

Tests are failing here due to an annoying interaction with explicitly passing -processorpath to a CompilationTestHelper, which we should probably not do. I can fix but would like some feedback on this approach first.

An alternative would be to integrate this checking into NullAway and enable it when the OnlyNullMarked setting is passed. I made this a separate checker at least partly because I didn't want to add yet another check to NullAway itself. But integrating into NullAway / OnlyNullMarked would make the issue more visible. A downside of that would be that users who were relying on implicitly making some classes / packages @NullUnmarked would now have to make that explicit. But maybe forcing explicitness would be a good thing? And of course, we could integrate into NullAway with a setting to disable, for a bit more complexity 🙂

@ben-manes @sdeleuze would be interested if you have any thoughts here (and anyone else who is reading...)

@ben-manes
Copy link
Copy Markdown

I know for large code bases that are converting it requires gradually opting in. Doing so using package-info instead of the annotatedPackages flag makes a lot of sense to me. A check to find accidental gaps post-migration like the Caffeine build error shown here would certainly be useful.

If the intent is to instead require a package-info on all packages and be @NullUnmarked to start the migration that that will be a large burden. The package-info and module-info files are very rare for internal code bases. I think you mean if absent then it is implicitly null unmarked, so it can be gradually migrated onto, and the checker would help enforce it afterwards.

@msridhar
Copy link
Copy Markdown
Collaborator Author

msridhar commented Nov 3, 2025

Thanks @ben-manes! The way this check is written it would indeed require either @NullUnmarked on every class or a @NullUnmarked in a package-info.java when starting a migration. My thinking was that it's pretty easy to script adding a stub package-info.java to every package and making it @NullUnmarked to start. That would add clutter to a codebase when onboarding. A really complex AnnotatedPackages regex as one gradually onboards is also clutter, but I guess it's proportional to the number of null-marked packages, and the hierarchy can reduce the clutter.

All that said, seems safer to make this check opt-in at first, which works well with the separate checker implementation. If lots of people run into this problem and aren't finding how to opt in, we could think about making it opt-out instead.

@sdeleuze
Copy link
Copy Markdown

sdeleuze commented Nov 3, 2025

Useful feature, but yeah I think this check should indeed be opt-in, to allow annotating initially packages after packages or classes after classes without too much friction.

msridhar added a commit that referenced this pull request Nov 4, 2025
…tests (#1326)

This seems to have a global side effect that causes weird test failures
in #1323, and it's a bit of a hack. Instead, move the relevant tests to
the `test-library-model` module where no such hacks are required.
Test-only changes.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added capability to suggest removal of unnecessary non-null casts from
library models

* **Tests**
* Expanded test coverage for library model scenarios with new test cases

* **Chores**
  * Updated build dependencies and test configuration

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
@codecov
Copy link
Copy Markdown

codecov Bot commented Nov 4, 2025

Codecov Report

❌ Patch coverage is 83.33333% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.41%. Comparing base (193f706) to head (c4af65e).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
.../com/uber/nullaway/RequireExplicitNullMarking.java 83.33% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master    #1323      +/-   ##
============================================
- Coverage     88.42%   88.41%   -0.01%     
- Complexity     2566     2574       +8     
============================================
  Files            96       97       +1     
  Lines          8605     8617      +12     
  Branches       1708     1713       +5     
============================================
+ Hits           7609     7619      +10     
- Misses          500      501       +1     
- Partials        496      497       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@msridhar
Copy link
Copy Markdown
Collaborator Author

msridhar commented Nov 5, 2025

BTW, another reason to prefer @NullMarked over AnnotatedPackages is that IntelliJ has a very good nullness analysis and using @NullMarked leads to better results right in the IDE.

@msridhar msridhar enabled auto-merge (squash) November 6, 2025 16:57
@msridhar msridhar merged commit 3e75b94 into master Nov 6, 2025
9 of 11 checks passed
@msridhar msridhar deleted the new-force-annotation-checker branch November 6, 2025 17:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants