B020: don't flag names bound in nested destructuring patterns (#521) - #581
Merged
Merged
Conversation
A name bound inside a nested destructuring pattern is not what the for statement rebinds each iteration; the iterable expression already read the outer value once when it was evaluated. Matching the whole target name tree reported safe loops like `for i, (ax, (series, name)) in enumerate(zip(axs, series))`. Co-authored-by: Claude <noreply@anthropic.com>
cooperlees
approved these changes
Sep 28, 2026
cooperlees
left a comment
Collaborator
There was a problem hiding this comment.
This is muse (muse-spark), posting on behalf of @cooperlees via his coding agent.
Reviewed the diff and verified locally: checked out the PR head and ran the full suite in an isolated venv — 84 passed (plus the 2 pre-existing skips on this 3.14 runner). Also merged current main to resolve the README UNRELEASED conflict (kept all entries) and pushed to b020-nested-unpacking; the merge result is green and the PR is MERGEABLE.
What I checked:
- The #521 false positive is genuinely fixed:
for i, (ax, (series, def_name)) in enumerate(zip(axs, series))is clean on the PR branch, fires onmain. - True positives preserved — I diffed old-vs-new B020 output on 11 target shapes: bare
for a in a, top-level tuple shadow (for key, items in list(items)), top-levelStarred, subscript targets, same-path attributes (for obj.value in obj.value), and thefor smoother in smoother.smootherscase from #521 all still fire; sibling attributes (for self.a in self.b, #568) stay clean. - The nested-Starred edge (
for (*a, b) in a) still fires via the top-levelStarredunwrap — no behavior loss there. The only silenced shapes are genuinely nested bindings, which is exactly the intent. NameFinderis still used elsewhere (B913UsageFindersubclasses it), so no dead import.- flake8 self-clean on
bugbear.py: clean. mypy: same 4 pre-existing errors asmain, no new ones. New eval cases intests/eval_files/b020.pycover the issue-verbatim shape, one-level and starred nesting, plus a still-firing top-level case.
No changes requested. Approving for merge.
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.
B020matched any name inside the loop target tree against the names loaded by the iterable expression. A name bound inside a nested destructuring pattern is not what theforstatement rebinds on each iteration — the iterable expression reads the outer value exactly once, before iteration starts — so safe loops were reported:(The two other shapes from #521:
for smoother in smoother.smoothersis a genuine top-level shadow and stays flagged; the sibling-attribute cases were already fixed by #568.)Fix
check_for_b020now considers only the top-level bindings of the target (includingStarred) as plain-name candidates. Whole dotted paths (for self.a in self.b) keep the path comparison from #568 unchanged, and top-level unpacking shadows likefor key, values in values.items()are still reported.Tests
tests/eval_files/b020.py: the issue-verbatim two-level unpacking, a one-level unpacking, a starred nested unpacking (all must stay clean), and a top-level tuple binding that must still fire.pytest tests/green (72 passed, 12 skipped — all skips are_PYversion-gated eval files on this local 3.9 runner) including the self-clean flake8 checks;flake8 bugbear.pyclean (the eval file intentionally contains flagged loops and is not linted);-k b902pep8-naming env green;mypy bugbear.pyshows no new errors on this env (8-error baseline is Python 3.9-specific —ast.TryStar, CI runs 3.13).# OPTIONS-style hypothesis fuzz tests pass.Fixes #521
Co-authored-by: Claude noreply@anthropic.com