Skip to content

Fix 1.20 regression: type narrowing lost on reassignment with generator/ternary expressions#21280

Draft
parinporecha wants to merge 3 commits intopython:masterfrom
parinporecha:fix/issue-21273-walrus-guard-regression
Draft

Fix 1.20 regression: type narrowing lost on reassignment with generator/ternary expressions#21280
parinporecha wants to merge 3 commits intopython:masterfrom
parinporecha:fix/issue-21273-walrus-guard-regression

Conversation

@parinporecha
Copy link
Copy Markdown

Summary

PR #20622 introduced a binder_version guard to prevent false negatives when walrus operators (:=) interact with type inference fallback logic. However, this guard was too broad: any expression that bumps the binder version (including generator expressions with ternary operators) would disable the union fallback path, causing type narrowing to be lost on reassignment.

Problem

from typing import Iterable

def foo(args: Iterable[str | int] | str | int) -> Iterable[str]:
    if isinstance(args, (str, int)):
        args = (args,)
    args = (
        arg if isinstance(arg, str) else str(arg)
        for arg in args
    )
    return args  # Error: Generator[str | int, ...] not compatible with Iterable[str]

This worked in mypy 1.19 but regressed in 1.20.

Fix

Replace the binder_version == self.binder.version check with an explicit check for AssignmentExpr (walrus :=) in the expression tree, following the suggestion from @ilevkivskyi in the issue.

The walrus operator is the specific construct that causes problematic binder mutations during r.h.s. acceptance. Other constructs that bump the binder version (ternary in generators, etc.) are benign and should not disable the fallback.

Changes

  • Add _AssignmentExprSeeker (TraverserVisitor) to detect walrus in expression trees
  • Add TypeChecker._has_assignment_expr() static method
  • Replace binder_version == self.binder.version with not has_walrus in union_fallback guard

Testing

  • All 60 check-python38 tests pass, including testAssignToOptionalTupleWalrus and testReturnTupleOptionalWalrus
  • Full testcheck suite running locally

Issue 21273

…ion guard

The binder version guard introduced in PR python#20622 was too broad: it
disabled union fallback for any expression that modifies the binder,
including generator expressions with ternary operators. This caused
type narrowing via reassignment to fail for non-walrus expressions.

Replace the binder_version == self.binder.version check with an
explicit check for AssignmentExpr (walrus :=) in the expression tree,
which is the actual problematic case. This restores type narrowing
for generator expressions and other non-walrus constructs while
preserving the walrus safety guard.
@github-actions

This comment has been minimized.

@github-actions
Copy link
Copy Markdown
Contributor

Diff from mypy_primer, showing the effect of this PR on open source code:

graphql-core (https://github.com/graphql-python/graphql-core)
+ src/graphql/execution/execute.py:1793: error: Unused "type: ignore" comment  [unused-ignore]

Copy link
Copy Markdown
Member

@ilevkivskyi ilevkivskyi left a comment

Choose a reason for hiding this comment

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

This will have horrible performance implications. Since the decision is made post-visit, a simple flag set in visit_assignment_expression() would be sufficient. Moreover, that flag can be set only if the binder was actually used.

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.

2 participants