Skip to content

Improve handling of var-declared local variables#1573

Merged
msridhar merged 27 commits into
masterfrom
handle-var-better
May 25, 2026
Merged

Improve handling of var-declared local variables#1573
msridhar merged 27 commits into
masterfrom
handle-var-better

Conversation

@msridhar
Copy link
Copy Markdown
Collaborator

@msridhar msridhar commented May 9, 2026

Fix #1572

Infer the nullability of type arguments for locals declared with var, based on the initializer at the variable's declaration. This change is a bit complex for two reasons. First, there is no efficient way with javac to get the declaration of a local from its Symbol. In the course of running the normal NullAway visitor, we should always visit the declaration of a variable before its uses, so as long as we cache the inferred type from the declaration, it should be available.

But, the second issue is that dataflow analysis may require the type of a var-declared local before the main NullAway visitor reaches the declaration. To handle this case, we cache declarations of var-declared locals as they are reached by the dataflow analysis, and use that cache of declarations to infer types on demand at uses. This is a bit subtle / fragile, but we want to minimize overhead, and the alternative would be to run a full visitor to find these declarations, which could be expensive.

We also need to thread the calledFromDataflow flag through more places, since we should not cache the inferred type of such a local when called from dataflow (since the inferred type may rely on incomplete dataflow results; see test varGenericInferenceFromDataflowInLoop()). Many of the changes in this PR are just passing calledFromDataflow through more methods.

Integration test failures are expected.

Summary by CodeRabbit

  • New Features

    • Context-sensitive nullability/type inference for locals declared with var, yielding more accurate diagnostics for generics and inferred types.
  • Bug Fixes

    • Improved analysis correctness during in-progress/dataflow-driven checks and refined cache handling to avoid incorrect or missed inferences.
  • Tests

    • Expanded test coverage for var-declared locals across loops, try-with-resources, anonymous classes, duplicate names, reassignments, and dataflow mutation scenarios.

Review Change Stack

@codecov
Copy link
Copy Markdown

codecov Bot commented May 9, 2026

Codecov Report

❌ Patch coverage is 85.22727% with 13 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.37%. Comparing base (943ee66) to head (3fa8293).

Files with missing lines Patch % Lines
...ava/com/uber/nullaway/generics/GenericsChecks.java 90.00% 1 Missing and 7 partials ⚠️
.../nullaway/dataflow/RunOnceForwardAnalysisImpl.java 16.66% 2 Missing and 3 partials ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master    #1573      +/-   ##
============================================
- Coverage     88.42%   88.37%   -0.06%     
- Complexity     2885     2910      +25     
============================================
  Files           103      103              
  Lines          9650     9709      +59     
  Branches       1942     1959      +17     
============================================
+ Hits           8533     8580      +47     
- Misses          535      538       +3     
- Partials        582      591       +9     

☔ 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.

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.

There seem to be changes here other than adding the cache, also, the description doesn't fully motivate the cache. Is this a performance-only optimization or do we expect the semantics to be different with the cache (given all the tests, I assume the second, but don't see it spelled out in the description 😅 )

pathToRhs,
lhsType,
// if a local is declared using `var`, don't use its javac-inferred type as part of
// inference
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this the case?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's because javac doesn't properly infer nullability of type arguments. So, if you have:

<T extends @Nullable Object> Foo<T> make(T t) { ... }
void baz(@Nullable String s) {
  var t = make(s); ...
}

javac may well compute the type of t as Foo, but with nullability, it should be Foo<@Nullable String>. So, for var-declared variables assigned the result of a generic method call, we run our own inference on the call ignoring the javac-inferred type of the variable, and then remember our own inference result as the variable's type. I will update the code comment.

ElementKind kind = symbol.getKind();
return (kind.equals(ElementKind.LOCAL_VARIABLE) || kind.equals(ElementKind.RESOURCE_VARIABLE))
&& symbol.owner != null
? new VarLocalKey(symbol.owner, symbol.name)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to double-check here: Is there any shadowing of note here that might cause us to see two local variables with the same owner (presumably a method or lambda?) and same name?

While Java seems fairly strict about re-declarations of locals, I could, for example, get this to compile:

class Main {
    public static void main(String[] args) {
        int i = 0;
        for(var foo = "Hello"; i == 0; ++i) {
            System.out.println(foo);
        }
        i = 0;
        for(var foo = 5; i == 0; ++i) {
            System.out.println(5);
        }
    }
}

I suspect we could translate that into a test case about nullability where we can cause a key collision here, no?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, this is an issue. Writing a test for it exposed a subtle bug. Working on it.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Went down a bit of a rabbit hole on this. It's fixed, along with other issues. This PR probably needs a from-scratch review.

Base automatically changed from nested-receiver-inference to master May 16, 2026 23:21
@msridhar msridhar force-pushed the handle-var-better branch from 0510d8e to 9402f1a Compare May 16, 2026 23:27
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 16, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds context-aware type inference and caching for local variables declared with var. Introduces caches and a registration API, threads a new calledFromDataflow context through getTreeType, diamond inference, and generic-method inference/repair, populates inferred types during assignments to var locals (registered from dataflow), adjusts forward-analysis value access behavior, and clears caches appropriately. New JSpecify-mode tests exercise inferred-nullability diagnostics across several var scenarios.

Possibly related PRs

  • uber/NullAway#1378: Both PRs modify GenericsChecks.getTreeType’s identifier/type-inference path in GenericsChecks.java (main PR adjusts identifier typing via var-local inferred types/context, while the retrieved PR changes how identifier types are derived for TypeVar cases), so the changes are related.
  • uber/NullAway#1371: Both PRs modify GenericsChecks’ type-computation logic in getTreeType—one adds var-local inferred-type handling (and a new context flag/overload), while the other restores explicit nullability annotations for method invocations and field reads.
  • uber/NullAway#1570: Both PRs modify GenericsChecks’ generic/type-inference plumbing by changing how contextual information is passed into getTreeType (the retrieved PR fixes/anchors TreePath leaves, while the main PR adds a context-sensitive getTreeType(..., calledFromDataflow) and consults inferred var-local types during inference/assignment checking).

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 54.05% 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 title accurately describes the main change: improving handling of var-declared local variables by adding support for context-sensitive nullability inference.
Linked Issues check ✅ Passed The PR implements all core requirements from #1572: inferring nullability of type arguments for var-declared locals, caching inferred types, using dataflow-aware caching, and ensuring availability in JSpecify mode.
Out of Scope Changes check ✅ Passed All changes directly support the objective of handling var-declared local variables and their nullability inference within the NullAway generic type system.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch handle-var-better

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.

@msridhar msridhar marked this pull request as draft May 17, 2026 02:01
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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java`:
- Around line 121-122: The current varLocalDeclarations map only stores the
JCVariableDecl, so getInferredVarLocalType rebuilds pathToInitializer from the
use-site path and loses the declaration's parent context; fix by preserving the
declaration TreePath at registration time (or eagerly computing and caching the
inferred type when registering the var). Update the registration code that
writes to varLocalDeclarations to also store the declaration TreePath (e.g., add
a secondary map or change the stored value to include the TreePath) or compute
and cache the inferred type for that JCVariableDecl immediately, then change
getInferredVarLocalType to read the preserved TreePath / cached type instead of
recomputing from the use-site path; reference varLocalDeclarations,
getInferredVarLocalType, and pathToInitializer when making these changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0e32bd5b-4f61-4736-b9f1-33e69b6269d4

📥 Commits

Reviewing files that changed from the base of the PR and between 9402f1a and 2a5e8a9.

📒 Files selected for processing (3)
  • nullaway/src/main/java/com/uber/nullaway/dataflow/AccessPathNullnessPropagation.java
  • nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java
  • nullaway/src/test/java/com/uber/nullaway/jspecify/VarDeclaredLocalTests.java

Comment thread nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java Outdated
@msridhar msridhar marked this pull request as ready for review May 19, 2026 17:16
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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java (2)

1032-1055: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Propagate calledFromDataflow through constraint generation.

runInferenceForCall(..., true) still drops the flag before entering generateConstraintsForCall(). From there, pseudo-assignment typing falls back to the normal getTreeType(...) flow, so a dataflow-originated inference can still populate caches from non-fixed-point facts while analyzing arguments or nested receivers. Thread the flag through the constraint-generation helpers and use the 3-arg getTreeType(...) consistently.

Suggested fix
 private void generateConstraintsForCall(
     VisitorState state,
     `@Nullable` TreePath path,
     `@Nullable` Type typeFromAssignmentContext,
     boolean assignedToLocal,
+    boolean calledFromDataflow,
     ConstraintSolver solver,
     Symbol.MethodSymbol methodSymbol,
     MethodInvocationTree methodInvocationTree,
     Set<MethodInvocationTree> allInvocations)
     throws UnsatisfiableConstraintsException {
@@
           generateConstraintsForPseudoAssignment(
               state.withPath(pathToArgument),
               solver,
               allInvocations,
               argument,
-              formalParamType);
+              formalParamType,
+              calledFromDataflow);
 }

 private void generateConstraintsForPseudoAssignment(
     VisitorState state,
     ConstraintSolver solver,
     Set<MethodInvocationTree> allInvocations,
     ExpressionTree rhsExpr,
-    Type lhsType) {
+    Type lhsType,
+    boolean calledFromDataflow) {
@@
-      Type argumentType = getTreeType(rhsExpr, state);
+      Type argumentType = getTreeType(rhsExpr, state, calledFromDataflow);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java` around
lines 1032 - 1055, runInferenceForCall currently drops the calledFromDataflow
flag before calling generateConstraintsForCall; propagate this boolean into
generateConstraintsForCall and all downstream constraint-generation helpers so
they know the call originated from dataflow, and update those helpers to pass it
along. Also ensure you call the 3-arg getTreeType(...) (including the
calledFromDataflow flag) everywhere inside the constraint generation path (e.g.,
in methods that analyze arguments, receivers, and nested invocations) so
pseudo-assignment typing uses dataflow-aware lookup consistently; update
signatures and call sites for generateConstraintsForCall and any helper methods
to accept and forward calledFromDataflow.

1065-1081: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Skip poly-expression cache writes during dataflow inference.

inferredPolyExpressionTypes is populated even when calledFromDataflow is true. Those entries are derived from the same inference result this method deliberately avoids caching elsewhere, so later non-dataflow visits can read stale lambda/member-reference target types from the cache.

Suggested fix
-      // Store inferred types for lambda arguments
-      new InvocationArguments(invocationTree, methodSymbol.type.asMethodType())
-          .forEach(
-              (argument, argPos, formalParamType, unused) -> {
-                if (argument instanceof LambdaExpressionTree
-                    || argument instanceof MemberReferenceTree) {
-                  Type polyExprTreeType = ASTHelpers.getType(argument);
-                  if (polyExprTreeType != null) {
-                    Type typeWithInferredNullability =
-                        TypeSubstitutionUtils.updateTypeWithInferredNullability(
-                            polyExprTreeType, formalParamType, typeVarNullability, state, config);
-                    inferredPolyExpressionTypes.put(argument, typeWithInferredNullability);
-                  }
-                }
-              });
+      if (!calledFromDataflow) {
+        new InvocationArguments(invocationTree, methodSymbol.type.asMethodType())
+            .forEach(
+                (argument, argPos, formalParamType, unused) -> {
+                  if (argument instanceof LambdaExpressionTree
+                      || argument instanceof MemberReferenceTree) {
+                    Type polyExprTreeType = ASTHelpers.getType(argument);
+                    if (polyExprTreeType != null) {
+                      Type typeWithInferredNullability =
+                          TypeSubstitutionUtils.updateTypeWithInferredNullability(
+                              polyExprTreeType, formalParamType, typeVarNullability, state, config);
+                      inferredPolyExpressionTypes.put(argument, typeWithInferredNullability);
+                    }
+                  }
+                });
+      }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java` around
lines 1065 - 1081, The code is writing inferred lambda/member-reference types
into inferredPolyExpressionTypes even when calledFromDataflow is true; to avoid
caching dataflow-only inference results, skip adding entries when
calledFromDataflow is true. Modify the lambda passed to
InvocationArguments.forEach so that before calling
inferredPolyExpressionTypes.put(argument, typeWithInferredNullability) you check
if (!calledFromDataflow) (or return early when calledFromDataflow) and only then
perform the put; keep the existing computation of typeWithInferredNullability if
you still need it for local logic, but do not mutate inferredPolyExpressionTypes
when calledFromDataflow is true (references: inferredPolyExpressionTypes,
calledFromDataflow, InvocationArguments.forEach,
TypeSubstitutionUtils.updateTypeWithInferredNullability, InferenceSuccess).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java`:
- Around line 1032-1055: runInferenceForCall currently drops the
calledFromDataflow flag before calling generateConstraintsForCall; propagate
this boolean into generateConstraintsForCall and all downstream
constraint-generation helpers so they know the call originated from dataflow,
and update those helpers to pass it along. Also ensure you call the 3-arg
getTreeType(...) (including the calledFromDataflow flag) everywhere inside the
constraint generation path (e.g., in methods that analyze arguments, receivers,
and nested invocations) so pseudo-assignment typing uses dataflow-aware lookup
consistently; update signatures and call sites for generateConstraintsForCall
and any helper methods to accept and forward calledFromDataflow.
- Around line 1065-1081: The code is writing inferred lambda/member-reference
types into inferredPolyExpressionTypes even when calledFromDataflow is true; to
avoid caching dataflow-only inference results, skip adding entries when
calledFromDataflow is true. Modify the lambda passed to
InvocationArguments.forEach so that before calling
inferredPolyExpressionTypes.put(argument, typeWithInferredNullability) you check
if (!calledFromDataflow) (or return early when calledFromDataflow) and only then
perform the put; keep the existing computation of typeWithInferredNullability if
you still need it for local logic, but do not mutate inferredPolyExpressionTypes
when calledFromDataflow is true (references: inferredPolyExpressionTypes,
calledFromDataflow, InvocationArguments.forEach,
TypeSubstitutionUtils.updateTypeWithInferredNullability, InferenceSuccess).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6dfbda00-7589-492f-bde0-ce220b6e9091

📥 Commits

Reviewing files that changed from the base of the PR and between 2a5e8a9 and 647bff9.

📒 Files selected for processing (4)
  • nullaway/src/main/java/com/uber/nullaway/dataflow/AccessPathNullnessPropagation.java
  • nullaway/src/main/java/com/uber/nullaway/dataflow/RunOnceForwardAnalysisImpl.java
  • nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java
  • nullaway/src/test/java/com/uber/nullaway/jspecify/VarDeclaredLocalTests.java

@msridhar msridhar marked this pull request as draft May 19, 2026 17:24
@msridhar msridhar marked this pull request as ready for review May 19, 2026 18:37
for (MethodInvocationTree invTree : allInvocations) {
inferredTypeVarNullabilityForGenericCalls.put(invTree, successResult);
}
// Store inferred types for lambda or method reference arguments
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is not strictly related to the PR, but it was spotted by coderabbit and relates to avoiding caching results when called from dataflow. It's a relatively simple fix so I decided to include it without doing a separate PR, but could separate it out if desired.

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.

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)

495-565: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reuse the cached var type for assignment targets too.

This only fixes IdentifierTree lookups. A later foo = ... still gets its LHS type from lhsSymbol.type, so a var local whose declaration was repaired to Foo<@nullable String> is re-checked/re-inferred as javac's Foo<String>. That leaves reassignments and RHS generic-call inference incorrect for the same local.

Suggested fix
@@
       } else if (tree instanceof AssignmentTree assignmentTree) {
         // type on the tree itself can be missing nested annotations for arrays; get the type from
         // the symbol for the assigned location instead, if available
         Symbol lhsSymbol = ASTHelpers.getSymbol(assignmentTree.getVariable());
         if (lhsSymbol != null) {
-          result = lhsSymbol.type;
+          Type inferredVarLocalType =
+              getInferredVarLocalType(lhsSymbol, state, calledFromDataflow);
+          result = inferredVarLocalType != null ? inferredVarLocalType : lhsSymbol.type;
         } else {
           result = ASTHelpers.getType(assignmentTree);
         }

Also applies to: 2356-2372

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java` around
lines 495 - 565, The IdentifierTree branch in getTreeType reuses
getInferredVarLocalType but assignment targets still read lhsSymbol.type causing
var locals with repaired generic annotations to lose inferred annotations on
reassignments; update the AssignmentTree handling to call
getInferredVarLocalType(symbol, state, calledFromDataflow) (same as used in the
IdentifierTree branch) and if it returns non-null use that Type instead of
lhsSymbol.type. Locate the assignment/compound-assignment handling in
getTreeType and replace the direct use of lhsSymbol.type with the
inferredVarLocalType when available; ensure to reference
getInferredVarLocalType, AssignmentTree, and lhsSymbol.type so the change
mirrors the IdentifierTree fix and also apply the same change to the other
occurrence referenced (lines ~2356-2372).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java`:
- Around line 495-565: The IdentifierTree branch in getTreeType reuses
getInferredVarLocalType but assignment targets still read lhsSymbol.type causing
var locals with repaired generic annotations to lose inferred annotations on
reassignments; update the AssignmentTree handling to call
getInferredVarLocalType(symbol, state, calledFromDataflow) (same as used in the
IdentifierTree branch) and if it returns non-null use that Type instead of
lhsSymbol.type. Locate the assignment/compound-assignment handling in
getTreeType and replace the direct use of lhsSymbol.type with the
inferredVarLocalType when available; ensure to reference
getInferredVarLocalType, AssignmentTree, and lhsSymbol.type so the change
mirrors the IdentifierTree fix and also apply the same change to the other
occurrence referenced (lines ~2356-2372).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: ba75d815-1f42-4159-b3af-d62ac00c5925

📥 Commits

Reviewing files that changed from the base of the PR and between 647bff9 and 43a13f1.

📒 Files selected for processing (1)
  • nullaway/src/main/java/com/uber/nullaway/generics/GenericsChecks.java

@msridhar msridhar requested a review from lazaroclapp May 19, 2026 20:21
@msridhar msridhar force-pushed the handle-var-better branch from 97205e2 to 5fed645 Compare May 22, 2026 02:08
@msridhar msridhar force-pushed the handle-var-better branch from 5fed645 to 89aaeca Compare May 23, 2026 20:14
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.

Minor, perhaps dumb, question, but otherwise this LGTM

void testPositive2(Foo<@Nullable Object> f1, Foo<Object> f2) {
var foo = makeNested(f1);
// BUG: Diagnostic contains: inference failure
foo = makeNested(f2);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this one a true positive and the one below a true negative, again?

We have: Foo<Object> :<: Foo<@Nullable Object> but not

Foo<Foo<Object>> :<: Foo<Foo<@Nullable Object>>?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For testNegative(), at the var foo = make(null) assignment we infer the type of foo as Foo<@Nullable Object>. Then, for the statement foo = make(new Object()), we can again infer the type returned by make to be Foo<@Nullable Object> to make type checking succeed (since you can pass new Object() to a parameter of type @Nullable Object. I will add a comment to the test to explain this, it's subtle.

@msridhar msridhar enabled auto-merge (squash) May 25, 2026 16:32
@msridhar msridhar merged commit 755f7fb into master May 25, 2026
9 of 14 checks passed
@msridhar msridhar deleted the handle-var-better branch May 25, 2026 16:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Handle interactions between var locals and generic method inference

2 participants