Skip to content

Conversation

@kunnaall04
Copy link

Fix false negative with walrus operator in tuples

Summary

Fixes #20606.

This PR fixes a regression introduced in mypy 1.18+ where type errors involving assignment expressions (walrus operators) inside tuples were silently ignored. In short: errors that should have been reported were not, resulting in false negatives.


Problem Description

Since commit a856e55 (#19767), mypy fails to detect certain type errors when assignment expressions appear inside tuples.

The issue originates in infer_context_dependent() inside checker.py. This method uses an optimization where type inference is attempted twice:

  1. First pass: using the provided type context (this correctly finds errors)
  2. Second pass: if errors are found, retry inference with an empty context (this is where things go wrong)

When walrus expressions are involved, the empty-context fallback can incorrectly infer types, effectively masking real errors.

Example

def condition() -> bool:
    return False

def fn() -> tuple[int, int]:
    i: int | None = 0 if condition() else None
    return (i, (i := i + 1))  # NO ERROR REPORTED!

Expected

  • Incompatible return type
  • Unsupported operand type (None + int)

Actual

  • No errors reported (false negative)

Why this happens

In the expression (i := i + 1), when inferred without the tuple context, mypy infers i as int instead of int | None. That makes the expression appear valid, even though it isn’t.


Root Cause

The empty-context fallback in infer_context_dependent() is unsafe for expressions that contain assignment expressions.

While the first inference pass correctly detects errors, the second pass can override those results with an incorrect but “valid-looking” inference.


Solution

This PR introduces a conservative fix:

  • Add a new helper method contains_assignment_expr() that recursively checks whether an expression contains a walrus operator.
  • If an expression contains an assignment expression, skip the empty-context fallback in infer_context_dependent().

This preserves correctness without affecting the existing optimization for regular expressions.


Changes

  • Modified infer_context_dependent() to skip empty-context inference when assignment expressions are present
  • Added a comprehensive contains_assignment_expr() helper that handles all expression types
  • Updated visit_return_stmt() to include AssignmentExpr in the set of expression types checked in return values

Testing

Test Cases

def fn() -> tuple[int, int]:
    i: int | None = 0 if condition() else None
    return (i, (i := i + 1))  # Should error (now does!)

def fn2() -> tuple[int, int]:
    i: int | None = None
    return (i, (i := i + 1))  # Should error (now does!)

def fn3() -> tuple[int, int]:
    i: int | None = 0 if condition() else None
    return (i, (j := i + 1))  # Should error (already did, still does)

Before the fix

  • Only fn3() produced errors
  • Total errors: 2

After the fix

  • All three functions produce errors
  • Total errors: 6

Additional Notes


Files Changed

  • mypy/checker.py

    • Modified infer_context_dependent()
    • Updated visit_return_stmt()
    • Added contains_assignment_expr() helper

Kunal Sali and others added 5 commits January 20, 2026 00:28
…us operator detection

- Add missing imports: ConditionalExpr, GeneratorExpr, ListComprehension, SetComprehension, DictionaryComprehension, SliceExpr
- Handle ConditionalExpr (ternary operator) to check cond, if_expr, and else_expr
- Handle SliceExpr to check begin_index, end_index, and stride
- Handle GeneratorExpr and comprehensions (ListComprehension, SetComprehension, DictionaryComprehension)
- Ensures comprehensive detection of assignment expressions (walrus operators) in all nested expression contexts
- Fixes CI failures related to incomplete expression type handling
- Fix DictExpr iteration: iterate over items directly (items is list[tuple[Expression | None, Expression]])
- Fix IndexExpr: remove unnecessary None check since index is always Expression, not Optional
- Addresses mypy self-check failures:
  - DictExpr has no attribute 'values'
  - Unreachable code after return statement
  - Type mismatch in zip iteration
@github-actions
Copy link
Contributor

According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅

@ilevkivskyi
Copy link
Member

Sorry, this is very wrong, and as you can se in the issue I mentioned I am already working on this.

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.

[1.18 regression] False negative with variable increment in a walrus inside a tuple

2 participants