Skip to content

Handle interaction of captured types and library models - #1666

Merged
msridhar merged 5 commits into
masterfrom
library-models-captures
Aug 5, 2026
Merged

Handle interaction of captured types and library models#1666
msridhar merged 5 commits into
masterfrom
library-models-captures

Conversation

@msridhar

@msridhar msridhar commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

If we use a library model to add a @Nullable to the upper bound of a wildcard, we may need to update a CapturedType with that bound. To do so, we update the wildcard type associated with the CapturedType. See the test for an example warning we missed before. (There are some related cases I know of that are still not working; addressing those in a follow-up.)

We also take the opportunity to replace some null checks with a more direct check for an unbound wildcard.

Summary by CodeRabbit

  • Bug Fixes

    • Improved nullability analysis for captured types and nested wildcard bounds.
    • Preserved nullability annotations more consistently when resolving generic type relationships.
    • Correctly handles unbounded wildcards without losing type information.
    • Detects unsafe assignments involving nullable upper-bounded wildcard types in JSpecify mode.
    • Improved handling of generic type substitutions during nested annotation processing.
  • Tests

    • Added coverage for nested generic types and wildcard-based nullability violations.
    • Added validation for nullable upper bounds on captured type variables.

@msridhar

msridhar commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator Author

@codecov

codecov Bot commented Jul 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 74.19355% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.66%. Comparing base (a47afb5) to head (6ea8dce).

Files with missing lines Patch % Lines
...librarymodel/AddAnnotationToNestedTypeVisitor.java 33.33% 3 Missing and 5 partials ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master    #1666      +/-   ##
============================================
- Coverage     87.70%   87.66%   -0.04%     
- Complexity     3136     3138       +2     
============================================
  Files           109      109              
  Lines         10586    10614      +28     
  Branches       2140     2145       +5     
============================================
+ Hits           9284     9305      +21     
- Misses          625      628       +3     
- Partials        677      681       +4     

☔ View full report in Codecov by Harness.
📢 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 requested a review from lazaroclapp July 31, 2026 00:47
@msridhar
msridhar marked this pull request as ready for review July 31, 2026 00:47
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3f12d01b-6b8c-4e12-a9d4-0b1b5fcc38c2

📥 Commits

Reviewing files that changed from the base of the PR and between a47afb5 and 6ea8dce.

📒 Files selected for processing (5)
  • nullaway/src/main/java/com/uber/nullaway/generics/TypeSubstitutionUtils.java
  • nullaway/src/main/java/com/uber/nullaway/librarymodel/AddAnnotationToNestedTypeVisitor.java
  • test-java-lib/src/main/java/com/uber/lib/unannotated/NestedAnnots.java
  • test-library-models/src/main/java/com/uber/nullaway/testlibrarymodels/TestLibraryModels.java
  • test-library-models/src/test/java/com/uber/nullaway/CustomLibraryModelsTests.java

Walkthrough

Adds captured-type wildcard replacement and propagates nullability annotations to captured types and backing wildcards. Extends nested annotation traversal for captured types, unbounded wildcards, and path validation. Adds a modeled nullable wildcard API and a JSpecify regression test.

Possibly related issues

  • uber/NullAway issue 1656 — Covers captured-type restoration and nested library-model traversal addressed by this change.

Possibly related PRs

  • uber/NullAway#1667 — Modifies captured-type wildcard annotation restoration and related regression coverage.
  • uber/NullAway#1668 — Modifies captured-type and wildcard annotation handling in AddAnnotationToNestedTypeVisitor.
  • uber/NullAway#1669 — Modifies captured-type cloning and wildcard metadata preservation in TypeSubstitutionUtils.

Suggested reviewers: lazaroclapp, yuxincs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary change: handling captured types when library models modify wildcard annotations.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch library-models-captures

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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)
nullaway/src/main/java/com/uber/nullaway/generics/TypeSubstitutionUtils.java (1)

337-350: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Short-circuit UNBOUND wildcards in visitWildcardType.

javac keeps a non-null WildcardType.type even when kind == BoundKind.UNBOUND, so t != null does not mean an explicit bound is present. Add the same unbound guard used in AddAnnotationToNestedTypeVisitor here; otherwise a captured unbound wildcard can be rebuilt with an explicit placeholder bound when the counterpart wildcard has annotations.

Applicable to visitWildcardType lines 337-350; visitCapturedType dispatches here at lines 365-378.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nullaway/src/main/java/com/uber/nullaway/generics/TypeSubstitutionUtils.java`
around lines 337 - 350, Update visitWildcardType to short-circuit when wt.kind
is BoundKind.UNBOUND, using the same unbound-wildcard guard as
AddAnnotationToNestedTypeVisitor. Return the original wildcard before inspecting
or visiting wt.type, while preserving the existing comparison and rebuild
behavior for explicitly bounded wildcards.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@nullaway/src/main/java/com/uber/nullaway/librarymodel/AddAnnotationToNestedTypeVisitor.java`:
- Around line 132-137: In the terminal UNBOUND branch of the nested-type
visitor, add a TODO documenting that annotations are intentionally deferred
because no bound exists to receive them and the wildcard itself is not annotated
here. Reference the corresponding acknowledged follow-up in visitWildcardType so
this case remains traceable, while preserving the existing return behavior.

---

Outside diff comments:
In
`@nullaway/src/main/java/com/uber/nullaway/generics/TypeSubstitutionUtils.java`:
- Around line 337-350: Update visitWildcardType to short-circuit when wt.kind is
BoundKind.UNBOUND, using the same unbound-wildcard guard as
AddAnnotationToNestedTypeVisitor. Return the original wildcard before inspecting
or visiting wt.type, while preserving the existing comparison and rebuild
behavior for explicitly bounded wildcards.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c9eeceeb-26b9-4c46-af55-13485d078a6e

📥 Commits

Reviewing files that changed from the base of the PR and between f54e916 and 1de4d06.

📒 Files selected for processing (5)
  • nullaway/src/main/java/com/uber/nullaway/generics/TypeSubstitutionUtils.java
  • nullaway/src/main/java/com/uber/nullaway/librarymodel/AddAnnotationToNestedTypeVisitor.java
  • test-java-lib/src/main/java/com/uber/lib/unannotated/NestedAnnots.java
  • test-library-models/src/main/java/com/uber/nullaway/testlibrarymodels/TestLibraryModels.java
  • test-library-models/src/test/java/com/uber/nullaway/CustomLibraryModelsTests.java

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@nullaway/src/main/java/com/uber/nullaway/generics/TypeSubstitutionUtils.java`:
- Around line 103-108: Update the Javadoc for replaceCapturedTypeWildcard to
include `@param` tags describing type and wildcard, and an `@return` tag describing
the copied CapturedType result, matching the documentation style of the other
public methods in TypeSubstitutionUtils.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 03e6ecba-0425-445d-b6f6-aa69dd1a1504

📥 Commits

Reviewing files that changed from the base of the PR and between 1de4d06 and 4c228f2.

📒 Files selected for processing (5)
  • nullaway/src/main/java/com/uber/nullaway/generics/TypeSubstitutionUtils.java
  • nullaway/src/main/java/com/uber/nullaway/librarymodel/AddAnnotationToNestedTypeVisitor.java
  • test-java-lib/src/main/java/com/uber/lib/unannotated/NestedAnnots.java
  • test-library-models/src/main/java/com/uber/nullaway/testlibrarymodels/TestLibraryModels.java
  • test-library-models/src/test/java/com/uber/nullaway/CustomLibraryModelsTests.java

Comment on lines +103 to +108
/**
* Returns a copy of {@code type} with {@code wildcard} as its backing wildcard.
*
* <p>The copy is necessary because javac capture types can be shared across attributed types.
*/
public static Type.CapturedType replaceCapturedTypeWildcard(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add @param/@return tags for consistency.

Every other public method in this file documents @param and @return (see asSuper, memberType, removeNullableAnnotation). Add the same tags here for consistency.

📝 Proposed Javadoc update
   /**
    * Returns a copy of {`@code` type} with {`@code` wildcard} as its backing wildcard.
    *
    * <p>The copy is necessary because javac capture types can be shared across attributed types.
+   *
+   * `@param` type the captured type to copy
+   * `@param` wildcard the wildcard to use as the backing wildcard of the copy
+   * `@return` the copy of {`@code` type} with the updated backing wildcard
    */
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* Returns a copy of {@code type} with {@code wildcard} as its backing wildcard.
*
* <p>The copy is necessary because javac capture types can be shared across attributed types.
*/
public static Type.CapturedType replaceCapturedTypeWildcard(
/**
* Returns a copy of {`@code` type} with {`@code` wildcard} as its backing wildcard.
*
* <p>The copy is necessary because javac capture types can be shared across attributed types.
*
* `@param` type the captured type to copy
* `@param` wildcard the wildcard to use as the backing wildcard of the copy
* `@return` the copy of {`@code` type} with the updated backing wildcard
*/
public static Type.CapturedType replaceCapturedTypeWildcard(
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nullaway/src/main/java/com/uber/nullaway/generics/TypeSubstitutionUtils.java`
around lines 103 - 108, Update the Javadoc for replaceCapturedTypeWildcard to
include `@param` tags describing type and wildcard, and an `@return` tag describing
the copied CapturedType result, matching the documentation style of the other
public methods in TypeSubstitutionUtils.

@msridhar
msridhar force-pushed the nested-captures-restored-ananotations branch from 61c62c6 to ec8cb15 Compare August 4, 2026 20:37
@msridhar
msridhar force-pushed the library-models-captures branch from 4c228f2 to 2449fdd Compare August 4, 2026 20:37

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@test-library-models/src/test/java/com/uber/nullaway/CustomLibraryModelsTests.java`:
- Around line 572-598: Add Javadoc to the non-trivial test method
nestedWildcardWithCapturedTypeVariableBound describing that it verifies the
expected incompatibility diagnostic from capture conversion of the nested
wildcard type under JSpecify nullness checking.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f0c7a3d3-e97b-463c-9da2-8e4acc4f5714

📥 Commits

Reviewing files that changed from the base of the PR and between 4c228f2 and 2449fdd.

📒 Files selected for processing (5)
  • nullaway/src/main/java/com/uber/nullaway/generics/TypeSubstitutionUtils.java
  • nullaway/src/main/java/com/uber/nullaway/librarymodel/AddAnnotationToNestedTypeVisitor.java
  • test-java-lib/src/main/java/com/uber/lib/unannotated/NestedAnnots.java
  • test-library-models/src/main/java/com/uber/nullaway/testlibrarymodels/TestLibraryModels.java
  • test-library-models/src/test/java/com/uber/nullaway/CustomLibraryModelsTests.java

Comment on lines +572 to +598
@Test
public void nestedWildcardWithCapturedTypeVariableBound() {
makeLibraryModelsTestHelperWithArgs(
JSpecifyJavacConfig.withJSpecifyModeArgs(
Arrays.asList(
"-d",
temporaryFolder.getRoot().getAbsolutePath(),
"-XepOpt:NullAway:OnlyNullMarked=true")))
.addSourceLines(
"Test.java",
"""
import com.uber.lib.unannotated.NestedAnnots;
import org.jspecify.annotations.*;
@NullMarked
public class Test {
NestedAnnots<? extends String> test(
NestedAnnots<? extends String> receiver) {
// should reject since return type of wildcardUpperTypeVariable
// is modeled to be NestedAnnots<? extends @Nullable T>, which
// here is incompatible with the return type NestedAnnots<? extends String>
// BUG: Diagnostic contains: incompatible types
return receiver.self().wildcardUpperTypeVariable();
}
}
""")
.doTest();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add Javadoc to the new test method.

nestedWildcardWithCapturedTypeVariableBound configures a JSpecify compilation test and verifies a capture-conversion diagnostic. Document this behavior.

As per coding guidelines, “Add Javadoc to every non-trivial method, including private methods.”

Proposed Javadoc
+  /**
+   * Verifies that a modeled nullable wildcard bound is incompatible after capture conversion.
+   */
   `@Test`
   public void nestedWildcardWithCapturedTypeVariableBound() {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Test
public void nestedWildcardWithCapturedTypeVariableBound() {
makeLibraryModelsTestHelperWithArgs(
JSpecifyJavacConfig.withJSpecifyModeArgs(
Arrays.asList(
"-d",
temporaryFolder.getRoot().getAbsolutePath(),
"-XepOpt:NullAway:OnlyNullMarked=true")))
.addSourceLines(
"Test.java",
"""
import com.uber.lib.unannotated.NestedAnnots;
import org.jspecify.annotations.*;
@NullMarked
public class Test {
NestedAnnots<? extends String> test(
NestedAnnots<? extends String> receiver) {
// should reject since return type of wildcardUpperTypeVariable
// is modeled to be NestedAnnots<? extends @Nullable T>, which
// here is incompatible with the return type NestedAnnots<? extends String>
// BUG: Diagnostic contains: incompatible types
return receiver.self().wildcardUpperTypeVariable();
}
}
""")
.doTest();
}
/**
* Verifies that a modeled nullable wildcard bound is incompatible after capture conversion.
*/
`@Test`
public void nestedWildcardWithCapturedTypeVariableBound() {
makeLibraryModelsTestHelperWithArgs(
JSpecifyJavacConfig.withJSpecifyModeArgs(
Arrays.asList(
"-d",
temporaryFolder.getRoot().getAbsolutePath(),
"-XepOpt:NullAway:OnlyNullMarked=true")))
.addSourceLines(
"Test.java",
"""
import com.uber.lib.unannotated.NestedAnnots;
import org.jspecify.annotations.*;
`@NullMarked`
public class Test {
NestedAnnots<? extends String> test(
NestedAnnots<? extends String> receiver) {
// should reject since return type of wildcardUpperTypeVariable
// is modeled to be NestedAnnots<? extends `@Nullable` T>, which
// here is incompatible with the return type NestedAnnots<? extends String>
// BUG: Diagnostic contains: incompatible types
return receiver.self().wildcardUpperTypeVariable();
}
}
""")
.doTest();
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@test-library-models/src/test/java/com/uber/nullaway/CustomLibraryModelsTests.java`
around lines 572 - 598, Add Javadoc to the non-trivial test method
nestedWildcardWithCapturedTypeVariableBound describing that it verifies the
expected incompatibility diagnostic from capture conversion of the nested
wildcard type under JSpecify nullness checking.

Source: Coding guidelines

Base automatically changed from nested-captures-restored-ananotations to master August 5, 2026 02:05
@msridhar
msridhar force-pushed the library-models-captures branch from 2449fdd to 6ea8dce Compare August 5, 2026 05:36
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@msridhar
msridhar enabled auto-merge (squash) August 5, 2026 05:36
@msridhar
msridhar merged commit 2b8d5cb into master Aug 5, 2026
12 of 14 checks passed
@msridhar
msridhar deleted the library-models-captures branch August 5, 2026 05:51
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.

2 participants