Skip to content

Include nested annotation information in astubx files when loading external library models#1456

Merged
msridhar merged 52 commits intouber:masterfrom
haewiful:nested-annot-astubx
Feb 5, 2026
Merged

Include nested annotation information in astubx files when loading external library models#1456
msridhar merged 52 commits intouber:masterfrom
haewiful:nested-annot-astubx

Conversation

@haewiful
Copy link
Contributor

@haewiful haewiful commented Jan 29, 2026

Fixes #1410

This pull request includes nested annotation information when loading external libraries.
First, the astubx-generator project is updated to include nested annotation information to astubx.
Second, the StubxCacheUtil class is updated so that it will read the newly added information and store it.
Finally, the LibraryModelsHandler class will now load the nested annotation information from the StubxCacheUtil and add it to external library models.
This is the final step from #1410.

The unit tests for this pull request are included in the following test classes.
jdk-annotations/astubx-generator/src/test/java/com/uber/nullaway/jdkannotations/AstubxTest.java
jdk-annotations/jdk-integration-test/src/test/java/com/uber/nullaway/jdkannotations/JDKIntegrationTest.java

Summary by CodeRabbit

  • New Features

    • Support for nested type annotations (on type arguments, array elements, and wildcards) throughout library models and library-stub handling, with serialization and retrieval of nested annotation data.
  • Public API

    • Library models and cache now expose nested-annotation metadata per method for consumers.
  • Tests

    • Added unit and integration tests covering nested return- and parameter-type annotations across parameterized types, arrays, wildcards, and mixed cases.

haewiful and others added 30 commits January 7, 2026 21:39
Co-authored-by: Manu Sridharan <msridhar@gmail.com>
…reateNestedAnnotationInfoVisitor.java

Co-authored-by: Manu Sridharan <msridhar@gmail.com>
…reateNestedAnnotationInfoVisitor.java

Co-authored-by: Manu Sridharan <msridhar@gmail.com>
…ullnessAnnotationSerializer.java

Co-authored-by: Manu Sridharan <msridhar@gmail.com>
…ullnessAnnotationSerializer.java

Co-authored-by: Manu Sridharan <msridhar@gmail.com>
@codecov
Copy link

codecov bot commented Jan 31, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 88.48%. Comparing base (009b79a) to head (e1c6b22).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@             Coverage Diff              @@
##             master    #1456      +/-   ##
============================================
+ Coverage     88.41%   88.48%   +0.07%     
- Complexity     2715     2723       +8     
============================================
  Files            99       99              
  Lines          9022     9077      +55     
  Branches       1805     1812       +7     
============================================
+ Hits           7977     8032      +55     
- Misses          517      518       +1     
+ Partials        528      527       -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 msridhar marked this pull request as ready for review January 31, 2026 18:37
api libs.commons.io
compileOnly libs.error.prone.check.api
implementation project(":library-model:library-model-generator")
compileOnly project(':jdk-javac-plugin')
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why this dependency?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I missed reverting that change when moving the NestedAnnotationInfo class. I removed it.

configurations = [
project.configurations.runtimeClasspath
]
from sourceSets.main.output
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is also a missed part of reverting my changes.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 31, 2026

Walkthrough

This PR adds end-to-end support for nested type annotations. It introduces a new ImmutableSetMultimap<Integer, NestedAnnotationInfo> field on MethodAnnotationsRecord, wires nested annotation construction through the astubx generator and javac plugin, persists nested annotation data in stubx writing/parsing and the Stubx cache, and exposes nested annotations via ExternalStubxLibraryModels.nestedAnnotationsForMethods(). Tests and test fixtures were updated to cover nested annotations on type arguments, wildcards, arrays, and mixed generics.

Possibly related issues

Possibly related PRs

Suggested reviewers

  • msridhar
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ 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 accurately and specifically describes the main objective of the pull request: adding nested annotation information to astubx files for external library models, matching the comprehensive changes across code generation, serialization, and loading layers.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
library-model/library-model-generator/src/main/java/com/uber/nullaway/libmodel/StubxWriter.java (1)

126-180: ⚠️ Potential issue | 🟠 Major

astubx format changed without a version bump.
The nested-annotation block is inserted before NullMarked classes but the magic number remains VERSION_1. Older readers will misparse new files and new readers will misparse old files. Please bump the magic/version and gate parsing by version (or handle the missing section) to maintain backward compatibility.

🤖 Fix all issues with AI agents
In `@nullaway/src/main/java/com/uber/nullaway/handlers/StubxCacheUtil.java`:
- Around line 80-82: The nestedAnnotationInfoCache field in StubxCacheUtil is
currently declared as a SetMultimap<String, SetMultimap<Integer,
NestedAnnotationInfo>>, which places a mutable SetMultimap inside a HashMultimap
value and can corrupt the set when mutated; change the field to Map<String,
SetMultimap<Integer, NestedAnnotationInfo>> (and likewise replace other similar
SetMultimap-of-SetMultimap fields/uses noted around lines 96-97, 113-116,
224-234) and update insertion/access sites to use computeIfAbsent() to create
and retrieve the inner SetMultimap (e.g., in methods that currently put or
mutate nestedAnnotationInfoCache) so you never store a mutable collection as a
value inside a HashMultimap. Ensure all call sites that assumed SetMultimap
behavior still operate on the inner SetMultimap retrieved from the Map.

Copy link
Collaborator

@msridhar msridhar left a comment

Choose a reason for hiding this comment

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

Looking pretty good! I have a few comments.

ImmutableList.of(
new TypePathEntry(Kind.ARRAY_ELEMENT, -1),
new TypePathEntry(Kind.TYPE_ARGUMENT, 0))))),
"ParameterizedTypeArray:java.util.List<? extends java.lang.String> wildcardIdentity(java.util.List<? extends java.lang.String>)",
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe:

Suggested change
"ParameterizedTypeArray:java.util.List<? extends java.lang.String> wildcardIdentity(java.util.List<? extends java.lang.String>)",
"ParameterizedTypeArray:java.util.List<? extends java.lang.String> wildcardIdentity(java.util.List<? super java.lang.String>)",

To test a super bound?

}

public static List<@Nullable String> nestedAnnotTypeArg() {
List<String> list = new ArrayList<>();
Copy link
Collaborator

Choose a reason for hiding this comment

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

You don't need this code here, but if you're going to write it, this should be:

Suggested change
List<String> list = new ArrayList<>();
List<@Nullable String> list = new ArrayList<>();

}

public static @Nullable String[] nestedAnnotArrayElement() {
return new String[] {"populated", "value", null};
Copy link
Collaborator

Choose a reason for hiding this comment

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

If you want to keep the code:

Suggested change
return new String[] {"populated", "value", null};
return new @Nullable String[] {"populated", "value", null};

@@ -12,7 +11,6 @@
* outermost type. Otherwise, each entry indicates one step in how to navigate to the nested
* type.
*/
@NullMarked
Copy link
Collaborator

Choose a reason for hiding this comment

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

why deleted?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have added it back.

Comment on lines 74 to 90
for (NestedAnnotationInfo.TypePathEntry.Kind kind :
NestedAnnotationInfo.TypePathEntry.Kind.values()) {
if (encodingDictionary.containsKey(kind.name())) {
continue;
}
strings.add(kind.name());
encodingDictionary.put(kind.name(), numStringEntries);
++numStringEntries;
}
for (NestedAnnotationInfo.Annotation annotation : NestedAnnotationInfo.Annotation.values()) {
if (encodingDictionary.containsKey(annotation.name())) {
continue;
}
strings.add(annotation.name());
encodingDictionary.put(annotation.name(), numStringEntries);
++numStringEntries;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think you can delete both these loops by adding the result of these values() calls to the keysets variable above. To convert to a Collection you can do List.of([...].values()).

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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
nullaway/src/main/java/com/uber/nullaway/handlers/LibraryModelsHandler.java (1)

63-64: ⚠️ Potential issue | 🔴 Critical

Same import path issue as StubxCacheUtil.

The imports use com.uber.nullaway.librarymodel but NestedAnnotationInfo is defined in com.uber.nullaway.libmodel.

🐛 Proposed fix for the import path
-import com.uber.nullaway.librarymodel.AddAnnotationToNestedTypeVisitor;
-import com.uber.nullaway.librarymodel.NestedAnnotationInfo;
-import com.uber.nullaway.librarymodel.NestedAnnotationInfo.Annotation;
+import com.uber.nullaway.libmodel.AddAnnotationToNestedTypeVisitor;
+import com.uber.nullaway.libmodel.NestedAnnotationInfo;
+import com.uber.nullaway.libmodel.NestedAnnotationInfo.Annotation;
🤖 Fix all issues with AI agents
In `@library-model/library-model-generator/build.gradle`:
- Around line 21-24: The jspecify dependency is leaked in the public API because
the public record NestedAnnotationInfo is annotated with `@NullMarked`; change the
dependency scope from implementation to api so consumers see jspecify
transitively. Update the dependencies block where libs.jspecify is declared (the
dependency line referencing jspecify) to use the api configuration instead of
implementation so the public annotation type is available to downstream modules.

In `@nullaway/src/main/java/com/uber/nullaway/handlers/StubxCacheUtil.java`:
- Around line 30-33: Update the imports in StubxCacheUtil to reference the
correct package com.uber.nullaway.libmodel instead of
com.uber.nullaway.librarymodel: replace imports for NestedAnnotationInfo and its
nested types (NestedAnnotationInfo, NestedAnnotationInfo.Annotation,
NestedAnnotationInfo.TypePathEntry, NestedAnnotationInfo.TypePathEntry.Kind) so
the class StubxCacheUtil uses com.uber.nullaway.libmodel.NestedAnnotationInfo
and the corresponding nested-type imports.

Copy link
Collaborator

@msridhar msridhar left a comment

Choose a reason for hiding this comment

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

LGTM!

@msridhar msridhar enabled auto-merge (squash) February 5, 2026 20:09
@msridhar msridhar merged commit 2e40e1d into uber:master Feb 5, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add support for nested annotations to JDK Javac plugin

2 participants