B031: don't count store-context references as generator uses#558
Conversation
A reference to the groupby loop variable in store context (an annotation target like `group: T`, or an assignment target) was counted as a use of the generator, so a single real use plus an annotation tripped a false-positive B031. Count only loads. Addresses PyCQA#465 (the annotation/store-context case; the if/else case is handled separately by PyCQA#557).
cooperlees
left a comment
There was a problem hiding this comment.
Nice - Thanks for the test showing it do as you claim too!
Let's just see if copilot nit picks anything useful before merge.
There was a problem hiding this comment.
Pull request overview
Fixes a B031 false positive by ensuring the “group” variable from itertools.groupby() is only counted as “used” when it’s read (AST Load context), not when it’s merely a store-context reference (e.g., annotation/assignment targets). This aligns B031 with its intent—detecting multiple consumptions of the group iterator—without inflating counts due to Store-context nodes.
Changes:
- Update the B031 usage counter to count only
ast.Namenodes inast.Loadcontext. - Add an eval-file regression case where the loop variable is annotated and then used once (no B031 expected).
- Document the fix in the UNRELEASED changelog.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
bugbear.py |
Restricts B031 “usage” counting (and nested-loop detection) to Load-context name references. |
tests/eval_files/b031.py |
Adds a regression example ensuring loop-variable annotations don’t trigger B031. |
README.rst |
Adds an UNRELEASED changelog entry describing the B031 behavior fix. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
what
fixes a B031 false positive: type-annotating (or assigning to) the
groupbyloop variable was counted as a "use", so a single real use plus an annotation tripped "used more than once".before: B031 fires. after: clean.
why
the B031 usage-counter walked the loop body and counted every
Namematching the group variable regardless of context. an annotation target (group: T) or an assignment target is aStore-contextName, not a use of the generator, so it inflated the count. count onlyLoad-context references.scope
this is the type-annotation / store-context case raised in the comments on #465. the original if/else case is handled separately by #557, and the two are orthogonal: if/else uses are both loads, untouched by this change, so they don't collide. real double-uses still fire B031.
tests
a case in
tests/eval_files/b031.pythat annotates the loop variable then uses it once, expecting no B031. it fails before the fix and passes after. full suite stays green.Addresses #465.