Fix annotated assignment expressions being treated as unpacking targets - #563
Merged
agronholm merged 3 commits intoJul 19, 2026
Merged
Conversation
visit_NamedExpr wrapped its single target in an extra list, emitting
[[('x', int)]] where the plain assignment path emits [('x', int)] for a
single name. check_variable_assignment reads a list group as a
tuple-unpacking target, so the walrus took the unpacking branch and
consumed the assigned value with list().
That produced two wrong outcomes for a name that carries an annotation:
a non-iterable value raised TypeError ('int' object is not iterable),
and an iterable value raised nothing but was silently replaced, so
x: str = 'abc' left x as ['a', 'b', 'c'].
A walrus target is always a single Name, so emit a bare tuple like the
assignment path does. Type checking itself is unaffected: a genuinely
wrong value still raises TypeCheckError.
The two transformer tests asserting the old output encoded this defect,
so they now expect the single-target form.
Closes agronholm#557
agronholm
reviewed
Jul 18, 2026
Contributor
Author
|
Removed, thanks — the reasoning lives in the PR description instead. For reference, the emitted call goes from
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #557.
visit_NamedExprwrapped its single target in an extra list, emittingwhere the plain-assignment path emits
[('x', int)]for a single name.check_variable_assignmentreads a list group as a tuple-unpacking target and a tuple group as a single target, so an annotated walrus took the unpacking branch and consumed the assigned value withlist(value).Two wrong outcomes, both only when the name carries an annotation:
5TypeError: 'int' object is not iterable5NoneTypeError: 'NoneType' object is not iterableNone"abc"xbecomes['a', 'b', 'c']"abc""not an int"(wrong type)TypeCheckErrorTypeCheckError(unchanged)The third row is the dangerous one — no exception, just a silently corrupted variable.
A walrus target is always a single
Name, so it now emits a bare tuple like the assignment path does. Type checking itself is unaffected: a genuinely wrong value still raisesTypeCheckError.Tests
New
TestAssignmentExpressionintest_typechecked.pycovers the behaviour rather than the emitted source: non-iterable value, iterable value not consumed, an annotated argument reused in a walrus, an iterator not drained, and a wrong-typed value still raising.Reverting only
src/fails 4 of the 5 (the 5th passes either way, since it asserts the error that already worked):test_transformer.py::TestAssign::test_assignment_exprandtest_assignment_expr_annotated_argumentasserted the old[[('x', int)]]output, i.e. they encoded the defect, so they now expect the single-target form. Worth noting those two golden tests passed throughout the bug's lifetime — pinning emitted source didn't catch it, which is why the new tests assert runtime behaviour instead.Verification
--ignore=tests/mypy) → 516 passed, 7 skipped, 9 xfailedruff check src tests→ all checks passed;ruff format --check→ 31 files already formatteddocs/versionhistory.rsttests/mypyis excluded above: both tests there fail withFileNotFoundErroron a clean tree too (no mypy binary in my environment), unrelated to this change.