Skip to content

Initial handling of constructor diamond operators#1464

Merged
msridhar merged 41 commits intomasterfrom
issue-1451
Feb 21, 2026
Merged

Initial handling of constructor diamond operators#1464
msridhar merged 41 commits intomasterfrom
issue-1451

Conversation

@msridhar
Copy link
Copy Markdown
Collaborator

@msridhar msridhar commented Feb 7, 2026

Fixes #1451

This adds partial support for diamond operators, sufficient to fix #1451. We still need to fully support diamonds in generic method inference.

Summary by CodeRabbit

  • New Features

    • Context- and path-aware type inference for Java diamond (<>) constructor calls using surrounding assignment/return/parameter contexts.
  • Enhancements

    • Preserves explicit type arguments and more accurately propagates inferred generics and nullability across assignments, returns, and calls.
    • Better parenthesized-expression handling with updated path-aware context tracking; error reporting and nullability checks reflect these inferences.
  • Tests

    • Added tests covering diamond inference, nested/parenthesized constructors, parameter/return scenarios, and nullability interactions.
  • Public API

    • Added a small public utility that returns an expression paired with an updated traversal state for path-aware processing.

@codecov
Copy link
Copy Markdown

codecov Bot commented Feb 8, 2026

Codecov Report

❌ Patch coverage is 84.31373% with 16 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.41%. Comparing base (bc7bae8) to head (caa54dd).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
...ava/com/uber/nullaway/generics/GenericsChecks.java 82.60% 3 Missing and 13 partials ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master    #1464      +/-   ##
============================================
- Coverage     88.47%   88.41%   -0.06%     
- Complexity     2759     2782      +23     
============================================
  Files            99       99              
  Lines          9204     9283      +79     
  Branches       1848     1872      +24     
============================================
+ Hits           8143     8208      +65     
- Misses          521      524       +3     
- Partials        540      551      +11     

☔ 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 changed the title [wip] initial handling of constructor diamond operators Initial handling of constructor diamond operators Feb 15, 2026
@msridhar msridhar marked this pull request as ready for review February 15, 2026 00:05
@msridhar msridhar requested a review from lazaroclapp February 15, 2026 00:06
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 15, 2026

Walkthrough

Adds path- and context-aware type inference for Java diamond (<>) constructor calls in GenericsChecks, inferring class type arguments from assignment/return/parameter/parent contexts when no anonymous class body is present. Threads a TreePath-aware VisitorState through getTreeType and related call sites to preserve type-use nullability, introduces multiple private helpers for diamond/parent-path discovery and parenthesis stripping, updates nullability-check call sites to use path-aware types, and adds tests (GenericDiamondTests). All changes are internal and do not change public API signatures.

Possibly related PRs

Suggested labels

jspecify

Suggested reviewers

  • yuxincs
  • lazaroclapp
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Initial handling of constructor diamond operators' clearly and specifically describes the main change: adding support for Java diamond operators in constructor calls.
Linked Issues check ✅ Passed The code changes successfully address issue #1451 by adding context-aware type inference for diamond operators in constructor calls, enabling correct handling of annotated types like @Nullable Void.
Out of Scope Changes check ✅ Passed All changes are directly related to the PR objective of handling constructor diamond operators: GenericsChecks enhancements for diamond inference, new test class for diamond scenarios, and NullabilityUtil utilities for path tracking.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch issue-1451

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.

@github-actions
Copy link
Copy Markdown

Main Branch:

Benchmark                          Mode  Cnt   Score   Error  Units
AutodisposeBenchmark.compile      thrpt   25  10.576 ± 0.060  ops/s
CaffeineBenchmark.compile         thrpt   25   2.003 ± 0.015  ops/s
DFlowMicroBenchmark.compile       thrpt   25  41.570 ± 0.307  ops/s
NullawayReleaseBenchmark.compile  thrpt   25   1.659 ± 0.012  ops/s

With This PR:

Benchmark                          Mode  Cnt   Score   Error  Units
AutodisposeBenchmark.compile      thrpt   25  10.622 ± 0.055  ops/s
CaffeineBenchmark.compile         thrpt   25   1.967 ± 0.008  ops/s
DFlowMicroBenchmark.compile       thrpt   25  41.532 ± 0.481  ops/s
NullawayReleaseBenchmark.compile  thrpt   25   1.659 ± 0.012  ops/s

@msridhar
Copy link
Copy Markdown
Collaborator Author

@coderabbitai resume

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 20, 2026

✅ Actions performed

Reviews resumed.

@msridhar
Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 20, 2026

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java`:
- Around line 573-583: getDiamondTypeFromParentContext currently returns null
for NewClassTree diamonds inside lambdas (expression or block) because the
ReturnTree/LambdaExpressionTree cases only handle enclosing MethodTree; add
TODOs and fix points: in getDiamondTypeFromParentContext and the
ReturnTree/LambdaExpressionTree handling (referencing symbols ReturnTree,
LambdaExpressionTree, NewClassTree, getDiamondTypeFromParentContext, and
NullabilityUtil.findEnclosingMethodOrLambdaOrInitializer) note that if enclosing
context is a lambda rather than a MethodTree we should attempt to derive the
target type from the lambda's target (expression lambda owner or block lambda
enclosing LambdaExpressionTree) and preserve nullability annotations instead of
falling back to javac; add TODO comments where the method currently returns null
indicating to implement extraction of the lambda target type and propagate
nullability, and link to the existing MethodTree branch as the intended
resolution pattern.
- Around line 598-611: The block handling parent instanceof NewClassTree must
null-check the constructor symbol: after obtaining Symbol parentCtorSymbol =
ASTHelpers.getSymbol(parentConstructorCall) guard if (parentCtorSymbol == null)
and return/fallback (e.g., return null) instead of calling
TypeSubstitutionUtils.memberType with a null symbol; specifically add the null
check before invoking TypeSubstitutionUtils.memberType and only call
getFormalParameterTypeForArgument(parentConstructorCall,
parentCtorType.asMethodType(), tree) when parentCtorSymbol is non-null.

---

Duplicate comments:
In `@nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java`:
- Around line 1613-1627: The diamond inference integration in
compareGenericTypeParameterNullabilityForCall (the new handling around
NewClassTree/newClassTree and enclosingType) is correct and requires no code
changes—keep the current logic that checks hasInferredClassTypeArguments, guards
with state.getPath()/ASTHelpers.stripParentheses, calls
getDiamondTypeFromParentContext(newClassTree, state, parentPath) to set
enclosingType, and falls back to getEnclosingTypeForCallExpression(methodSymbol,
tree, null, state, false) when null; proceed to merge as-is.

Comment on lines +598 to +611
if (parent instanceof NewClassTree parentConstructorCall) {
// get the type returned by the parent constructor call
Type parentClassType = getTreeType(parentConstructorCall, state.withPath(parentPath));
if (parentClassType != null) {
Symbol parentCtorSymbol = ASTHelpers.getSymbol(parentConstructorCall);
// get the proper type for the constructor, as a member of the type returned by the
// constructor
Type parentCtorType =
TypeSubstitutionUtils.memberType(
state.getTypes(), parentClassType, parentCtorSymbol, config);
return getFormalParameterTypeForArgument(
parentConstructorCall, parentCtorType.asMethodType(), tree);
}
}
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.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for ASTHelpers.getSymbol usage with NewClassTree in the codebase
rg -n --type=java 'ASTHelpers\.getSymbol\(' nullaway/src/main/java/ | head -50

Repository: uber/NullAway

Length of output: 7770


🏁 Script executed:

#!/bin/bash
# Check the specific file and surrounding context for how getSymbol is used with NewClassTree
# Also look for other NewClassTree getSymbol usages
rg -n --type=java -B2 -A2 'NewClassTree' nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java | grep -A5 -B5 'getSymbol'

Repository: uber/NullAway

Length of output: 39


🏁 Script executed:

#!/bin/bash
# Look at the exact context in the file at lines 598-611 and check for null checks
sed -n '590,620p' nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java

Repository: uber/NullAway

Length of output: 1324


🏁 Script executed:

#!/bin/bash
# Check TypeSubstitutionUtils.memberType implementation to see if it handles null
rg -n --type=java 'public.*memberType' nullaway/src/main/java/com/uber/nullaway/

Repository: uber/NullAway

Length of output: 218


🏁 Script executed:

#!/bin/bash
# Search for TypeSubstitutionUtils.memberType to understand its signature
rg -n --type=java -A10 'static.*memberType' nullaway/src/main/java/com/uber/nullaway/

Repository: uber/NullAway

Length of output: 1493


🏁 Script executed:

#!/bin/bash
# Check if there are other places in the codebase where getSymbol(NewClassTree) is used
# and how they handle potential null
rg -n --type=java -B3 -A3 'NewClassTree' nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java | head -100

Repository: uber/NullAway

Length of output: 4962


🏁 Script executed:

#!/bin/bash
# Check the implementation details and null handling in TypeSubstitutionUtils.memberType
cat -n nullaway/src/main/java/com/uber/nullaway/generics/TypeSubstitutionUtils.java | sed -n '67,77p'

Repository: uber/NullAway

Length of output: 690


🏁 Script executed:

#!/bin/bash
# Look for any defensive null checks after getSymbol(NewClassTree) in the codebase
rg -n --type=java -B2 -A5 'NewClassTree.*=' nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java | grep -A5 'getSymbol'

Repository: uber/NullAway

Length of output: 39


🏁 Script executed:

#!/bin/bash
# Check if there are other instances where getSymbol result from NewClassTree is used
# and whether null checks are applied
rg -n --type=java 'instanceof NewClassTree' nullaway/src/main/java/ -A10 | grep -A10 'getSymbol'

Repository: uber/NullAway

Length of output: 1358


🏁 Script executed:

#!/bin/bash
# Check Error Prone's ASTHelpers documentation or implementation for getSymbol behavior
# First, let's see if there's any documentation in NullAway about this
rg -n --type=java 'getSymbol.*NewClass' nullaway/src/main/java/ -B5 -A10

Repository: uber/NullAway

Length of output: 39


🏁 Script executed:

#!/bin/bash
# Check if there are any null checks or error handling after getSymbol(NewClassTree)
# in the entire codebase
rg -n --type=java 'getSymbol\(' nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java | grep -A2 -B2 '602'

Repository: uber/NullAway

Length of output: 436


🏁 Script executed:

#!/bin/bash
# Look at how getSymbol is typically used after being called
# Check patterns of null checking around line 522 where memberSelectSymbol is retrieved
sed -n '515,530p' nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java

Repository: uber/NullAway

Length of output: 925


🏁 Script executed:

#!/bin/bash
# Let's see the context around line 522 more clearly to confirm null checking pattern
sed -n '520,530p' nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java

Repository: uber/NullAway

Length of output: 639


🏁 Script executed:

#!/bin/bash
# Look at how other similar cases handle getSymbol - check line 502 (assignmentTree)
sed -n '500,510p' nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java

Repository: uber/NullAway

Length of output: 542


🏁 Script executed:

#!/bin/bash
# Check if there are Error Prone docs or other references about getSymbol nullability
# Let's look for any Nullable annotations on the method
rg -n --type=java '@Nullable.*getSymbol|getSymbol.*@Nullable' nullaway/src/

Repository: uber/NullAway

Length of output: 39


🏁 Script executed:

#!/bin/bash
# Final confirmation: check if there are any explicit null-safety guarantees in Error Prone
# by looking at how other overloads of getSymbol are documented/used in NullAway
rg -n --type=java 'ASTHelpers\.getSymbol\(' nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java | head -20

Repository: uber/NullAway

Length of output: 1665


🏁 Script executed:

#!/bin/bash
# Check the broader pattern: do all other Symbol assignments from getSymbol have null checks?
rg -n --type=java 'Symbol.*=.*ASTHelpers\.getSymbol' nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java | wc -l

Repository: uber/NullAway

Length of output: 59


🏁 Script executed:

#!/bin/bash
# Count how many of these have immediate null checks following them
# Let's look at a few more examples to establish the pattern
sed -n '645,680p' nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java

Repository: uber/NullAway

Length of output: 1860


Add null guard for parentCtorSymbol to prevent potential NPE.

ASTHelpers.getSymbol(parentConstructorCall) can return null. The codebase pattern shows null checks for getSymbol() results on MemberSelectTree (line 522) and AssignmentTree (line 502). If parentCtorSymbol is null, TypeSubstitutionUtils.memberType() at line 606 will dereference sym.type and throw an NPE.

🛡️ Proposed null guard
     if (parent instanceof NewClassTree parentConstructorCall) {
       // get the type returned by the parent constructor call
       Type parentClassType = getTreeType(parentConstructorCall, state.withPath(parentPath));
       if (parentClassType != null) {
         Symbol parentCtorSymbol = ASTHelpers.getSymbol(parentConstructorCall);
+        if (parentCtorSymbol == null) {
+          return null;
+        }
         // get the proper type for the constructor, as a member of the type returned by the
         // constructor
         Type parentCtorType =
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java` around
lines 598 - 611, The block handling parent instanceof NewClassTree must
null-check the constructor symbol: after obtaining Symbol parentCtorSymbol =
ASTHelpers.getSymbol(parentConstructorCall) guard if (parentCtorSymbol == null)
and return/fallback (e.g., return null) instead of calling
TypeSubstitutionUtils.memberType with a null symbol; specifically add the null
check before invoking TypeSubstitutionUtils.memberType and only call
getFormalParameterTypeForArgument(parentConstructorCall,
parentCtorType.asMethodType(), tree) when parentCtorSymbol is non-null.

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.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java`:
- Around line 598-611: The code calls
ASTHelpers.getSymbol(parentConstructorCall) which can return null; add a null
guard after obtaining parentCtorSymbol and bail out (e.g., return null or
continue) before calling TypeSubstitutionUtils.memberType to avoid an NPE.
Specifically, in the block handling parent instanceof NewClassTree, check if
parentCtorSymbol is null and handle it (returning null from this method or
skipping the substitution and subsequent getFormalParameterTypeForArgument call)
so TypeSubstitutionUtils.memberType and parentCtorSymbol.type are never
dereferenced when parentCtorSymbol is null.

@github-actions
Copy link
Copy Markdown

Main Branch:

Benchmark                          Mode  Cnt   Score   Error  Units
AutodisposeBenchmark.compile      thrpt   25  10.455 ± 0.095  ops/s
CaffeineBenchmark.compile         thrpt   25   1.974 ± 0.022  ops/s
DFlowMicroBenchmark.compile       thrpt   25  40.552 ± 0.582  ops/s
NullawayReleaseBenchmark.compile  thrpt   25   1.629 ± 0.021  ops/s

With This PR:

Benchmark                          Mode  Cnt   Score   Error  Units
AutodisposeBenchmark.compile      thrpt   25  10.331 ± 0.116  ops/s
CaffeineBenchmark.compile         thrpt   25   1.935 ± 0.027  ops/s
DFlowMicroBenchmark.compile       thrpt   25  40.432 ± 0.378  ops/s
NullawayReleaseBenchmark.compile  thrpt   25   1.619 ± 0.028  ops/s

Copy link
Copy Markdown
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.

LGTM with one nit / potential follow up consideration.

Comment thread nullaway/src/main/java/com/uber/nullaway/NullabilityUtil.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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@nullaway/src/main/java/com/uber/nullaway/NullabilityUtil.java`:
- Line 734: Add a Javadoc comment for the public record ExprTreeAndState to
match the style of other public types in the file: describe the purpose of the
record and its components (ExpressionTree expr and VisitorState state), include
a short summary sentence and `@param` tags for expr and state, and place it
immediately above the record declaration `public record
ExprTreeAndState(ExpressionTree expr, VisitorState state) {}` so tooling and
generated docs will include it.
- Around line 736-752: Update the contract on stripParensAndUpdateTreePath:
change the Javadoc to state that the method requires the provided ExpressionTree
to be the leaf of the VisitorState path (i.e., a hard precondition), remove the
commented-out dead code "return new ExprTreeAndState(expr, state)" and replace
the RuntimeException thrown when the precondition is violated with an idiomatic
precondition check (use IllegalArgumentException or Preconditions.checkArgument)
so callers get a clear, appropriate exception type; references: method
stripParensAndUpdateTreePath, return type ExprTreeAndState, parameter
VisitorState.

Comment thread nullaway/src/main/java/com/uber/nullaway/NullabilityUtil.java
Comment on lines +736 to +752
/**
* strip out enclosing parentheses, and update the tree path in the VisitorState to point to the
* stripped expression if the original expression was the leaf of the path
*
* @param expr a potentially parenthesised expression.
* @param state the VisitorState
* @return the same expression without parentheses, and the updated VisitorState
*/
public static ExprTreeAndState stripParensAndUpdateTreePath(
ExpressionTree expr, VisitorState state) {
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
// return new ExprTreeAndState(expr, state);
throw new RuntimeException(
"stripParensAndUpdateTreePath should only be called when the expression is the leaf of the VisitorState's path");
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.

⚠️ Potential issue | 🟠 Major

Three issues: misleading Javadoc, dead commented-out code, and overly broad exception type.

  1. Javadoc vs. implementation mismatch: The Javadoc says "update the tree path … if the original expression was the leaf of the path", which implies a soft guard (return unchanged on mismatch). The implementation unconditionally throws. Revise the Javadoc to document the hard precondition/contract.

  2. Dead code (Line 750): The commented-out return new ExprTreeAndState(expr, state) is a development artifact and should be removed.

  3. RuntimeException for a precondition violation (Lines 751–752): IllegalArgumentException (or Preconditions.checkArgument, which is already imported) is the idiomatic choice for this kind of input-contract enforcement.

🔧 Proposed fix
  /**
-  * strip out enclosing parentheses, and update the tree path in the VisitorState to point to the
-  * stripped expression if the original expression was the leaf of the path
+  * Strips enclosing parentheses from {`@code` expr} and updates the {`@link` TreePath} in the
+  * returned {`@link` VisitorState} to reflect the unparenthesized expression.
+  *
+  * <p>Requires that {`@code` expr} is the leaf of {`@code` state}'s current path.
   *
-  * `@param` expr a potentially parenthesised expression.
+  * `@param` expr a potentially parenthesized expression; must be the leaf of {`@code` state}'s path
   * `@param` state the VisitorState
   * `@return` the same expression without parentheses, and the updated VisitorState
+  * `@throws` IllegalArgumentException if {`@code` expr} is not the leaf of {`@code` state}'s path
   */
  public static ExprTreeAndState stripParensAndUpdateTreePath(
      ExpressionTree expr, VisitorState state) {
    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
-     // return new ExprTreeAndState(expr, state);
-     throw new RuntimeException(
-         "stripParensAndUpdateTreePath should only be called when the expression is the leaf of the VisitorState's path");
-   }
+   Preconditions.checkArgument(
+       path.getLeaf() == expr,
+       "stripParensAndUpdateTreePath: expr must be the leaf of state's path");
    ExpressionTree resultExpr = expr;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nullaway/src/main/java/com/uber/nullaway/NullabilityUtil.java` around lines
736 - 752, Update the contract on stripParensAndUpdateTreePath: change the
Javadoc to state that the method requires the provided ExpressionTree to be the
leaf of the VisitorState path (i.e., a hard precondition), remove the
commented-out dead code "return new ExprTreeAndState(expr, state)" and replace
the RuntimeException thrown when the precondition is violated with an idiomatic
precondition check (use IllegalArgumentException or Preconditions.checkArgument)
so callers get a clear, appropriate exception type; references: method
stripParensAndUpdateTreePath, return type ExprTreeAndState, parameter
VisitorState.

@msridhar msridhar enabled auto-merge (squash) February 21, 2026 18:43
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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@nullaway/src/main/java/com/uber/nullaway/NullabilityUtil.java`:
- Around line 739-756: Update the `@return` Javadoc for
stripParensAndUpdateTreePath to accurately describe both outcomes: when
path.getLeaf() == expr it returns the stripped (parentheses-removed) expression
along with an updated VisitorState, and when path.getLeaf() != expr it returns
the original expression and the original VisitorState without modification (the
fallback case that preserves the potentially parenthesized expr). Mention the
returned types (ExprTreeAndState) and reference the behavior tied to the
parameters expr and state so readers can map the doc to the implementation.

---

Duplicate comments:
In `@nullaway/src/main/java/com/uber/nullaway/NullabilityUtil.java`:
- Around line 734-737: The Javadoc for the record ExprTreeAndState (used by
stripParensAndUpdateTreePath) is present and correct, so no code changes are
required; keep the record declaration public record
ExprTreeAndState(ExpressionTree expr, VisitorState state) {} and its Javadoc
as-is.

Comment on lines +739 to +756
/**
* strip out enclosing parentheses, and update the tree path in the VisitorState to point to the
* stripped expression if the original expression was the leaf of the path
*
* @param expr a potentially parenthesised expression.
* @param state the VisitorState
* @return the same expression without parentheses, and the updated VisitorState
*/
public static ExprTreeAndState stripParensAndUpdateTreePath(
ExpressionTree expr, VisitorState state) {
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
return new ExprTreeAndState(expr, state);
}
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.

⚠️ Potential issue | 🟡 Minor

@return Javadoc is inaccurate for the fallback path.

When path.getLeaf() != expr (line 750), the method returns the original, potentially still-parenthesized expression — not "the same expression without parentheses". The @return tag should document both branches.

✏️ Proposed fix
-  * `@return` the same expression without parentheses, and the updated VisitorState
+  * `@return` if {`@code` expr} is the leaf of {`@code` state}'s path, the expression with enclosing
+  *     parentheses stripped and a correspondingly updated {`@link` VisitorState}; otherwise
+  *     {`@code` expr} and {`@code` state} unchanged (see TODO in body, tracked in
+  *     <a href="https://github.com/uber/NullAway/issues/1479">issue 1479</a>)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nullaway/src/main/java/com/uber/nullaway/NullabilityUtil.java` around lines
739 - 756, Update the `@return` Javadoc for stripParensAndUpdateTreePath to
accurately describe both outcomes: when path.getLeaf() == expr it returns the
stripped (parentheses-removed) expression along with an updated VisitorState,
and when path.getLeaf() != expr it returns the original expression and the
original VisitorState without modification (the fallback case that preserves the
potentially parenthesized expr). Mention the returned types (ExprTreeAndState)
and reference the behavior tied to the parameters expr and state so readers can
map the doc to the implementation.

@msridhar msridhar merged commit 6945664 into master Feb 21, 2026
10 of 12 checks passed
@msridhar msridhar deleted the issue-1451 branch February 21, 2026 18:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incompatible types error with @Nullable Void

2 participants