Skip to content

Conversation

@msridhar
Copy link
Collaborator

@msridhar msridhar commented Dec 25, 2025

This PR adds support for modeling nested type annotations via library models, to support cases like #1157. Specifically we make the following changes:

  • Add a method LibraryModels::nestedAnnotationsForMethods to specify nested annotations, with corresponding data types like NestedAnnotationInfo and TypePathEntry. Also add support in CombinedLibraryModels and OptimizedLibraryModels.
  • Add a method Handler:: onOverrideMethodType to allow handlers to override method types, and invoke it from the appropriate places in GenericsChecks.
  • Implement LibraryModelsHandler:: onOverrideMethodType to insert annotations specified by the library models. Beyond that method, the primary logic is in the new visitor AddAnnotationToNestedTypeVisitor.
  • Enhance RestoreNullnessAnnotationsVisitor to restore annotations from class types and array types (previously it would only do type variables).

Tests are in CustomLibraryModelsTests, based on the code in NestedAnnots and the sample library models for that code in TestLibraryModels.

There are other minor related changes. Apologies for the size of this PR.

Once this lands, we can work on parsing this info out of jspecify/jdk and adding to astubx for our JDK models.

Summary by CodeRabbit

  • New Features

    • Support for nested nullability annotations on method arguments and return types, improving null-safety analysis for complex generics and arrays.
  • Tests

    • Added comprehensive tests covering deeply nested generics, arrays, wildcards, and multiple-argument scenarios.
  • Chores

    • Added a supporting test library with sample types and models to exercise nested-annotation behaviors.

✏️ Tip: You can customize this high-level summary in your review settings.

@msridhar msridhar changed the base branch from master to rename-synthetic December 25, 2025 00:34
@codecov
Copy link

codecov bot commented Dec 25, 2025

Codecov Report

❌ Patch coverage is 86.63366% with 27 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.35%. Comparing base (b8bc1e7) to head (a0b4ccb).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
...librarymodel/AddAnnotationToNestedTypeVisitor.java 54.90% 9 Missing and 14 partials ⚠️
...m/uber/nullaway/handlers/LibraryModelsHandler.java 93.54% 2 Missing and 2 partials ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master    #1407      +/-   ##
============================================
+ Coverage     88.21%   88.35%   +0.13%     
- Complexity     2615     2662      +47     
============================================
  Files            95       98       +3     
  Lines          8672     8895     +223     
  Branches       1741     1765      +24     
============================================
+ Hits           7650     7859     +209     
- Misses          509      514       +5     
- Partials        513      522       +9     

☔ 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 msridhar marked this pull request as ready for review December 25, 2025 02:36
@msridhar
Copy link
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 25, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 25, 2025

Walkthrough

Adds support for nested nullability annotations in library models and wires that data through analysis. Introduces NestedAnnotationInfo and AddAnnotationToNestedTypeVisitor, extends LibraryModels with nestedAnnotationsForMethods(), updates Combined/Optimized/Default library-model implementations to aggregate per-method nested annotation maps, and adds a handler hook onOverrideMethodType to apply nested annotations to MethodType arguments and returns. Refactors generics/type-restoration paths: GenericsChecks now uses handler-provided override-aware MethodType, TypeSubstitutionUtils adopts a direct update flow for restoring annotations, and CompositeHandler and Handler gain the onOverrideMethodType hook. New tests and a test-library-models module are added; build dependency updated.

Possibly related PRs

Suggested reviewers

  • yuxincs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.23% 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 title directly and clearly describes the main change: adding support for nested nullability annotations in library models, which is the core objective.
✨ 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 lib-models-for-nested-annotations

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.

@msridhar msridhar requested a review from yuxincs December 25, 2025 16:48
Base automatically changed from rename-synthetic to master December 25, 2025 17:24
Copy link
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: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 836c2a7 and a0b4ccb.

📒 Files selected for processing (1)
  • nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java
🧰 Additional context used
🧠 Learnings (3)
📓 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: 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-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/generics/GenericsChecks.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/generics/GenericsChecks.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). (4)
  • GitHub Check: Build and test on ubuntu-latest
  • GitHub Check: Build and test on macos-latest
  • GitHub Check: Build and test on windows-latest
  • GitHub Check: Build caffeine with snapshot
🔇 Additional comments (5)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (5)

488-492: LGTM! Override-aware method type resolution applied correctly.

The invocation now uses handler.onOverrideMethodType() to obtain a potentially modified method type before restoring nullability annotations. This correctly enables library models to inject nested annotations on method return types.


779-787: LGTM! Constraint generation consistently uses override-aware method type.

The method now obtains the method type via handler.onOverrideMethodType() and uses it consistently for:

  • Return type constraint generation (line 784)
  • InvocationArguments construction (line 787)

This ensures library model annotations are properly incorporated during generic method inference.


1317-1319: LGTM! Parameter nullability comparison uses override-aware method type.

The method type is now obtained via handler.onOverrideMethodType() before constructing InvocationArguments, ensuring parameter type checks account for library model annotations.


2090-2090: LGTM! Public API exposure for synthetic annotation type accessor.

The visibility change from private to public static is intentional per the PR summary, enabling external components (like handlers) to access the synthetic @Nullable annotation type for creating annotated types programmatically.


2102-2122: LGTM! New public accessor for synthetic @nonnull annotation type.

The new method mirrors getSyntheticNullableAnnotType and correctly creates a synthetic @NonNull annotation type. This complements the nullable variant and supports the library models feature for nested annotations.

Note: The thread-safety concern for lazy initialization applies here as well (see comment on lines 2074-2075).

Comment on lines +2074 to +2075
private static @Nullable Type syntheticNullableAnnotType;
private static @Nullable Type syntheticNonNullAnnotType;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add synchronization for thread-safe lazy initialization.

The static fields are lazily initialized without synchronization in their respective getter methods (lines 2091-2099 and 2113-2121). In multi-threaded compilation scenarios, this can cause race conditions where multiple ErrorType instances are created or visibility issues occur.

🔎 Suggested fix using double-checked locking
-  private static @Nullable Type syntheticNullableAnnotType;
-  private static @Nullable Type syntheticNonNullAnnotType;
+  private static volatile @Nullable Type syntheticNullableAnnotType;
+  private static volatile @Nullable Type syntheticNonNullAnnotType;

Then update the getter methods to use double-checked locking:

For getSyntheticNullableAnnotType:

   public static Type getSyntheticNullableAnnotType(VisitorState state) {
     if (syntheticNullableAnnotType == null) {
-      Names names = Names.instance(state.context);
-      Symtab symtab = Symtab.instance(state.context);
-      Name name = names.fromString("nullaway.synthetic");
-      Symbol.PackageSymbol packageSymbol = new Symbol.PackageSymbol(name, symtab.noSymbol);
-      Name simpleName = names.fromString("Nullable");
-      syntheticNullableAnnotType = new Type.ErrorType(simpleName, packageSymbol, Type.noType);
+      synchronized (GenericsChecks.class) {
+        if (syntheticNullableAnnotType == null) {
+          Names names = Names.instance(state.context);
+          Symtab symtab = Symtab.instance(state.context);
+          Name name = names.fromString("nullaway.synthetic");
+          Symbol.PackageSymbol packageSymbol = new Symbol.PackageSymbol(name, symtab.noSymbol);
+          Name simpleName = names.fromString("Nullable");
+          syntheticNullableAnnotType = new Type.ErrorType(simpleName, packageSymbol, Type.noType);
+        }
+      }
     }
     return syntheticNullableAnnotType;
   }

Apply the same pattern to getSyntheticNonNullAnnotType (lines 2113-2121).

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (lines
2074-2075), the two static lazy-initialized fields must be made thread-safe:
change both declarations to private static volatile Type
syntheticNullableAnnotType and private static volatile Type
syntheticNonNullAnnotType, and update their getters (around lines 2091-2099 and
2113-2121) to use double-checked locking — first check for null, then
synchronize on GenericsChecks.class, re-check null inside the block and
initialize if still null, then return the field.

@msridhar msridhar merged commit c86f08d into master Dec 25, 2025
9 of 11 checks passed
@msridhar msridhar deleted the lib-models-for-nested-annotations branch December 25, 2025 21:00
msridhar added a commit that referenced this pull request Dec 26, 2025
Fixes #1157 

Leverages recent new functionality in `LibraryModels` (#1407, #1345) to
properly support inference for calls to the
`AtomicReferenceFieldUpdater::newUpdater` generic method.


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

## Summary by CodeRabbit

## Release Notes

* **Improvements**
* Enhanced null-safety analysis for AtomicReferenceFieldUpdater to
properly support nullable type variable upper bounds and nested
annotations for generic method parameters.

* **Tests**
* Added test coverage for AtomicReferenceFieldUpdater functionality
within JDK library integration and null-marked code scenarios.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
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.

3 participants