Skip to content

Conversation

@msridhar
Copy link
Collaborator

@msridhar msridhar commented Sep 13, 2025

Fixes #1262, fixes #1263

We now invoke inference for generic method calls in more places, including when it gets queried from dataflow analysis of a full method. Where possible, we provide a TreePath to the relevant call. With this TreePath, we can reconstruct the assignment context of the call and use that information when performing inference (see the new getInvocationAndContextForInference method). In some cases, we pass null as the TreePath since one is not readily available. I believe that in most / all of these cases, inference should have already been performed for the call, so we should be able to use the cached result. If / when we find cases where that didn't happen, and it is impacting the inference result negatively, we can fix in follow ups.

Summary by CodeRabbit

  • New Features
    • Path-aware nullness inference for generic method calls that selects the correct invocation/assignment context, including nested calls and assignment sites.
  • Bug Fixes
    • More accurate generic return nullness determination to reduce incorrect diagnostics in complex call and assignment patterns.
  • Performance
    • Per-invocation caching to speed repeated inference and reuse results across related calls.
  • Tests
    • Re-enabled and expanded generics inference tests, adding dataflow-triggered inference scenarios and related assertions.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 13, 2025

Warning

Rate limit exceeded

@msridhar has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 11 minutes and 5 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between be314ea and b5dd3e1.

📒 Files selected for processing (1)
  • nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (17 hunks)

Walkthrough

Adds path-aware, per-invocation generic-method type-argument inference and caching in GenericsChecks, changes getGenericReturnNullnessAtInvocation to accept an optional TreePath and updates callers accordingly, and expands tests to cover dataflow-triggered inference and inference-failure warnings.

Changes

Cohort / File(s) Summary
Generics inference core (path-aware, caching, API change)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java
Adds per-invocation cache and path-aware inference plumbing (InvocationAndContext, runInferenceForCall, getInvocationAndContextForInference, getInvocationInferenceInfoForAssignment); makes substitution/inference path-aware with optional TreePath parameter; changes getGenericReturnNullnessAtInvocation signature (...MethodInvocationTree tree, @Nullable TreePath path, VisitorState state); makes prettyTypeForError private.
Call-site updates
nullaway/src/main/java/com/uber/nullaway/NullAway.java, nullaway/src/main/java/com/uber/nullaway/dataflow/AccessPathNullnessPropagation.java
Updates calls to getGenericReturnNullnessAtInvocation to pass an explicit TreePath when available (node.getTreePath() in dataflow) or null when not (e.g., mayBeNullMethodCall uses null); no other control-flow changes.
Dataflow & generics tests
nullaway/src/test/java/com/uber/nullaway/jspecify/GenericMethodTests.java
Re-enables a previously ignored test, switches helpers to inference-failure-warning mode, adds/expands tests exercising dataflow-triggered generic-method inference, and adds inferenceFromDataflow() test(s).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Checker as Checker/Dataflow
  participant GC as GenericsChecks
  participant IF as InferenceEngine
  participant Cache as InvocationCache

  Checker->>GC: getGenericReturnNullnessAtInvocation(method, tree, path?, state)
  alt return not a type variable
    GC-->>Checker: declared/nullness result
  else
    GC->>Cache: lookup(invocation + path)
    alt cache hit
      Cache-->>GC: cached inference
    else
      GC->>IF: runInferenceForCall(invocation, path?, state)
      IF->>IF: select invocation & assignment context (path-aware)
      IF->>IF: generate constraints & solve
      IF->>Cache: store inference result for related invocations
      Cache-->>GC: inference result
    end
    GC-->>Checker: substituted return nullness (possibly NULLABLE)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

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 71.43% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Invoke generic method inference in more places" is concise and accurately describes the primary change set: extending where generic-method inference runs (changes in GenericsChecks, NullAway, AccessPathNullnessPropagation, and tests). It clearly communicates the main intent without extraneous detail, so a reviewer scanning history will understand the primary change.
Linked Issues Check ✅ Passed The code changes implement path-aware and dataflow-triggered inference: GenericsChecks now accepts an optional TreePath, adds a per-invocation cache and runInferenceForCall flow, and callers (AccessPathNullnessPropagation and NullAway) either pass a TreePath or null; tests in GenericMethodTests exercise dataflow-driven inference and unassigned-return scenarios. These changes directly address #1262 (ensure inference is invoked from dataflow and annotated locals propagate nullness) and #1263 (invoke inference for calls with no assignment context). The author’s note about passing null when no TreePath is available is consistent with the caching approach and is covered by tests, though it warrants follow-up if edge cases surface.
Out of Scope Changes Check ✅ Passed The modifications are narrowly scoped to generics inference plumbing, caller integration, and tests; I do not see unrelated feature work or broad refactors. One minor out-of-scope technical change is reducing prettyTypeForError from public to private, which is cosmetic and not required by the inference objectives and should be checked for external usage. The signature change to getGenericReturnNullnessAtInvocation and the related caller updates are in-scope to enable the new behavior.

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.

@codecov
Copy link

codecov bot commented Sep 13, 2025

Codecov Report

❌ Patch coverage is 86.11111% with 15 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.34%. Comparing base (c187bf5) to head (b5dd3e1).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
...ava/com/uber/nullaway/generics/GenericsChecks.java 85.84% 5 Missing and 10 partials ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master    #1286      +/-   ##
============================================
- Coverage     88.43%   88.34%   -0.09%     
- Complexity     2450     2465      +15     
============================================
  Files            93       93              
  Lines          8116     8188      +72     
  Branches       1600     1614      +14     
============================================
+ Hits           7177     7234      +57     
- Misses          475      480       +5     
- Partials        464      474      +10     

☔ 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 September 14, 2025 05:31
@msridhar
Copy link
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 14, 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.

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

🧹 Nitpick comments (3)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (2)

1270-1276: Handle parenthesized arguments when locating the formal parameter type.

Comparison should ignore parentheses; otherwise, inference misses the assignment context for calls like f((id(x))).

Apply:

-                (arg, pos, formalParamType, unused) -> {
-                  if (arg == invocation) {
+                (arg, pos, formalParamType, unused) -> {
+                  if (ASTHelpers.stripParentheses(arg) == invocation) {
                     formalParamTypeRef.set(formalParamType);
                   }
                 });

1244-1255: Inference for returns inside lambdas is unhandled.

ReturnTree branch TODO: when the invocation is the returned expr of a lambda, we can derive the assignment context from the functional interface method return type.

I can send a follow-up patch to resolve the return type from NullabilityUtil.getFunctionalInterfaceMethod and use its return type here.

nullaway/src/main/java/com/uber/nullaway/NullAway.java (1)

2714-2720: Null path in getGenericReturnNullnessAtInvocation is acceptable; opportunistically pass a TreePath when available.

Verified call sites: nullaway/src/main/java/com/uber/nullaway/NullAway.java:2719 (passes null for the TreePath), nullaway/src/main/java/com/uber/nullaway/dataflow/AccessPathNullnessPropagation.java:1157; method defined at nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java:1103. Not blocking — consider constructing/passing a TreePath (e.g., new TreePath(state.getPath(), invocationTree) when state.getPath() reliably exists for the invocation) to improve inference.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c187bf5 and b720581.

📒 Files selected for processing (4)
  • nullaway/src/main/java/com/uber/nullaway/NullAway.java (1 hunks)
  • nullaway/src/main/java/com/uber/nullaway/dataflow/AccessPathNullnessPropagation.java (1 hunks)
  • nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (17 hunks)
  • nullaway/src/test/java/com/uber/nullaway/jspecify/GenericMethodTests.java (4 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: msridhar
PR: uber/NullAway#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.
📚 Learning: 2025-08-28T04:54:20.953Z
Learnt from: msridhar
PR: uber/NullAway#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/dataflow/AccessPathNullnessPropagation.java
  • nullaway/src/main/java/com/uber/nullaway/NullAway.java
  • nullaway/src/test/java/com/uber/nullaway/jspecify/GenericMethodTests.java
  • nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java
📚 Learning: 2025-08-14T18:50:06.159Z
Learnt from: msridhar
PR: uber/NullAway#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/test/java/com/uber/nullaway/jspecify/GenericMethodTests.java
🧬 Code graph analysis (1)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (1)
nullaway/src/main/java/com/uber/nullaway/NullabilityUtil.java (1)
  • NullabilityUtil (66-665)
⏰ 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 caffeine with snapshot
  • GitHub Check: Build and test on macos-latest
  • GitHub Check: Build and test on windows-latest
🔇 Additional comments (4)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (1)

928-933: Scope control on NewClassTree substitution looks intentional; LGTM.

Keeping compareGenericTypeParameterNullabilityForCall limited to MethodInvocationTree matches the stated preference to handle NewClassTree support separately.

If desired later, enabling NewClassTree here should be coordinated with broader support to avoid partial behavior.

nullaway/src/main/java/com/uber/nullaway/dataflow/AccessPathNullnessPropagation.java (1)

1157-1159: Propagating TreePath into generic return nullness check: good integration.

Passing node.getTreePath() enables path-aware inference during dataflow, improving precision.

nullaway/src/test/java/com/uber/nullaway/jspecify/GenericMethodTests.java (2)

742-774: Enabling inference-failure warnings and adding dataflow-triggered checks: nice coverage.

Switching to makeHelperWithInferenceFailureWarning and asserting deref diagnostics exercises the new inference paths.


1144-1195: New inferenceFromDataflow test validates nested/paren/receiver/param contexts.

Solid end-to-end coverage of path-aware inference via dataflow.

@msridhar msridhar marked this pull request as draft September 14, 2025 16:01
@msridhar msridhar marked this pull request as ready for review September 14, 2025 17:53
@msridhar msridhar changed the title issue 1263 Invoke generic method inference in more places Sep 14, 2025
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: 0

♻️ Duplicate comments (4)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (4)

1156-1181: Path-aware substitution should re-infer on prior failure when path/context exists

Same failure-caching pitfall: if result was a failure, don’t skip re-inference when we now have an invocation/context from path.

Apply:

-      if (result == null) {
-        // have not yet attempted inference for this call
-        MethodInvocationTree invocationTree = (MethodInvocationTree) tree;
-        InvocationAndContext invocationAndType =
-            path == null ? null : getInvocationAndContextForInference(path, state);
-        result =
-            invocationAndType == null
-                ? runInferenceForCall(state, invocationTree, null, false)
-                : runInferenceForCall(
-                    state,
-                    invocationAndType.invocation,
-                    invocationAndType.typeFromAssignmentContext,
-                    invocationAndType.assignedToLocal);
-      }
+      MethodInvocationTree invocationTree = (MethodInvocationTree) tree;
+      InvocationAndContext invocationAndType =
+          path == null ? null : getInvocationAndContextForInference(path, state);
+      boolean shouldReinfer =
+          result == null
+              || (result instanceof InferenceFailure && invocationAndType != null);
+      if (shouldReinfer) {
+        result =
+            invocationAndType == null
+                ? runInferenceForCall(state, invocationTree, null, false)
+                : runInferenceForCall(
+                    state,
+                    invocationAndType.invocation,
+                    invocationAndType.typeFromAssignmentContext,
+                    invocationAndType.assignedToLocal);
+      }

1282-1287: Avoid throwing when invocation isn’t a direct receiver; fall back gracefully

Throwing here can abort analysis for valid trees. Return null and continue without assignment context.

Apply:

-            } else {
-              throw new RuntimeException(
-                  "did not find invocation "
-                      + state.getSourceForNode(invocation)
-                      + " as receiver expression of "
-                      + state.getSourceForNode(parentInvocation));
-            }
+            } else {
+              // Not a direct receiver; fall back to inference without assignment context.
+              return null;
+            }

541-544: Re-run inference when a cached failure meets new assignment context

Currently, a cached InferenceFailure blocks re-inference even when typeFromAssignmentContext becomes available. Re-run in that case.

Apply:

-    MethodInferenceResult result = inferredTypeVarNullabilityForGenericCalls.get(invocationTree);
-    if (result == null) { // have not yet attempted inference for this call
-      result =
-          runInferenceForCall(state, invocationTree, typeFromAssignmentContext, assignedToLocal);
-    }
+    MethodInferenceResult result = inferredTypeVarNullabilityForGenericCalls.get(invocationTree);
+    if (result == null
+        || (result instanceof InferenceFailure && typeFromAssignmentContext != null)) {
+      result =
+          runInferenceForCall(state, invocationTree, typeFromAssignmentContext, assignedToLocal);
+    }

606-623: Don’t cache failures; allow re-try with richer context

Caching InferenceFailure for allInvocations freezes a context-less result and prevents later success.

Apply:

-      InferenceFailure failureResult = new InferenceFailure(e.getMessage());
-      for (MethodInvocationTree invTree : allInvocations) {
-        inferredTypeVarNullabilityForGenericCalls.put(invTree, failureResult);
-      }
-      return failureResult;
+      // Do not cache failures; allow re-run with better context later.
+      return new InferenceFailure(e.getMessage());
🧹 Nitpick comments (1)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (1)

1139-1142: Minor: guard against null expression.type when converting explicit args

In rare error states, JCExpression.type can be null. Safe-guard to avoid NPE.

Apply:

-        types.add(expression.type); // Retrieve the Type
+        if (expression.type != null) {
+          types.add(expression.type); // Retrieve the Type
+        }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b720581 and e17e3f9.

📒 Files selected for processing (1)
  • nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (17 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: msridhar
PR: uber/NullAway#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.
📚 Learning: 2025-08-28T04:54:20.953Z
Learnt from: msridhar
PR: uber/NullAway#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
🧬 Code graph analysis (1)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (2)
nullaway/src/main/java/com/uber/nullaway/NullabilityUtil.java (1)
  • NullabilityUtil (66-665)
nullaway/src/main/java/com/uber/nullaway/ErrorMessage.java (1)
  • ErrorMessage (26-73)
⏰ 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 caffeine with snapshot
  • GitHub Check: Build spring-framework with snapshot
  • GitHub Check: Build and test on windows-latest
  • GitHub Check: Build and test on ubuntu-latest
  • GitHub Check: Build and test on macos-latest
🔇 Additional comments (5)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (5)

472-481: Good: correctly tracking local-variable assignments for inference context

Using assignedToLocal and the helper isAssignmentToLocalVariable() makes constraints more precise. LGTM.

Also applies to: 503-515


592-605: Success caching across nested invocations is sensible

Propagating InferenceSuccess to allInvocations avoids duplicate work. LGTM.


665-677: Good: only constrain return when assignment context exists

Guarding addSubtypeConstraint with a null check prevents spurious constraints. LGTM.


1570-1572: Good: reduce API exposure of prettyTypeForError

Private static is appropriate here. LGTM.


1104-1112: Confirmed: only TYPEVAR returns can be top-level nullable; callers handle explicit @nullable.

Both call sites enforce explicit @nullable handling: NullAway.mayBeNullMethodCall checks Nullness.hasNullableAnnotation before calling, and AccessPathNullnessPropagation.genericReturnIsNullable only invokes the generics check when hasNullableAnnotation is false. No other callers found.

Copy link
Collaborator

@lazaroclapp lazaroclapp left a comment

Choose a reason for hiding this comment

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

Some nits and thoughts, but this LGTM :)

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

♻️ Duplicate comments (1)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (1)

618-623: Avoid caching inference failures across all invocations; allow re-run with richer context.

Caching an InferenceFailure for every invTree in allInvocations can permanently block later successful, path-aware inference (e.g., when dataflow provides an assignment/return context). Prefer not caching failures, or at least don’t broadcast them to nested calls.

Apply:

-      InferenceFailure failureResult = new InferenceFailure(e.getMessage());
-      for (MethodInvocationTree invTree : allInvocations) {
-        inferredTypeVarNullabilityForGenericCalls.put(invTree, failureResult);
-      }
-      return failureResult;
+      // Do not cache failures; allow re-run if/when better context (e.g., TreePath) appears.
+      return new InferenceFailure(e.getMessage());
🧹 Nitpick comments (7)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (7)

503-515: Treat try-with-resources variables as “local” too.

isAssignmentToLocalVariable(...) misses ElementKind.RESOURCE_VARIABLE, which are effectively local variables. Include it to avoid skewing assignedToLocal during inference when initializing resources with generic calls.

Apply:

-    return treeSymbol != null && treeSymbol.getKind().equals(ElementKind.LOCAL_VARIABLE);
+    return treeSymbol != null
+        && (treeSymbol.getKind() == ElementKind.LOCAL_VARIABLE
+            || treeSymbol.getKind() == ElementKind.RESOURCE_VARIABLE);

Also applies to: 472-481


1120-1124: Use enum identity comparison instead of Objects.equals.

Simpler and avoids unnecessary boxing.

Apply:

-      if (substitutedReturnType != null
-          && Objects.equals(getTypeNullness(substitutedReturnType), Nullness.NULLABLE)) {
+      if (substitutedReturnType != null
+          && getTypeNullness(substitutedReturnType) == Nullness.NULLABLE) {

1351-1354: Same enum comparison nit here.

Apply:

-      if (substitutedParamTypes != null
-          && Objects.equals(
-              getParameterTypeNullness(substitutedParamTypes.get(paramIndex), isVarargsParam),
-              Nullness.NULLABLE)) {
+      if (substitutedParamTypes != null
+          && getParameterTypeNullness(substitutedParamTypes.get(paramIndex), isVarargsParam)
+              == Nullness.NULLABLE) {

1147-1154: Javadoc missing path parameter.

Document the new path arg so readers understand when/why it’s used.

Apply:

-   * @param state the visitor state
+   * @param path optional TreePath to the invocation; when non-null, used to reconstruct
+   *     assignment context for path-aware inference
+   * @param state the visitor state

1210-1226: Javadoc return contract out of sync with implementation.

The text says “or null,” but the method always returns a non-null InvocationAndContext (with a nullable typeFromAssignmentContext). Update the doc to avoid confusion.

Apply:

-   * @return the correct invocation on which to perform inference, along with the relevant
-   *     assignment context information, or {@code null} if no good assignment context can be found
+   * @return the invocation on which to perform inference, along with any assignment-context info
+   *     (its {@code typeFromAssignmentContext} may be {@code null} when no context is available)

1238-1244: Track TODO for lambda handling.

There’s a TODO to support lambdas here; worth opening an issue so it doesn’t get lost.

I can open a follow-up issue with a minimal repro and proposed approach. Want me to file it?


566-624: Optional: avoid broadcasting successes to unrelated nested calls.

Success caching to every invTree in allInvocations works when all constraints are solved together, but it can make provenance opaque and complicate future per-site invalidation. Consider caching only for the root call and letting nested calls be re-solved when they are encountered independently.

If you want, I can sketch a lightweight provenance map keyed by root invocation.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 46b066b and fe94a37.

📒 Files selected for processing (1)
  • nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (17 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: msridhar
PR: uber/NullAway#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.
📚 Learning: 2025-08-28T04:54:20.953Z
Learnt from: msridhar
PR: uber/NullAway#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
🧬 Code graph analysis (1)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (2)
nullaway/src/main/java/com/uber/nullaway/NullabilityUtil.java (1)
  • NullabilityUtil (66-665)
nullaway/src/main/java/com/uber/nullaway/ErrorMessage.java (1)
  • ErrorMessage (26-73)
🔇 Additional comments (4)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (4)

665-677: Good use of assignment context in constraint generation.

Feeding the return-type-to-context constraint only when typeFromAssignmentContext != null and propagating assignedToLocal to the solver looks correct and aligns with the objectives.


1104-1112: Early exit for non-typevar returns is fine.

The explicit-nullability path is expected to be handled by the caller; this method focusing on type-variable-driven nullness is OK.

Can you confirm all callers check explicit @Nullable on return types before calling this method?


1570-1572: Making prettyTypeForError private is a good tightening.

Removes unnecessary surface area; no concerns.


1103-1113: ```shell
#!/bin/bash
set -euo pipefail
echo "PWD: $(pwd)"
echo "---- search: keywords related to ignored/unused returns ----"
rg -n --hidden -S --glob 'nullaway/' '(ignored return|unused result|ignore result|@CanIgnoreReturnValue|CanIgnoreReturnValue|CheckReturnValue|discard|discarded|unused)' || true
echo "---- search: void method definitions in nullaway tests ----"
rg -n --hidden -S --glob 'nullaway/
' '\bvoid\s+\w+\s*(' || true
echo "---- search: getGenericReturnNullnessAtInvocation references ----"
rg -n --hidden -S 'getGenericReturnNullnessAtInvocation' || true
echo "---- list test files under nullaway (if any) ----"
rg --files -g 'nullaway//test/' || true


</blockquote></details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

@msridhar msridhar enabled auto-merge (squash) September 16, 2025 16:51
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: 0

♻️ Duplicate comments (3)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (3)

541-544: Re-run inference when a cached failure exists but a richer assignment context is now provided.

Otherwise a prior failure (e.g., inferred without context) permanently blocks success when context appears.

Apply:

-    if (result == null) { // have not yet attempted inference for this call
+    if (result == null
+        || (result instanceof InferenceFailure && typeFromAssignmentContext != null)) {
       result =
           runInferenceForCall(state, invocationTree, typeFromAssignmentContext, assignedToLocal);
     }

618-623: Don’t cache failures for all nested invocations.

Caching an InferenceFailure for every invTree can freeze a context-less failure and prevent future successful re-inference.

Apply:

-      InferenceFailure failureResult = new InferenceFailure(e.getMessage());
-      for (MethodInvocationTree invTree : allInvocations) {
-        inferredTypeVarNullabilityForGenericCalls.put(invTree, failureResult);
-      }
-      return failureResult;
+      // Do not cache failures; allow re-run later when a better context is available.
+      return new InferenceFailure(e.getMessage());

1165-1181: Allow re-inference on prior failure when a TreePath (context) is now available.

Prevents a no-context failure from blocking path-aware success.

Apply:

-      if (result == null) {
+      InvocationAndContext invocationAndType =
+          path == null
+              ? new InvocationAndContext(invocationTree, null, false)
+              : getInvocationAndContextForInference(path, state);
+      boolean shouldReinfer =
+          result == null
+              || (result instanceof InferenceFailure && invocationAndType.typeFromAssignmentContext != null);
+      if (shouldReinfer) {
-        // have not yet attempted inference for this call
-        MethodInvocationTree invocationTree = (MethodInvocationTree) tree;
-        InvocationAndContext invocationAndType =
-            path == null
-                ? new InvocationAndContext(invocationTree, null, false)
-                : getInvocationAndContextForInference(path, state);
         result =
             runInferenceForCall(
                 state,
                 invocationAndType.invocation,
                 invocationAndType.typeFromAssignmentContext,
                 invocationAndType.assignedToLocal);
       }
🧹 Nitpick comments (5)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (5)

1121-1124: Tiny cleanup: drop redundant null-check and use enum comparison.

getReturnType() won’t be null here; prefer == for enums.

-      if (substitutedReturnType != null
-          && Objects.equals(getTypeNullness(substitutedReturnType), Nullness.NULLABLE)) {
+      if (getTypeNullness(substitutedReturnType) == Nullness.NULLABLE) {
         return Nullness.NULLABLE;
       }

1155-1159: Javadoc parameter list out of date.

Add the @param path description and fix ordering to match the signature.

  /**
   * Substitutes the type arguments from a generic method invocation into the method's type.
   *
   * @param tree the method invocation tree
   * @param forAllType the generic method type
-  * @param state the visitor state
+  * @param path the TreePath to the invocation (used to reconstruct assignment context), or null
+  * @param state the visitor state
   * @return the substituted method type for the generic method
   */

1211-1226: Javadoc return contract doesn’t match implementation.

Method never returns null; it falls back to InvocationAndContext(invocation, null, false). Update docs.

-   * @return the correct invocation on which to perform inference, along with the relevant
-   *     assignment context information, or {@code null} if no good assignment context can be found
+   * @return the correct invocation on which to perform inference, along with the relevant
+   *     assignment context information; falls back to a context with {@code typeFromAssignmentContext = null}

1251-1254: Fix misleading inline comment.

The code only uses the parent expression’s type when the parent is a MethodInvocation; otherwise it falls back to “no assignment context”.

-      // could be a parameter to another method call, or part of a conditional expression, etc.
-      // in any case, just return the type of the parent expression
+      // Could be a parameter to another method call, or part of a larger expression.
+      // If the parent is a method invocation, use its formal parameter/receiver type; otherwise,
+      // fall back to no assignment context.

1354-1355: Optional: consider threading TreePath here too for symmetry/future cases.

getGenericParameterNullnessAtInvocation could accept an optional TreePath like the return-path API, even if unused today.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fe94a37 and be314ea.

📒 Files selected for processing (1)
  • nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (17 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: msridhar
PR: uber/NullAway#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.
📚 Learning: 2025-08-28T04:54:20.953Z
Learnt from: msridhar
PR: uber/NullAway#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
🧬 Code graph analysis (1)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (2)
nullaway/src/main/java/com/uber/nullaway/NullabilityUtil.java (1)
  • NullabilityUtil (66-665)
nullaway/src/main/java/com/uber/nullaway/ErrorMessage.java (1)
  • ErrorMessage (26-73)
⏰ 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 and test on ubuntu-latest
  • GitHub Check: Build and test on windows-latest
  • GitHub Check: Build spring-framework with snapshot
  • GitHub Check: Build caffeine with snapshot
  • GitHub Check: Build and test on macos-latest
🔇 Additional comments (8)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (8)

7-7: LGTM on added imports.

Preconditions, ReturnTree, NullabilityUtil, and AtomicReference uses are appropriate.

Also applies to: 25-25, 45-45, 57-57


472-481: Good: track whether RHS is assigned to a local.

Plumbing assignedToLocal into inference improves precision for dataflow-triggered inference.


503-514: Helper to detect local-variable assignments looks correct.

Covers both VariableTree and AssignmentTree; safely falls back when the symbol is null (e.g., array element writes).


665-676: LGTM: add return-type constraint from assignment context.

Captures assignedToLocal and only emits constraint when context exists.


1104-1112: Path-aware return nullness check is a solid addition.

Early exit for non-typevars avoids unnecessary work; passing path enables assignment-context reconstruction.


1191-1208: LGTM: compact carrier for invocation + assignment context.

Keeps inference plumbing tidy.


1303-1310: LGTM: assignment-site info helper.

Precondition + reuse of isAssignmentToLocalVariable are clean.


1577-1577: Visibility reduction to private is good.

Helper isn’t part of the public API surface anymore.

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.

JSpecify inference: handle calls where result is not assigned anywhere JSpecify inference: properly call into inference from dataflow

3 participants