Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
a1e4bb8
WIP
msridhar Jan 29, 2026
22fb655
test case
msridhar Feb 6, 2026
e86b344
tweak test
msridhar Feb 6, 2026
81f952c
better tests
msridhar Feb 6, 2026
03ce931
WIP
msridhar Feb 6, 2026
dfba3e5
docs
msridhar Feb 6, 2026
fdf01ee
WIP
msridhar Feb 6, 2026
4446516
simplify
msridhar Feb 6, 2026
dc3a227
more
msridhar Feb 6, 2026
471701d
simplify
msridhar Feb 6, 2026
aea98ae
simplify
msridhar Feb 6, 2026
0e2df18
reuse helper method
msridhar Feb 6, 2026
e607850
bug fix
msridhar Feb 6, 2026
d1636f7
fix nested constructors
msridhar Feb 7, 2026
c6c1e9a
fix diagnostic message
msridhar Feb 7, 2026
68ad56d
simplify
msridhar Feb 7, 2026
02d32b6
small fix
msridhar Feb 8, 2026
ec32f89
another fix
msridhar Feb 8, 2026
bae860f
Merge branch 'master' into issue-1451
msridhar Feb 8, 2026
e1d8386
Merge branch 'master' into issue-1451
msridhar Feb 14, 2026
89bb268
failing test
msridhar Feb 14, 2026
36ac60c
test fix
msridhar Feb 14, 2026
65fc519
tweaks
msridhar Feb 14, 2026
2454efd
docs
msridhar Feb 15, 2026
df8f89e
add issue links
msridhar Feb 15, 2026
82e633e
remove unnecessary code
msridhar Feb 15, 2026
319f453
more coderabbit comments
msridhar Feb 15, 2026
1374e89
Merge branch 'master' into issue-1451
msridhar Feb 17, 2026
4babffe
test of Void without @Nullable
msridhar Feb 18, 2026
78501f2
extract helper to check for raw types and use for NewClassTrees
msridhar Feb 18, 2026
2c50426
add tracking issue
msridhar Feb 18, 2026
95ee81d
add another link to issue
msridhar Feb 18, 2026
39898d7
tweak comment
msridhar Feb 18, 2026
bfd95f7
get rid of withPathToSubtree
msridhar Feb 19, 2026
d50bf7b
WIP
msridhar Feb 19, 2026
bc76012
fixes
msridhar Feb 19, 2026
2883e5a
add case and TODO for ConditionalExpressionTree
msridhar Feb 19, 2026
e321bbc
Merge branch 'master' into issue-1451
msridhar Feb 21, 2026
dc9d2dc
try throwing
msridhar Feb 21, 2026
829490a
don't throw, but track in an issue
msridhar Feb 21, 2026
caa54dd
add javadoc
msridhar Feb 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
WIP
  • Loading branch information
msridhar committed Feb 19, 2026
commit d50bf7b838bbfc547f4f5ed2de5e3f102e16ad85
27 changes: 27 additions & 0 deletions nullaway/src/main/java/com/uber/nullaway/NullabilityUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -730,4 +730,31 @@ public static ExpressionTree stripParensAndCasts(ExpressionTree expr) {
}
return expr;
}

public record ExprTreeAndState(ExpressionTree expr, VisitorState state) {}
Comment thread
msridhar marked this conversation as resolved.

/**
* 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
Comment thread
msridhar marked this conversation as resolved.
return new ExprTreeAndState(expr, state);
}
Comment on lines +739 to +756
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.

ExpressionTree resultExpr = expr;
while (resultExpr instanceof ParenthesizedTree) {
resultExpr = ((ParenthesizedTree) resultExpr).getExpression();
path = new TreePath(path, resultExpr);
}
VisitorState resultState = path == state.getPath() ? state : state.withPath(path);
return new ExprTreeAndState(resultExpr, resultState);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
import com.sun.source.util.TreePathScanner;
import com.sun.source.util.TreeScanner;
import com.sun.tools.javac.code.Attribute;
import com.sun.tools.javac.code.Symbol;
Expand Down Expand Up @@ -425,7 +424,12 @@ private void reportInvalidOverridingMethodParamTypeError(
* @return Type of the tree with preserved annotations.
*/
private @Nullable Type getTreeType(Tree tree, VisitorState state) {
tree = ASTHelpers.stripParentheses(tree);
if (tree instanceof ExpressionTree exprTree) {
NullabilityUtil.ExprTreeAndState exprTreeAndState =
NullabilityUtil.stripParensAndUpdateTreePath(exprTree, state);
tree = exprTreeAndState.expr();
state = exprTreeAndState.state();
}
if (tree instanceof LambdaExpressionTree || tree instanceof MemberReferenceTree) {
Type result = inferredPolyExpressionTypes.get(tree);
if (result == null) {
Expand Down Expand Up @@ -545,10 +549,10 @@ private void reportInvalidOverridingMethodParamTypeError(
* available.
*/
private @Nullable Type getDiamondTypeFromContext(NewClassTree tree, VisitorState state) {
TreePath treePath = findPathToSubtree(state.getPath(), tree);
if (treePath == null) {
return null;
}
TreePath treePath = state.getPath();
// if (treePath == null) {
// return null;
// }
return getDiamondTypeFromParentContext(tree, state, castToNonNull(treePath.getParentPath()));
}

Expand Down Expand Up @@ -629,28 +633,28 @@ private void reportInvalidOverridingMethodParamTypeError(
}
Comment thread
msridhar marked this conversation as resolved.

/** Finds the path to {@code target} within {@code rootPath}, or null when not found. */
private static @Nullable TreePath findPathToSubtree(TreePath rootPath, Tree target) {
if (rootPath.getLeaf() == target) {
return rootPath;
}
return new TreePathScanner<@Nullable TreePath, @Nullable TreePath>() {
@Override
public @Nullable TreePath scan(Tree tree, @Nullable TreePath prevPath) {
if (tree == target) {
// When overriding scan(), getCurrentPath() still points at the parent.
return new TreePath(getCurrentPath(), tree);
}
return super.scan(tree, null);
}

@Override
public @Nullable TreePath reduce(@Nullable TreePath r1, @Nullable TreePath r2) {
// we should only find the target once, so at most one of r1 and r2 should be non-null
Verify.verify(r1 == null || r2 == null);
return r1 != null ? r1 : r2;
}
}.scan(rootPath, null);
}
// private static @Nullable TreePath findPathToSubtree(TreePath rootPath, Tree target) {
// if (rootPath.getLeaf() == target) {
// return rootPath;
// }
// return new TreePathScanner<@Nullable TreePath, @Nullable TreePath>() {
// @Override
// public @Nullable TreePath scan(Tree tree, @Nullable TreePath prevPath) {
// if (tree == target) {
// // When overriding scan(), getCurrentPath() still points at the parent.
// return new TreePath(getCurrentPath(), tree);
// }
// return super.scan(tree, null);
// }
//
// @Override
// public @Nullable TreePath reduce(@Nullable TreePath r1, @Nullable TreePath r2) {
// // we should only find the target once, so at most one of r1 and r2 should be non-null
// Verify.verify(r1 == null || r2 == null);
// return r1 != null ? r1 : r2;
// }
// }.scan(rootPath, null);
// }

/**
* Returns true when javac inferred class type arguments for a constructor call, i.e. there are
Expand Down Expand Up @@ -753,7 +757,8 @@ public void checkTypeParameterNullnessForAssignability(Tree tree, VisitorState s
&& isAssignmentToField(tree)) {
maybeStoreLambdaTypeFromTarget(lambdaExpressionTree, lhsType);
}
Type rhsType = getTreeType(rhsTree, state);
TreePath pathToRhs = new TreePath(state.getPath(), rhsTree);
Type rhsType = getTreeType(rhsTree, state.withPath(pathToRhs));
if (rhsType != null) {
if (isGenericCallNeedingInference(rhsTree)) {
rhsType =
Expand Down