Skip to content

Adjust more TreePaths to have correct leaf before calling getTreeType#1570

Merged
msridhar merged 17 commits into
masterfrom
proper-tree-paths
May 9, 2026
Merged

Adjust more TreePaths to have correct leaf before calling getTreeType#1570
msridhar merged 17 commits into
masterfrom
proper-tree-paths

Conversation

@msridhar
Copy link
Copy Markdown
Collaborator

@msridhar msridhar commented May 8, 2026

Fixes #1479

Now the stripParensAndUpdateTreePath utility method will throw if the leaf of the TreePath passed in doesn't match expr. This is a good invariant to enforce. There are a lot of code changes here, but they are all related to manipulating TreePaths rather than core logic, and the new throw will guard against getting this wrong in the future.

Summary by CodeRabbit

  • Bug Fixes

    • Improved accuracy of nullability analysis for generic method invocations, lambdas, returns and method references by ensuring the correct expression context is used.
    • Added fail-fast validation to detect and surface mismatched analysis state earlier.
  • Improvements

    • Propagated precise, path-aware context throughout inference and constraint generation to strengthen dataflow refinement and type resolution.

@msridhar
Copy link
Copy Markdown
Collaborator Author

msridhar commented May 8, 2026

This change is part of the following stack:

Change managed by git-spice.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 8, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR threads explicit TreePath context through generics inference and related flows: method-reference qualifier resolution uses a TreePath-rooted state; generateConstraintsForCall builds invocation and per-argument TreePaths and runs pseudo-assignment under state.withPath(...); ReturnFinder is rewritten to return TreePath objects; a pathWithLeaf helper ensures dataflow/refinement queries use paths whose leaf matches the target expression; stripParensAndUpdateTreePath now throws on mismatched path leaves.

Possibly related PRs

  • uber/NullAway#1328: Prior work threading TreePath through generics/inference code that this PR continues.
  • uber/NullAway#1305: Related TreePath/VisitorState propagation changes in GenericsChecks/GenericsUtils.
  • uber/NullAway#1309: Related generics inference plumbing and per-argument path handling changes.

Suggested labels

run-benchmarks

Suggested reviewers

  • yuxincs
  • lazaroclapp
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the primary change: adjusting TreePaths so they have the correct leaf before calling getTreeType, which is the main objective.
Linked Issues check ✅ Passed The PR successfully addresses #1479's objective: identifying call sites where stripParensAndUpdateTreePath would fall back, updating those sites to supply VisitorState with correct TreePath, and adding a RuntimeException to enforce correctness.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the linked issue: modifying NullabilityUtil to enforce correct path leaves, updating GenericsChecks and GenericsUtils to pass proper TreePaths, with no unrelated alterations.
Docstring Coverage ✅ Passed Docstring coverage is 81.82% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ 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 proper-tree-paths

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.

Caution

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

⚠️ Outside diff range comments (3)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (2)

2192-2202: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Prefer the caller's structurally-correct parent path over re-deriving it from state.getPath().

When path.getLeaf() != assignment, pathWithLeaf(path, assignment) produces a TreePath where assignment is appended below the current state path (typically the invocation), even though in the real AST the assignment is an ancestor of that invocation. The current getTreeType implementation only requires leaf-correctness for AssignmentTree/VariableTree, so this happens to work today, but it relies on the same "sketchy" pattern called out in the refineArgumentTypeWithDataflow comment. Here, the immediate caller (getInvocationAndContextForInference) already computed a structurally-correct parentPath to the assignment — threading it through avoids the issue entirely and makes the path safe for any future use.

♻️ Suggested change (call-site + signature)
     if (parent instanceof AssignmentTree || parent instanceof VariableTree) {
-      return getInvocationInferenceInfoForAssignment(parent, invocation, state);
+      return getInvocationInferenceInfoForAssignment(parent, parentPath, invocation, state);
     }
   private InvocationAndContext getInvocationInferenceInfoForAssignment(
-      Tree assignment, MethodInvocationTree invocation, VisitorState state) {
+      Tree assignment, TreePath assignmentPath, MethodInvocationTree invocation, VisitorState state) {
     Preconditions.checkArgument(
         assignment instanceof AssignmentTree || assignment instanceof VariableTree);
-    TreePath path = state.getPath();
-    if (path.getLeaf() != assignment) {
-      state = state.withPath(pathWithLeaf(path, assignment));
+    if (state.getPath().getLeaf() != assignment) {
+      state = state.withPath(assignmentPath);
     }
     Type treeType = getTreeType(assignment, state);
     return new InvocationAndContext(invocation, treeType, isAssignmentToLocalVariable(assignment));
   }
🤖 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/GenericsChecks.java` around
lines 2192 - 2202, The current getInvocationInferenceInfoForAssignment
re-derives a TreePath from state.getPath() and calls pathWithLeaf, which can
produce an incorrect descendant path; change the API to accept the caller's
structurally-correct parent TreePath (e.g., add a TreePath parentPath parameter
or pass through the existing parentPath from
getInvocationAndContextForInference) and use that parentPath when creating the
path for state.withPath instead of path.getLeaf()/pathWithLeaf; update the
caller (getInvocationAndContextForInference) to pass its computed parentPath,
and keep the remainder (calls to getTreeType and construction of
InvocationAndContext) unchanged.

1670-1700: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Hoist the duplicate pathWithLeaf(state.getPath(), currentActualParam) computation.

Lines 1678 and 1686 compute the same TreePath and bind it to two different local names (pathToActualParam and pathToParam). Since state and currentActualParam don't change between the two sites, you can compute this once and reuse it for both the getTreeType call and the inferGenericMethodCallType call. This also makes it clearer that the two call sites are operating on the same path context.

♻️ Suggested simplification
               Type actualParameterType = null;
               if (currentActualParam instanceof LambdaExpressionTree) {
                 maybeStorePolyExpressionTypeFromTarget(currentActualParam, formalParameter);
               }
               Type inferredPolyType = inferredPolyExpressionTypes.get(currentActualParam);
+              TreePath pathToActualParam = pathWithLeaf(state.getPath(), currentActualParam);
               if (inferredPolyType != null) {
                 actualParameterType = inferredPolyType;
               } else {
-                TreePath pathToActualParam = pathWithLeaf(state.getPath(), currentActualParam);
                 actualParameterType =
                     getTreeType(currentActualParam, state.withPath(pathToActualParam));
               }
               if (actualParameterType != null) {
                 if (isGenericCallNeedingInference(currentActualParam)) {
                   // infer the type of the method call based on the assignment context
                   // and the formal parameter type
-                  TreePath pathToParam = pathWithLeaf(state.getPath(), currentActualParam);
                   actualParameterType =
                       inferGenericMethodCallType(
-                          state.withPath(pathToParam),
+                          state.withPath(pathToActualParam),
                           (MethodInvocationTree) currentActualParam,
-                          pathToParam,
+                          pathToActualParam,
                           formalParameter,
                           false,
                           false);
                 }
🤖 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/GenericsChecks.java` around
lines 1670 - 1700, Compute TreePath once and reuse it: replace the two separate
calls to pathWithLeaf(state.getPath(), currentActualParam) (previously bound to
pathToActualParam and pathToParam) with a single local variable (e.g.,
pathToActualParam) created once before calling getTreeType and
inferGenericMethodCallType; then pass that same TreePath into getTreeType(...)
and into inferGenericMethodCallType(...) (used in the
isGenericCallNeedingInference branch) and remove the duplicate variable to avoid
recomputation while keeping references to currentActualParam, state,
getTreeType, and inferGenericMethodCallType intact.
nullaway/src/main/java/com/uber/nullaway/NullabilityUtil.java (1)

754-760: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Update the stale comment and drop the dead commented-out fallback.

The comment at lines 754–757 still describes the old fallback ("we just return the original expression and state ... remove this fallback case"), but the code now throws unconditionally on a leaf mismatch. Line 760 also leaves the previous return commented out as dead code. These should be brought in sync with the new behavior so future readers (and the issue tracker note) aren't misled.

♻️ Proposed cleanup
     TreePath path = state.getPath();
     if (path.getLeaf() != expr) {
-      // if the expression is not the leaf of the path, we can't update the path to point to the
-      // stripped expression, so we just return the original expression and state
-      // TODO fix all cases where this happens and remove this fallback case
-      //  Tracked in https://github.com/uber/NullAway/issues/1479
+      // The caller is required to pass a state whose tree path's leaf is `expr`; otherwise we
+      // cannot reliably update the path to point at the stripped expression.
+      // See https://github.com/uber/NullAway/issues/1479 for tracking remaining call sites.
       throw new RuntimeException(
           String.format("Wrong leaf %s in path to %s", path.getLeaf(), expr));
-      //      return new ExprTreeAndState(expr, state);
     }
🤖 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/NullabilityUtil.java` around lines
754 - 760, The comment describing a now-removed fallback is stale and the
commented-out return is dead; update the comment above the RuntimeException in
NullabilityUtil (the block that references path.getLeaf(), expr and currently
throws RuntimeException) to state that a leaf mismatch is a fatal/unrecoverable
error and that we intentionally throw, and remove the commented-out "return new
ExprTreeAndState(expr, state);" (and any other obsolete text referencing the old
fallback) so the code and comment accurately reflect the current behavior.
🤖 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.

Outside diff comments:
In `@nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java`:
- Around line 2192-2202: The current getInvocationInferenceInfoForAssignment
re-derives a TreePath from state.getPath() and calls pathWithLeaf, which can
produce an incorrect descendant path; change the API to accept the caller's
structurally-correct parent TreePath (e.g., add a TreePath parentPath parameter
or pass through the existing parentPath from
getInvocationAndContextForInference) and use that parentPath when creating the
path for state.withPath instead of path.getLeaf()/pathWithLeaf; update the
caller (getInvocationAndContextForInference) to pass its computed parentPath,
and keep the remainder (calls to getTreeType and construction of
InvocationAndContext) unchanged.
- Around line 1670-1700: Compute TreePath once and reuse it: replace the two
separate calls to pathWithLeaf(state.getPath(), currentActualParam) (previously
bound to pathToActualParam and pathToParam) with a single local variable (e.g.,
pathToActualParam) created once before calling getTreeType and
inferGenericMethodCallType; then pass that same TreePath into getTreeType(...)
and into inferGenericMethodCallType(...) (used in the
isGenericCallNeedingInference branch) and remove the duplicate variable to avoid
recomputation while keeping references to currentActualParam, state,
getTreeType, and inferGenericMethodCallType intact.

In `@nullaway/src/main/java/com/uber/nullaway/NullabilityUtil.java`:
- Around line 754-760: The comment describing a now-removed fallback is stale
and the commented-out return is dead; update the comment above the
RuntimeException in NullabilityUtil (the block that references path.getLeaf(),
expr and currently throws RuntimeException) to state that a leaf mismatch is a
fatal/unrecoverable error and that we intentionally throw, and remove the
commented-out "return new ExprTreeAndState(expr, state);" (and any other
obsolete text referencing the old fallback) so the code and comment accurately
reflect the current behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4b55719b-3ca9-4dba-aa2f-feddc9d7fe67

📥 Commits

Reviewing files that changed from the base of the PR and between dff4b58 and ca3f7df.

📒 Files selected for processing (3)
  • nullaway/src/main/java/com/uber/nullaway/NullabilityUtil.java
  • nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java
  • nullaway/src/main/java/com/uber/nullaway/generics/GenericsUtils.java

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.

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/GenericsChecks.java (1)

1047-1067: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Re-anchor state and path after stripping parentheses.

Line 1047 can change rhsExpr from a ParenthesizedTree to its child, but the method keeps using the old state/path. That makes getTreeType(rhsExpr, state) violate the new strict leaf invariant, and it also passes a stale path into recursive inference for parenthesized nested calls. Valid inputs like parenthesized actuals or lambda returns will now blow up here.

Suggested fix
-    rhsExpr = ASTHelpers.stripParentheses(rhsExpr);
+    ExpressionTree strippedRhsExpr = ASTHelpers.stripParentheses(rhsExpr);
+    if (strippedRhsExpr != rhsExpr) {
+      TreePath rhsPath = pathWithLeaf(path != null ? path : state.getPath(), strippedRhsExpr);
+      path = rhsPath;
+      state = state.withPath(rhsPath);
+    }
+    rhsExpr = strippedRhsExpr;

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 41a160f8-265d-4d4e-8072-f917b2e3b9e9

📥 Commits

Reviewing files that changed from the base of the PR and between ca3f7df and 2c422b4.

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

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.

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/GenericsChecks.java (1)

1983-1988: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Thread the explicit invocation path into nested-nullability repair.

substituteTypeArgsInGenericMethodType(...) already carries an explicit path for cases where state.getPath() is not the invocation leaf, but restoreNestedNullabilityForTypeVarArguments(...) drops that and rebuilds pathToInvocation from state.getPath() again. In those callers, Line 2031 can still derive argument paths from the wrong leaf, so the getTreeType(...) call on Line 2055 can miss the exact path/leaf alignment this PR is trying to enforce.

Proposed fix
-        methodTypeAtCallSite =
-            restoreNestedNullabilityForTypeVarArguments(
-                invocationTree, methodType, methodTypeAtCallSite, state);
+        methodTypeAtCallSite =
+            restoreNestedNullabilityForTypeVarArguments(
+                invocationTree, path, methodType, methodTypeAtCallSite, state);
@@
   private Type.MethodType restoreNestedNullabilityForTypeVarArguments(
       MethodInvocationTree invocationTree,
+      `@Nullable` TreePath invocationPath,
       Type.MethodType origMethodType,
       Type.MethodType methodTypeAtCallSite,
       VisitorState state) {
@@
-    TreePath pathToInvocation = pathWithLeaf(state.getPath(), invocationTree);
+    TreePath pathToInvocation =
+        invocationPath != null
+            ? pathWithLeaf(invocationPath, invocationTree)
+            : pathWithLeaf(state.getPath(), invocationTree);

Also applies to: 2017-2055

🤖 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/GenericsChecks.java` around
lines 1983 - 1988, The nested-nullability repair drops the explicit invocation
path: change restoreNestedNullabilityForTypeVarArguments to accept an explicit
TreePath/Path parameter (instead of always using state.getPath()), and use that
passed-in path when computing pathToInvocation/getTreeType; then update callers
(notably substituteTypeArgsInGenericMethodType and the call sites that build
methodTypeAtCallSite) to thread the explicit invocation leaf/path through into
the new parameter so argument paths are derived from the correct leaf. Ensure
method signatures and all call sites (including the branch handling
InferenceSuccess where methodTypeAtCallSite is set) are updated to pass the
invocationTree/path consistently.
🤖 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.

Outside diff comments:
In `@nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java`:
- Around line 1983-1988: The nested-nullability repair drops the explicit
invocation path: change restoreNestedNullabilityForTypeVarArguments to accept an
explicit TreePath/Path parameter (instead of always using state.getPath()), and
use that passed-in path when computing pathToInvocation/getTreeType; then update
callers (notably substituteTypeArgsInGenericMethodType and the call sites that
build methodTypeAtCallSite) to thread the explicit invocation leaf/path through
into the new parameter so argument paths are derived from the correct leaf.
Ensure method signatures and all call sites (including the branch handling
InferenceSuccess where methodTypeAtCallSite is set) are updated to pass the
invocationTree/path consistently.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 24a5183d-7813-4e2e-81b1-0b0155267288

📥 Commits

Reviewing files that changed from the base of the PR and between 2c422b4 and 53b7c66.

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

@msridhar msridhar requested review from seemantasaha and yuxincs May 9, 2026 00:14
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

Caution

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

⚠️ Outside diff range comments (3)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (3)

1221-1271: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider tightening ReturnFinder visibility.

ReturnFinder is declared package-private (static class ReturnFinder). It is only used within GenericsChecks, and other private helpers in this file (e.g. pathWithLeaf, the various report... methods) are already private. Marking the class private would match that style and prevent accidental external use.

♻️ Proposed refactor
-  static class ReturnFinder extends TreePathScanner<@Nullable Void, `@Nullable` Void> {
+  private static class ReturnFinder extends TreePathScanner<@Nullable Void, `@Nullable` Void> {
🤖 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/GenericsChecks.java` around
lines 1221 - 1271, ReturnFinder is declared package-private but is only used
inside GenericsChecks; make it a private static nested class to match the other
private helpers and prevent external use. Change the declaration of ReturnFinder
to private static class ReturnFinder and ensure any internal references (e.g.,
the static factory method findReturnPaths, the getReturnPaths accessor, and
overrides like visitLambdaExpression, visitClass, visitMethod, visitReturn)
remain accessible from within GenericsChecks; no other code changes should be
necessary if all usages are internal.

2187-2197: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Minor: defensive path.getLeaf() != assignment check is dead with the only current caller.

The only caller (getInvocationAndContextForInference, line 2122-2123) already invokes this with state.withPath(parentPath) where parentPath.getLeaf() == parent == assignment, so the branch never fires today. It's harmless as defensive code in case a future caller passes an unaligned state, but you could simplify to an unconditional state.withPath(pathWithLeaf(state.getPath(), assignment)) (which is also a no-op when leaves match) or drop the wrapping entirely and require callers to pre-align. Up to you.

🤖 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/GenericsChecks.java` around
lines 2187 - 2197, The conditional defensive check in
getInvocationInferenceInfoForAssignment is unnecessary given current callers;
replace the if-check and conditional state reassignment with an unconditional
state = state.withPath(pathWithLeaf(path, assignment)) (using the existing local
path and assignment) so alignment is always applied (it's a no-op when
path.getLeaf() already equals assignment) and leave getTreeType and the
InvocationAndContext creation unchanged; alternatively, if you prefer requiring
callers to pass an aligned state, remove the path alignment entirely—if you
choose removal, update or document callers such as
getInvocationAndContextForInference to ensure they pass a state whose leaf
equals the assignment.

2299-2312: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Consider using symmetric state path handling in both branches for consistency and maintainability.

The current code is functionally safe: although the if branch passes unmodified state to inferGenericMethodCallType, the receiverPath parameter is used at line 1016 in generateConstraintsForCall to establish the pathToInvocation basis. By the time stripParensAndUpdateTreePath is invoked within generateConstraintsForPseudoAssignment, the state has been properly re-anchored via state.withPath(pathToArgument), so the throwing contract in stripParensAndUpdateTreePath (which requires path.getLeaf() == expr) will not be triggered.

However, the asymmetry is unnecessarily confusing: the if branch effectively delegates path correction to the path parameter, while the else branch (line 2311) directly passes the corrected state. For code clarity and to protect against future refactoring, consider applying the same pattern in both branches by using state.withPath(receiverPath) in the inference branch as suggested.

♻️ Suggested symmetric update
         if (isGenericCallNeedingInference(receiver)) {
           enclosingType =
               inferGenericMethodCallType(
-                  state,
+                  state.withPath(receiverPath),
                   (MethodInvocationTree) receiver,
                   receiverPath,
                   null,
                   false,
                   calledFromDataflow);
         } else {
           enclosingType = getTreeType(receiver, state.withPath(receiverPath));
         }
🤖 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/GenericsChecks.java` around
lines 2299 - 2312, The two branches handling receiver path are asymmetric: the
else branch calls getTreeType(state.withPath(receiverPath)) but the if branch
calls inferGenericMethodCallType(state, ..., receiverPath, ...) leaving state
unmodified; change the if branch to pass state.withPath(receiverPath) into
inferGenericMethodCallType so both branches consistently anchor the state to
receiverPath (referencing inferGenericMethodCallType, getTreeType, receiverPath,
and state.withPath).
🤖 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/GenericsChecks.java`:
- Around line 789-794: The explicit path argument passed alongside an updated
state is redundant: when you call inferGenericMethodCallType /
generateConstraintsForCall with state.withPath(pathToRhs) (or
state.withPath(pathToRetExpr)) and also pass the same path (e.g., the call site
that currently passes state.withPath(pathToRhs), (MethodInvocationTree) rhsTree,
pathToRhs, ...), remove the duplicate by passing null for the explicit path
parameter (or otherwise stop passing pathToRhs) so generateConstraintsForCall
will derive the path from state.getPath(); apply the same change to the
analogous call sites (including the other occurrence noted around the second
block) where the explicit path equals the new state path.

---

Outside diff comments:
In `@nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java`:
- Around line 1221-1271: ReturnFinder is declared package-private but is only
used inside GenericsChecks; make it a private static nested class to match the
other private helpers and prevent external use. Change the declaration of
ReturnFinder to private static class ReturnFinder and ensure any internal
references (e.g., the static factory method findReturnPaths, the getReturnPaths
accessor, and overrides like visitLambdaExpression, visitClass, visitMethod,
visitReturn) remain accessible from within GenericsChecks; no other code changes
should be necessary if all usages are internal.
- Around line 2187-2197: The conditional defensive check in
getInvocationInferenceInfoForAssignment is unnecessary given current callers;
replace the if-check and conditional state reassignment with an unconditional
state = state.withPath(pathWithLeaf(path, assignment)) (using the existing local
path and assignment) so alignment is always applied (it's a no-op when
path.getLeaf() already equals assignment) and leave getTreeType and the
InvocationAndContext creation unchanged; alternatively, if you prefer requiring
callers to pass an aligned state, remove the path alignment entirely—if you
choose removal, update or document callers such as
getInvocationAndContextForInference to ensure they pass a state whose leaf
equals the assignment.
- Around line 2299-2312: The two branches handling receiver path are asymmetric:
the else branch calls getTreeType(state.withPath(receiverPath)) but the if
branch calls inferGenericMethodCallType(state, ..., receiverPath, ...) leaving
state unmodified; change the if branch to pass state.withPath(receiverPath) into
inferGenericMethodCallType so both branches consistently anchor the state to
receiverPath (referencing inferGenericMethodCallType, getTreeType, receiverPath,
and state.withPath).
🪄 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

Run ID: e6d95894-5572-4f78-ad8c-ebe21e944f32

📥 Commits

Reviewing files that changed from the base of the PR and between fa3f02e and f9bd902.

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

@codecov
Copy link
Copy Markdown

codecov Bot commented May 9, 2026

Codecov Report

❌ Patch coverage is 89.06250% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.40%. Comparing base (dff4b58) to head (f9bd902).

Files with missing lines Patch % Lines
...ava/com/uber/nullaway/generics/GenericsChecks.java 91.52% 1 Missing and 4 partials ⚠️
...c/main/java/com/uber/nullaway/NullabilityUtil.java 0.00% 2 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master    #1570      +/-   ##
============================================
- Coverage     88.44%   88.40%   -0.04%     
  Complexity     2881     2881              
============================================
  Files           103      103              
  Lines          9609     9637      +28     
  Branches       1938     1940       +2     
============================================
+ Hits           8499     8520      +21     
- Misses          532      535       +3     
- Partials        578      582       +4     

☔ 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 merged commit 9b04703 into master May 9, 2026
13 of 14 checks passed
@msridhar msridhar deleted the proper-tree-paths branch May 9, 2026 05:14
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.

Remove fallback case in stripParensAndUpdateTreePath

2 participants