Improve inference for generic method with void-returning lambda argument#1312
Improve inference for generic method with void-returning lambda argument#1312msridhar merged 11 commits intouber:masterfrom
Conversation
WalkthroughAccessPathNullnessPropagation now constructs CoreNullnessStoreInitializer with analysis.getGenericsChecks(). CoreNullnessStoreInitializer stores that GenericsChecks instance; its constructor changed and its former static lambdaInitialStore is now an instance method that consults genericsChecks.getInferredLambdaType(...) to resolve lambda types when computing member/parameter substitutions. GenericsChecks gained an inferredLambdaTypes cache, a public getter, logic to populate it during generic-method inference, and cache-clear behavior. A new test issue1294_lambdaArguments was added to exercise lambda-argument inference. Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing touches🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (2)
105-107: Add documentation for the public getter.The
getInferredLambdaTypemethod is part of the public API but lacks documentation. Consider adding a Javadoc comment explaining:
- When this method returns a non-null value (i.e., for lambda arguments in generic method calls that have undergone inference)
- When it returns null (i.e., for lambdas not part of generic method inference, or before inference has been performed)
- The intended callers and usage context
Example:
+ /** + * Returns the inferred type for a lambda expression that was passed as an argument to a generic + * method call. The type is computed during constraint-based inference for generic method calls + * and includes inferred nullability for type parameters. + * + * @param tree the lambda expression tree + * @return the inferred functional interface type with nullability annotations, or null if the + * lambda was not part of a generic method call requiring inference + */ public @Nullable Type getInferredLambdaType(LambdaExpressionTree tree) {
983-992: Consider adding a comment to explain lambda fallback behavior.The code correctly uses cached inferred types for lambda arguments, but the fallback behavior when a lambda is not in the cache (i.e.,
actualParameterTyperemainsnulland checks are skipped at line 993) is subtle. This is actually correct because:
getTreeTypewould return the functional interface type, which isn't useful for checking type parameter nullability- Only generic method calls populate the cache through inference
- For non-generic methods, there are no type parameters to infer/check anyway
However, this reasoning is not obvious from the code. Consider adding a comment explaining why lambdas not in the cache are skipped rather than falling back to
getTreeType.Type actualParameterType = null; if ((currentActualParam instanceof LambdaExpressionTree)) { + // For lambda arguments, use the inferred type from the constraint solver if available. + // If not in cache (e.g., non-generic method calls), actualParameterType remains null + // and checks are skipped below, which is correct since getTreeType() would only return + // the functional interface type, not the lambda's actual return type. Type lambdaInferredType = inferredLambdaTypes.get(currentActualParam);
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java(6 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: msridhar
PR: uber/NullAway#1248
File: nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java:847-857
Timestamp: 2025-08-28T04:54:20.953Z
Learning: In NullAway's GenericsChecks.java, NewClassTree support for explicit type argument substitution requires more extensive changes beyond just modifying the conditional in compareGenericTypeParameterNullabilityForCall. The maintainers prefer to handle NewClassTree support in a separate follow-up rather than expanding the scope of PRs focused on specific issues like super constructor calls.
📚 Learning: 2025-08-28T04:54:20.953Z
Learnt from: msridhar
PR: uber/NullAway#1248
File: nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java:847-857
Timestamp: 2025-08-28T04:54:20.953Z
Learning: In NullAway's GenericsChecks.java, NewClassTree support for explicit type argument substitution requires more extensive changes beyond just modifying the conditional in compareGenericTypeParameterNullabilityForCall. The maintainers prefer to handle NewClassTree support in a separate follow-up rather than expanding the scope of PRs focused on specific issues like super constructor calls.
Applied to files:
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java
🔇 Additional comments (3)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (3)
627-636: Good approach to cache inferred lambda types.The logic correctly captures the constraint solver's inference results for lambda arguments, storing the formal parameter type with inferred nullability annotations. This enables downstream code to use these types instead of relying on potentially incorrect javac inference.
738-746: LGTM - correctly skips subtype constraints for lambdas.This change addresses the core issue described in the PR. By skipping subtype constraint generation for lambda arguments (which can be incorrect due to javac inference), and instead relying on the constraint solver's inference results (stored in lines 627-636), the implementation avoids the incorrect constraint generation problem.
1686-1686: LGTM - proper cache cleanup.The lambda types cache is correctly cleared along with the existing inference cache to prevent memory leaks between compilation units.
msridhar
left a comment
There was a problem hiding this comment.
This is looking pretty clean! Just a few minor comments
nullaway/src/main/java/com/uber/nullaway/dataflow/CoreNullnessStoreInitializer.java
Outdated
Show resolved
Hide resolved
nullaway/src/main/java/com/uber/nullaway/dataflow/CoreNullnessStoreInitializer.java
Outdated
Show resolved
Hide resolved
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java
Outdated
Show resolved
Hide resolved
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java
Outdated
Show resolved
Hide resolved
nullaway/src/test/java/com/uber/nullaway/jspecify/GenericMethodTests.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
nullaway/src/test/java/com/uber/nullaway/jspecify/GenericMethodTests.java (1)
1249-1274: Good coverage for lambda-argument inference; matches prior ask.ret3/ret4 were added and ret2 asserts the deref error as intended. Looks solid.
🧹 Nitpick comments (1)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (1)
988-997: Fallback to AST type when no inferred lambda type available.Prevents silently skipping param checks for non-generic or failed-inference cases.
Apply:
- Type actualParameterType = null; - if ((currentActualParam instanceof LambdaExpressionTree)) { - Type lambdaInferredType = inferredLambdaTypes.get(currentActualParam); - if (lambdaInferredType != null) { - actualParameterType = lambdaInferredType; - } - } else { - actualParameterType = getTreeType(currentActualParam, state); - } + Type actualParameterType; + if (currentActualParam instanceof LambdaExpressionTree) { + Type lambdaInferredType = inferredLambdaTypes.get(currentActualParam); + actualParameterType = + (lambdaInferredType != null) + ? lambdaInferredType + : getTreeType(currentActualParam, state); + } else { + actualParameterType = getTreeType(currentActualParam, state); + }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
nullaway/src/main/java/com/uber/nullaway/dataflow/CoreNullnessStoreInitializer.java(4 hunks)nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java(6 hunks)nullaway/src/test/java/com/uber/nullaway/jspecify/GenericMethodTests.java(1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: msridhar
PR: uber/NullAway#1248
File: nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java:847-857
Timestamp: 2025-08-28T04:54:20.953Z
Learning: In NullAway's GenericsChecks.java, NewClassTree support for explicit type argument substitution requires more extensive changes beyond just modifying the conditional in compareGenericTypeParameterNullabilityForCall. The maintainers prefer to handle NewClassTree support in a separate follow-up rather than expanding the scope of PRs focused on specific issues like super constructor calls.
📚 Learning: 2025-08-28T04:54:20.953Z
Learnt from: msridhar
PR: uber/NullAway#1248
File: nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java:847-857
Timestamp: 2025-08-28T04:54:20.953Z
Learning: In NullAway's GenericsChecks.java, NewClassTree support for explicit type argument substitution requires more extensive changes beyond just modifying the conditional in compareGenericTypeParameterNullabilityForCall. The maintainers prefer to handle NewClassTree support in a separate follow-up rather than expanding the scope of PRs focused on specific issues like super constructor calls.
Applied to files:
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java
🧬 Code graph analysis (1)
nullaway/src/main/java/com/uber/nullaway/dataflow/CoreNullnessStoreInitializer.java (2)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (1)
GenericsChecks(67-1723)nullaway/src/main/java/com/uber/nullaway/generics/TypeSubstitutionUtils.java (1)
TypeSubstitutionUtils(22-287)
🔇 Additional comments (5)
nullaway/src/main/java/com/uber/nullaway/dataflow/CoreNullnessStoreInitializer.java (1)
114-123: Nice: documents EP traversal ordering for lambda inference.This resolves the subtle “have we inferred yet?” concern without extra plumbing.
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (4)
102-111: Cache for inferred lambda types looks good.Docstring is clear and API is minimal.
630-640: Recording inferred types for lambda args at solve-time is the right place.Keeps inference concerns localized; no extra passes needed.
741-744: Correct to skip subtype constraints for lambda arguments.Prevents javac’s immediate target typing from polluting our constraints; matches the PR goal.
1690-1691: Clearing lambda-inference cache with the other maps avoids leaks.Good cleanup.
nullaway/src/main/java/com/uber/nullaway/dataflow/CoreNullnessStoreInitializer.java
Show resolved
Hide resolved
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #1312 +/- ##
============================================
+ Coverage 88.39% 88.42% +0.03%
- Complexity 2516 2523 +7
============================================
Files 94 94
Lines 8393 8415 +22
Branches 1657 1661 +4
============================================
+ Hits 7419 7441 +22
Misses 491 491
Partials 483 483 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
… return statements (#1337) This is a follow up to #1312 While generating constraints for params in `generateConstraintsForParam`, if argument is a lambda expression, then generate constraints for the return expression inside lambda and the functional interface method return type. If the return expression is a method invocation then recursively call `generateConstraintsForCall` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved null-safety checks for lambdas used with generic methods in JSpecify mode by using more accurate inferred lambda return types, reducing false diagnostics about returning nullable values. * **Tests** * Expanded and re-enabled tests to validate lambda return-type inference with generic method calls and updated expectations to reflect corrected behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Manu Sridharan <msridhar@gmail.com>
javacinference for lambda argument leads to incorrect subtype constraint generation.solution: Skipping subtype constraint generation for lambda argument and using the results from constraint solver inference by saving it in a new map.
This currently only handles void-returning lambdas. We will extend inference to handle return statements within lambdas in a follow-up.
Fixes #1294
Summary by CodeRabbit