Skip to content
Merged
Show file tree
Hide file tree
Changes from 39 commits
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
29 changes: 29 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,33 @@ 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);
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.

}
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);
}
}
202 changes: 173 additions & 29 deletions nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Types;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeInfo;
import com.sun.tools.javac.util.Name;
import com.sun.tools.javac.util.Names;
import com.uber.nullaway.CodeAnnotationInfo;
Expand Down Expand Up @@ -423,25 +424,39 @@ 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) {
result = ASTHelpers.getType(tree);
}
if (result != null && result.isRaw()) {
return null;
return typeOrNullIfRaw(result);
}
if (tree instanceof NewClassTree newClassTree) {
if (TreeInfo.isDiamond((JCTree) newClassTree)) {
if (newClassTree.getClassBody() != null) {
// Keep existing behavior for diamond anonymous classes, which are not yet fully
// supported. Tracked in https://github.com/uber/NullAway/issues/1475
return null;
}
// For constructor calls using diamond operator, infer from assignment context.
// TODO handle diamond constructor calls passed to generic methods
// https://github.com/uber/NullAway/issues/1470
Type fromAssignmentContext = getDiamondTypeFromContext(newClassTree, state);
if (fromAssignmentContext != null) {
return fromAssignmentContext;
}
}
return result;
}
if (tree instanceof NewClassTree
&& ((NewClassTree) tree).getIdentifier() instanceof ParameterizedTypeTree paramTypedTree) {
if (paramTypedTree.getTypeArguments().isEmpty()) {
// diamond operator, which we do not yet support; for now, return null
// TODO: support diamond operators
return null;
if (newClassTree.getIdentifier() instanceof ParameterizedTypeTree paramTypedTree
&& !paramTypedTree.getTypeArguments().isEmpty()) {
return typeWithPreservedAnnotations(paramTypedTree);
}
return typeWithPreservedAnnotations(paramTypedTree);
return typeOrNullIfRaw(ASTHelpers.getType(tree));
} else if (tree instanceof NewArrayTree
&& ((NewArrayTree) tree).getType() instanceof AnnotatedTypeTree) {
return typeWithPreservedAnnotations(tree);
Expand Down Expand Up @@ -514,12 +529,126 @@ private void reportInvalidOverridingMethodParamTypeError(
}
}
}
if (result != null && result.isRaw()) {
// bail out of any checking involving raw types for now
return typeOrNullIfRaw(result);
}
}

/**
* @param type a type to check
* @return the given type, or null if the type is a raw type
*/
private static @Nullable Type typeOrNullIfRaw(@Nullable Type type) {
if (type != null && type.isRaw()) {
return null;
}
return type;
}

/**
* Gets the type of a constructor call using a diamond operator from its assignment context, if
* available.
*/
private @Nullable Type getDiamondTypeFromContext(NewClassTree tree, VisitorState state) {
return getDiamondTypeFromParentContext(
tree, state, castToNonNull(state.getPath().getParentPath()));
}

/**
* Computes the assignment-context type for an inferred constructor call, given a path to its
* parent context.
*/
private @Nullable Type getDiamondTypeFromParentContext(
NewClassTree tree, VisitorState state, TreePath parentPath) {
Tree parent = parentPath.getLeaf();
while (parent instanceof ParenthesizedTree) {
parentPath = parentPath.getParentPath();
if (parentPath == null) {
return null;
}
return result;
parent = parentPath.getLeaf();
}
if (parent instanceof VariableTree || parent instanceof AssignmentTree) {
return getTreeType(parent, state);
}
if (parent instanceof ReturnTree) {
TreePath enclosingMethodOrLambda =
NullabilityUtil.findEnclosingMethodOrLambdaOrInitializer(parentPath);
if (enclosingMethodOrLambda != null
&& enclosingMethodOrLambda.getLeaf() instanceof MethodTree enclosingMethod) {
Symbol.MethodSymbol methodSymbol = ASTHelpers.getSymbol(enclosingMethod);
if (methodSymbol != null) {
return methodSymbol.getReturnType();
}
}
return null;
Comment thread
msridhar marked this conversation as resolved.
}
if (parent instanceof MethodInvocationTree parentInvocation) {
if (isGenericCallNeedingInference(parentInvocation)) {
// TODO support full integration of diamond constructor calls with generic method inference
// https://github.com/uber/NullAway/issues/1470
// for now, just give up and return null
Comment thread
msridhar marked this conversation as resolved.
return null;
}
Type methodType = ASTHelpers.getType(parentInvocation.getMethodSelect());
if (methodType == null) {
return null;
}
return getFormalParameterTypeForArgument(parentInvocation, methodType.asMethodType(), tree);
}
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);
}
}
Comment on lines +598 to +611
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.

if (parent instanceof ConditionalExpressionTree) {
// TODO infer diamond type from the overall conditional expression type
// tracked in https://github.com/uber/NullAway/issues/1477
return null;
}
return null;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

/**
* Returns the inferred/declared formal parameter type corresponding to actual parameter {@code
* argumentTree}.
*/
private @Nullable Type getFormalParameterTypeForArgument(
Tree invocationTree, Type.MethodType invocationType, Tree argumentTree) {
AtomicReference<@Nullable Type> formalParamTypeRef = new AtomicReference<>();
new InvocationArguments(invocationTree, invocationType)
.forEach(
Comment thread
msridhar marked this conversation as resolved.
(arg, pos, formalParamType, unused) -> {
if (ASTHelpers.stripParentheses(arg) == argumentTree) {
formalParamTypeRef.set(formalParamType);
}
});
return formalParamTypeRef.get();
}
Comment thread
msridhar marked this conversation as resolved.

/**
* Returns true when javac inferred class type arguments for a constructor call, i.e. there are
* instantiated type arguments at the type level, but no explicit non-diamond source type args.
*/
private static boolean hasInferredClassTypeArguments(NewClassTree newClassTree) {
if (newClassTree.getClassBody() != null) {
// we still need to properly handle anonymous classes
return false;
}
if (!TreeInfo.isDiamond((JCTree) newClassTree)) {
// explicit class type arguments in source
return false;
}
Type newClassType = ASTHelpers.getType(newClassTree);
return newClassType != null && !newClassType.getTypeArguments().isEmpty();
}

/**
Expand Down Expand Up @@ -606,7 +735,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 Expand Up @@ -1298,6 +1428,7 @@ private Type updateTypeWithNullness(
private static boolean isGenericCallNeedingInference(ExpressionTree argument) {
// For now, we only support calls to generic methods.
// TODO also support calls to generic constructors that use the diamond operator
// https://github.com/uber/NullAway/issues/1470
if (argument instanceof MethodInvocationTree methodInvocation) {
Symbol.MethodSymbol methodSymbol = ASTHelpers.getSymbol(methodInvocation);
// true for generic method calls with no explicit type arguments
Expand Down Expand Up @@ -1328,7 +1459,8 @@ public void checkTypeParameterNullnessForFunctionReturnType(
// bail out of any checking involving raw types for now
return;
}
Type returnExpressionType = getTreeType(retExpr, state);
TreePath pathToRetExpr = new TreePath(state.getPath(), retExpr);
Type returnExpressionType = getTreeType(retExpr, state.withPath(pathToRetExpr));
if (returnExpressionType != null) {
if (isGenericCallNeedingInference(retExpr)) {
returnExpressionType =
Expand Down Expand Up @@ -1478,7 +1610,21 @@ public void compareGenericTypeParameterNullabilityForCall(
return;
}
Type invokedMethodType = methodSymbol.type;
Type enclosingType = getEnclosingTypeForCallExpression(methodSymbol, tree, null, state, false);
Type enclosingType = null;
if (tree instanceof NewClassTree newClassTree) {
if (hasInferredClassTypeArguments(newClassTree)) {
TreePath currentPath = state.getPath();
if (currentPath != null && ASTHelpers.stripParentheses(currentPath.getLeaf()) == tree) {
TreePath parentPath = currentPath.getParentPath();
if (parentPath != null) {
enclosingType = getDiamondTypeFromParentContext(newClassTree, state, parentPath);
}
}
}
}
if (enclosingType == null) {
enclosingType = getEnclosingTypeForCallExpression(methodSymbol, tree, null, state, false);
}
Comment thread
msridhar marked this conversation as resolved.
if (enclosingType != null) {
invokedMethodType =
TypeSubstitutionUtils.memberType(state.getTypes(), enclosingType, methodSymbol, config);
Expand Down Expand Up @@ -1509,7 +1655,9 @@ public void compareGenericTypeParameterNullabilityForCall(
if (inferredPolyType != null) {
actualParameterType = inferredPolyType;
} else {
actualParameterType = getTreeType(currentActualParam, state);
TreePath pathToActualParam = new TreePath(state.getPath(), currentActualParam);
actualParameterType =
getTreeType(currentActualParam, state.withPath(pathToActualParam));
}
if (actualParameterType != null) {
if (isGenericCallNeedingInference(currentActualParam)) {
Expand Down Expand Up @@ -1874,16 +2022,12 @@ private InvocationAndContext getInvocationAndContextForInference(
}
// the generic invocation is either a regular parameter to the parent call, or the
// receiver expression
AtomicReference<@Nullable Type> formalParamTypeRef = new AtomicReference<>();
Type type = ASTHelpers.getSymbol(parentInvocation).type;
new InvocationArguments(parentInvocation, type.asMethodType())
.forEach(
(arg, pos, formalParamType, unused) -> {
if (ASTHelpers.stripParentheses(arg) == invocation) {
formalParamTypeRef.set(formalParamType);
}
});
Type formalParamType = formalParamTypeRef.get();
Type formalParamType =
getFormalParameterTypeForArgument(
parentInvocation,
castToNonNull(ASTHelpers.getType(parentInvocation.getMethodSelect()))
.asMethodType(),
invocation);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
if (formalParamType == null) {
// this can happen if the invocation is the receiver expression of the call, e.g.,
// id(x).foo() (note that foo() need not be generic)
Expand Down
Loading
Loading