Conversation
WalkthroughDerives the enclosing class/type for generic nullability checks at call sites via a new getEnclosingTypeForCallExpression(...); centralizes type-arg substitution using that enclosing type for method returns and parameters; refactors substituteTypeArgsInGenericMethodType signature; updates tests and adds a Caffeine-derived generic test. Changes
Sequence Diagram(s)sequenceDiagram
participant Caller as Call site (AST)
participant GC as GenericsChecks
participant TS as TypeSystem
Caller->>GC: getGenericParameterNullnessAtInvocation(tree, methodSym, state)
GC->>GC: normalize tree (stripParentheses)
GC->>GC: getEnclosingTypeForCallExpression(methodSym, tree, state)
alt NewClassTree (constructor)
GC-->>TS: enclosingType = invocation.type
else MethodInvocationTree
alt static method
GC-->>GC: enclosingType = null
else selector is IdentifierTree (unqualified)
GC->>Caller: find enclosing ClassTree
Caller-->>TS: enclosingType = ClassTree.type
else selector is MemberSelectTree (qualified)
GC->>TS: enclosingType = receiver.expression.type
end
end
GC->>TS: substituteTypeArgsInGenericMethodType(tree, methodType, tvars, state) using enclosingType
TS-->>GC: resolved parameter/return nullness
GC-->>Caller: nullness result
Estimated code review effort🎯 4 (Complex) | ⏱️ ~40 minutes Assessment against linked issues
Assessment — Out-of-scope changes
Possibly related PRs
Suggested reviewers
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. 📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (1)
1136-1155: Correctly derive enclosing type for unqualified and super calls; two small refinementsThis fixes the core issue by using the enclosing class for IdentifierTree (this/super) calls and the receiver for MemberSelectTree. Early return for static methods is also correct.
Two small improvements:
- Prefer ASTHelpers.getSymbol(enclosingClassTree).type over casting to JCTree to reduce reliance on javac internals.
- Strip parentheses from the MemberSelect receiver as well (symmetry with the methodSelect strip), even if it’s typically harmless.
Apply this diff within this block:
- ClassTree enclosingClassTree = - ASTHelpers.findEnclosingNode(state.getPath(), ClassTree.class); - if (enclosingClassTree != null) { - enclosingType = ((JCTree.JCClassDecl) enclosingClassTree).type; - } + ClassTree enclosingClassTree = + ASTHelpers.findEnclosingNode(state.getPath(), ClassTree.class); + if (enclosingClassTree != null) { + enclosingType = ASTHelpers.getSymbol(enclosingClassTree).type; + } @@ - } else if (methodSelect instanceof MemberSelectTree) { - enclosingType = getTreeType(((MemberSelectTree) methodSelect).getExpression(), config); + } else if (methodSelect instanceof MemberSelectTree) { + ExpressionTree receiver = + ASTHelpers.stripParentheses(((MemberSelectTree) methodSelect).getExpression()); + enclosingType = getTreeType(receiver, config); }Optional follow-ups (no changes requested here):
- Consider extracting this “compute enclosing type for an invocation” logic into a helper and reusing it in:
- compareGenericTypeParameterNullabilityForCall (Lines 753–768 currently use methodSymbol.owner.type for IdentifierTree, which may miss subclass-instantiated type args),
- getGenericReturnNullnessAtInvocation (Lines 1016–1026 currently skip unqualified calls).
This would keep behavior consistent across parameter and return nullness checks.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java(2 hunks)nullaway/src/test/java/com/uber/nullaway/jspecify/GenericsTests.java(0 hunks)
💤 Files with no reviewable changes (1)
- nullaway/src/test/java/com/uber/nullaway/jspecify/GenericsTests.java
🔇 Additional comments (1)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (1)
12-12: Import for ClassTree is appropriateRequired to locate the enclosing class for unqualified/super calls. Looks good.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1248 +/- ##
============================================
- Coverage 88.17% 88.16% -0.02%
+ Complexity 2427 2422 -5
============================================
Files 92 92
Lines 8010 8010
Branches 1595 1594 -1
============================================
- Hits 7063 7062 -1
Misses 492 492
- Partials 455 456 +1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (1)
1154-1154: Minor consistency: prefer static verify(...) over Verify.verify(...)The file mixes both; use the static import consistently.
Apply this diff:
- Verify.verify(tree instanceof NewClassTree); + verify(tree instanceof NewClassTree);Additionally, since the non-static import becomes unused, remove the regular import at Line 6:
-import com.google.common.base.Verify;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java(5 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (1)
nullaway/src/main/java/com/uber/nullaway/generics/TypeSubstitutionUtils.java (1)
TypeSubstitutionUtils(22-251)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: JDK 17 on windows-latest with Error Prone 2.41.0
- GitHub Check: JDK 17 on ubuntu-latest with Error Prone 2.41.0
- GitHub Check: JDK 17 on macos-latest with Error Prone 2.41.0
- GitHub Check: JDK 11 on ubuntu-latest with Error Prone 2.31.0
- GitHub Check: JDK 17 on ubuntu-latest with Error Prone 2.14.0
- GitHub Check: Build Caffeine with snapshot
🔇 Additional comments (5)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (5)
12-12: Import addition is appropriateClassTree is needed for locating the enclosing class; good addition.
376-377: Normalize input by stripping parenthesesStripping parentheses here is a solid fix to avoid losing annotations when callers forget to pre-strip. This should reduce edge-case misses for annotated types under parentheses.
996-1002: Correctly derive enclosing type for return-nullness at call sitesLeveraging getEnclosingTypeForCallExpression(...) here fixes the super and unqualified-call cases in JSpecify mode. Static-call early-exit to NONNULL is also correct (class generics don’t apply).
1112-1120: Consistent parameter-nullness derivation via enclosing typeSame improvement applied to parameters; this should address nullable super constructor/super method arguments in JSpecify mode.
1122-1160: Helper captures receiver/enclosing context for both implicit and explicit receiversCentralizing the logic is great. A couple of edge considerations to keep in mind:
- For methodSelect being a super-qualified select (e.g., A.super.m()), methodSelect is a MemberSelectTree; getTreeType(receiver, config) should yield the appropriate supertype with type args. This looks correct, but it’s worth validating with a qualified-super test if you don’t already have one.
- For constructor diamond calls, getTreeType returns null (as documented). That means we’ll skip class-level substitution for those. That behavior matches the current limitation; just calling it out.
Would you like me to add a test for A.super.m(...) with @nullable type args to lock this down?
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
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 (1)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (1)
1169-1189: Do parameter nullness composition in one pass (class-level then method-level).Same rationale as for return nullness: start from memberType when available, then apply explicit/inferred method type args onto that before checking the parameter.
Apply:
- // If generic method invocation - if (!invokedMethodSymbol.getTypeParameters().isEmpty()) { - // Substitute the argument types within the MethodType - Type.ForAll forAllType = (Type.ForAll) invokedMethodSymbol.type; - List<Type> substitutedParamTypes = - substituteTypeArgsInGenericMethodType( - tree, forAllType.qtype.asMethodType(), forAllType.tvars, state) - .getParameterTypes(); - // If this condition evaluates to false, we fall through to the subsequent logic, to handle - // type variables declared on the enclosing class - if (substitutedParamTypes != null - && Objects.equals( - getParameterTypeNullness(substitutedParamTypes.get(paramIndex), isVarargsParam), - Nullness.NULLABLE)) { - return Nullness.NULLABLE; - } - } - - Type enclosingType = getEnclosingTypeForCallExpression(invokedMethodSymbol, tree, state); - if (enclosingType == null) { - return Nullness.NONNULL; - } - - return getGenericMethodParameterNullness(paramIndex, invokedMethodSymbol, enclosingType, state); + Type enclosingType = getEnclosingTypeForCallExpression(invokedMethodSymbol, tree, state); + Type invokedMethodType = invokedMethodSymbol.type; + if (enclosingType != null) { + invokedMethodType = + TypeSubstitutionUtils.memberType( + state.getTypes(), enclosingType, invokedMethodSymbol, config); + } + if (invokedMethodType instanceof Type.ForAll) { + Type.MethodType mt = invokedMethodType.asMethodType(); + com.sun.tools.javac.util.List<Type> tvars = ((Type.ForAll) invokedMethodType).tvars; + java.util.List<Type> substitutedParamTypes = + substituteTypeArgsInGenericMethodType(tree, mt, tvars, state).getParameterTypes(); + if (substitutedParamTypes != null + && getParameterTypeNullness(substitutedParamTypes.get(paramIndex), isVarargsParam) + == Nullness.NULLABLE) { + return Nullness.NULLABLE; + } + } + return (enclosingType == null) + ? Nullness.NONNULL + : getGenericMethodParameterNullness(paramIndex, invokedMethodSymbol, enclosingType, state);
♻️ Duplicate comments (1)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (1)
847-861: Also handle explicit type args on generic constructors (NewClassTree).The substitution now composes method-level args on top of class-level memberType — great. But the guard limits this to MethodInvocationTree, missing explicit type args on generic constructors (e.g., new C(...)). Extend the condition to include NewClassTree.
Apply:
- if (tree instanceof MethodInvocationTree && invokedMethodType instanceof Type.ForAll) { + if ((tree instanceof MethodInvocationTree || tree instanceof NewClassTree) + && invokedMethodType instanceof Type.ForAll) { invokedMethodType = substituteTypeArgsInGenericMethodType( tree, invokedMethodType.asMethodType(), ((Type.ForAll) invokedMethodType).tvars, state); }
🧹 Nitpick comments (1)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (1)
1069-1071: Use enum comparison directly.Nullness is an enum; prefer == over Objects.equals for clarity and micro-efficiency.
Apply:
- && Objects.equals(getTypeNullness(substitutedReturnType), Nullness.NULLABLE)) { + && getTypeNullness(substitutedReturnType) == Nullness.NULLABLE) {- && Objects.equals( - getParameterTypeNullness(substitutedParamTypes.get(paramIndex), isVarargsParam), - Nullness.NULLABLE)) { + && getParameterTypeNullness(substitutedParamTypes.get(paramIndex), isVarargsParam) + == Nullness.NULLABLE) {Also applies to: 1175-1178
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java(8 hunks)nullaway/src/test/java/com/uber/nullaway/jspecify/GenericMethodTests.java(1 hunks)nullaway/src/test/java/com/uber/nullaway/jspecify/GenericsTests.java(0 hunks)
💤 Files with no reviewable changes (1)
- nullaway/src/test/java/com/uber/nullaway/jspecify/GenericsTests.java
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-14T18:50:06.159Z
Learnt from: msridhar
PR: uber/NullAway#1245
File: guava-recent-unit-tests/src/test/java/com/uber/nullaway/guava/NullAwayGuavaParametricNullnessTests.java:101-102
Timestamp: 2025-08-14T18:50:06.159Z
Learning: In NullAway JSpecify tests, when JDK version requirements exist due to bytecode annotation reading capabilities, prefer failing tests over skipping them on unsupported versions to ensure CI catches regressions and enforces proper JDK version usage for developers.
Applied to files:
nullaway/src/test/java/com/uber/nullaway/jspecify/GenericMethodTests.java
🧬 Code graph analysis (1)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (1)
nullaway/src/main/java/com/uber/nullaway/generics/TypeSubstitutionUtils.java (1)
TypeSubstitutionUtils(22-256)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
- GitHub Check: JDK 11 on ubuntu-latest with Error Prone 2.14.0
- GitHub Check: JDK 17 on windows-latest with Error Prone 2.41.0
- GitHub Check: JDK 17 on ubuntu-latest with Error Prone 2.41.0
- GitHub Check: Build spring-framework with snapshot
- GitHub Check: JDK 11 on ubuntu-latest with Error Prone 2.31.0
- GitHub Check: JDK 17 on ubuntu-latest with Error Prone 2.14.0
- GitHub Check: JDK 17 on macos-latest with Error Prone 2.41.0
- GitHub Check: Build caffeine with snapshot
🔇 Additional comments (3)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (2)
370-371: Good call: normalize trees before type lookup.Stripping parentheses up front reduces edge cases in downstream logic that relies on exact Tree kinds.
1201-1228: Enclosing-type derivation looks right.Covers implicit this/super (via enclosing ClassTree) and explicit receivers, and skips statics; aligns with the PR objective for super calls.
nullaway/src/test/java/com/uber/nullaway/jspecify/GenericMethodTests.java (1)
1086-1121: Great regression: exercises wildcard + nullable V in a Caffeine-like shape.This captures the subtle substitution/identity case well and documents the current inference limitation with a TODO+SuppressWarnings.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (2)
1058-1075: Compose method-level type args on top of class-level substitution for return nullnessReturn nullness is computed against the raw method type, discarding class-level substitutions when both class and method type variables are involved. Build from memberType(...) first, then apply explicit/inferred method type args.
- // If generic method invocation - if (!invokedMethodSymbol.getTypeParameters().isEmpty()) { - // Substitute type arguments inside the return type - Type.ForAll forAllType = (Type.ForAll) invokedMethodSymbol.type; - Type substitutedReturnType = - substituteTypeArgsInGenericMethodType(tree, forAllType, state).getReturnType(); - // If this condition evaluates to false, we fall through to the subsequent logic, to handle - // type variables declared on the enclosing class - if (substitutedReturnType != null - && Objects.equals(getTypeNullness(substitutedReturnType), Nullness.NULLABLE)) { - return Nullness.NULLABLE; - } - } - - Type enclosingType = getEnclosingTypeForCallExpression(invokedMethodSymbol, tree, state); - if (enclosingType == null) { - return Nullness.NONNULL; - } else { - return getGenericMethodReturnTypeNullness(invokedMethodSymbol, enclosingType, state); - } + Type enclosingType = getEnclosingTypeForCallExpression(invokedMethodSymbol, tree, state); + Type invokedMethodType = invokedMethodSymbol.type; + if (enclosingType != null) { + invokedMethodType = + TypeSubstitutionUtils.memberType( + state.getTypes(), enclosingType, invokedMethodSymbol, config); + } + if (invokedMethodType instanceof Type.ForAll) { + Type substitutedReturnType = + substituteTypeArgsInGenericMethodType(tree, (Type.ForAll) invokedMethodType, state) + .getReturnType(); + if (substitutedReturnType != null + && getTypeNullness(substitutedReturnType) == Nullness.NULLABLE) { + return Nullness.NULLABLE; + } + } + return (enclosingType == null) + ? Nullness.NONNULL + : getGenericMethodReturnTypeNullness(invokedMethodSymbol, enclosingType, state);
1161-1179: Compose method-level type args on top of class-level substitution for parameter nullnessLike return nullness, parameter nullness should start from memberType(...). Otherwise, explicit/inferred method type args can override class-level substitutions.
- // If generic method invocation - if (!invokedMethodSymbol.getTypeParameters().isEmpty()) { - // Substitute the argument types within the MethodType - Type.ForAll forAllType = (Type.ForAll) invokedMethodSymbol.type; - List<Type> substitutedParamTypes = - substituteTypeArgsInGenericMethodType(tree, forAllType, state).getParameterTypes(); - // If this condition evaluates to false, we fall through to the subsequent logic, to handle - // type variables declared on the enclosing class - if (substitutedParamTypes != null - && Objects.equals( - getParameterTypeNullness(substitutedParamTypes.get(paramIndex), isVarargsParam), - Nullness.NULLABLE)) { - return Nullness.NULLABLE; - } - } - - Type enclosingType = getEnclosingTypeForCallExpression(invokedMethodSymbol, tree, state); + Type enclosingType = getEnclosingTypeForCallExpression(invokedMethodSymbol, tree, state); + Type invokedMethodType = invokedMethodSymbol.type; + if (enclosingType != null) { + invokedMethodType = + TypeSubstitutionUtils.memberType( + state.getTypes(), enclosingType, invokedMethodSymbol, config); + } + if (invokedMethodType instanceof Type.ForAll) { + List<Type> substitutedParamTypes = + substituteTypeArgsInGenericMethodType(tree, (Type.ForAll) invokedMethodType, state) + .getParameterTypes(); + if (substitutedParamTypes != null + && getParameterTypeNullness(substitutedParamTypes.get(paramIndex), isVarargsParam) + == Nullness.NULLABLE) { + return Nullness.NULLABLE; + } + } if (enclosingType == null) { return Nullness.NONNULL; } return getGenericMethodParameterNullness(paramIndex, invokedMethodSymbol, enclosingType, state);
🧹 Nitpick comments (3)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (3)
1092-1105: Javadoc clarity: tree can be either method- or constructor-invocationThe method handles both MethodInvocationTree and NewClassTree; update the doc to match.
- * @param tree the method invocation tree - * @param forAllType the generic method type + * @param tree the invocation tree (MethodInvocationTree or NewClassTree) + * @param forAllType the generic method type (after any class-level substitution if available)
1191-1218: Super/this receiver edge cases: fall back to enclosing class when receiver type is unavailableFor MemberSelectTree where the receiver is super/this, getTreeType(expr) can be null or surprising in some javac versions. Add a defensive fallback to the enclosing class type to keep behavior consistent with the implicit-this path.
} else if (methodSelect instanceof MemberSelectTree) { - enclosingType = getTreeType(((MemberSelectTree) methodSelect).getExpression()); + enclosingType = getTreeType(((MemberSelectTree) methodSelect).getExpression()); + if (enclosingType == null) { + ClassTree enclosingClassTree = + ASTHelpers.findEnclosingNode(state.getPath(), ClassTree.class); + if (enclosingClassTree != null) { + enclosingType = castToNonNull(ASTHelpers.getType(enclosingClassTree)); + } + } }
1212-1212: Use a single Verify styleYou import both the class and the static method. Prefer one style (static import) for consistency.
- Verify.verify(tree instanceof NewClassTree); + verify(tree instanceof NewClassTree);Then remove the non-static import:
- Remove
import com.google.common.base.Verify;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java(8 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (1)
nullaway/src/main/java/com/uber/nullaway/generics/TypeSubstitutionUtils.java (1)
TypeSubstitutionUtils(22-256)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
- GitHub Check: JDK 17 on ubuntu-latest with Error Prone 2.14.0
- GitHub Check: JDK 11 on ubuntu-latest with Error Prone 2.31.0
- GitHub Check: JDK 11 on ubuntu-latest with Error Prone 2.14.0
- GitHub Check: JDK 17 on windows-latest with Error Prone 2.41.0
- GitHub Check: JDK 17 on macos-latest with Error Prone 2.41.0
- GitHub Check: JDK 17 on ubuntu-latest with Error Prone 2.41.0
- GitHub Check: Build caffeine with snapshot
- GitHub Check: Build spring-framework with snapshot
🔇 Additional comments (1)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (1)
370-371: Parenthesis stripping in getTreeType — LGTMThis avoids missing annotations/types hidden by extra parentheses.
| * @return Type of the tree with preserved annotations. | ||
| */ | ||
| private @Nullable Type getTreeType(Tree tree) { | ||
| tree = ASTHelpers.stripParentheses(tree); |
There was a problem hiding this comment.
Drive-by fix to handle parens
|
@lazaroclapp I added your suggested tests in aa796f0 and they seem to work as I expect. The first passes (since it's passing a |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
nullaway/src/test/java/com/uber/nullaway/jspecify/GenericsTests.java (2)
2366-2385: Add a negative control to harden coverageConsider adding a companion test where B extends A and calls super(null) (or super(o) with o annotated @nullable) to assert we still flag null passed to a non-null T.
2387-2408: Consider a symmetric method-call variantAdd an analogous superclass method test (A(List) m(...); B extends A<@nullable Object> calls super.m(l) with List) to mirror the constructor case and catch regressions in both paths.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
nullaway/src/test/java/com/uber/nullaway/jspecify/GenericsTests.java(1 hunks)🧰 Additional context used
🧠 Learnings (3)
📓 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.922Z 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.922Z
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.922Z 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/test/java/com/uber/nullaway/jspecify/GenericsTests.java📚 Learning: 2025-08-14T18:50:06.159Z
Learnt from: msridhar PR: uber/NullAway#1245 File: guava-recent-unit-tests/src/test/java/com/uber/nullaway/guava/NullAwayGuavaParametricNullnessTests.java:101-102 Timestamp: 2025-08-14T18:50:06.159Z Learning: In NullAway JSpecify tests, when JDK version requirements exist due to bytecode annotation reading capabilities, prefer failing tests over skipping them on unsupported versions to ensure CI catches regressions and enforces proper JDK version usage for developers.Applied to files:
nullaway/src/test/java/com/uber/nullaway/jspecify/GenericsTests.java🔇 Additional comments (2)
nullaway/src/test/java/com/uber/nullaway/jspecify/GenericsTests.java (2)
2366-2385: LGTM: super-ctor accepts NonNull for @nullable type argThis positive test accurately exercises the new enclosing-type logic for super constructor calls and should pass under JSpecify mode.
2387-2408: LGTM: mismatched nested type arg is correctly rejectedThe List vs List<@nullable Object> mismatch is the right regression guard for variance/inference subtleties on super-ctor calls.
lazaroclapp
left a comment
There was a problem hiding this comment.
LGTM.
Presumably the Spring example needs a type like extends DefaultBodySpec<List<? extends @Nullable E super E>, correct? Which seems a bit convoluted, but it is in line with Java's treatment of collections vs arrays in terms of covariance, right? (Still swapping in some context here).
|
FWIW this change makes Spring pass NullAway with this PR included: msridhar/spring-framework@8fb857d But it's quite possible it only passes due to other false negatives in NullAway (e.g., we don't yet do proper inference for the diamond operator). |
Fixes #1246
We were computing the enclosing type for such calls incorrectly before; now we use the enclosing class of the call itself. We extract the logic for computing enclosing types to its own function and use it consistently everywhere.
This change exposed a subtle bug with type substitutions; relevant test case is
nullableWildcardFromCaffeine. We fix that bug here as well, though there is still another inference bug to be fixed. Opened #1267 and will fix in a follow-up.Summary by CodeRabbit