Skip to content

fix B031 false positive with if-else branches#557

Open
wali-reheman wants to merge 2 commits into
PyCQA:mainfrom
wali-reheman:fix/b031-false-positive-if-else
Open

fix B031 false positive with if-else branches#557
wali-reheman wants to merge 2 commits into
PyCQA:mainfrom
wali-reheman:fix/b031-false-positive-if-else

Conversation

@wali-reheman

Copy link
Copy Markdown

when the groupby variable is used inside an if-else, only one branch executes at runtime, so using it in both branches shouldn't trigger B031.

Only one branch of an if-else executes at runtime, so using the
groupby variable in both branches shouldn't trigger B031.
Previously, ast.walk() visited both branches and counted the
variable twice, causing a false positive.

@cooperlees cooperlees left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Hi - many thanks for the potential bug fix. Can you add some example code into B031's tests to show it now working and the code so I can try understand the bug more please.

Also, please make sure pre-commit passes and let's see if copilot finds anything.

Thanks again.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the B031 check (check_for_b031) to avoid flagging itertools.groupby() iterators as “used multiple times” when the iterator is referenced in both branches of an if/else, since only one branch executes at runtime.

Changes:

  • Added parent-tracking within the loop subtree to enable upward traversal from ast.Name nodes.
  • Introduced helper logic to detect if/else constructs where both branches reference the group iterator, and attempted to suppress B031 counting in that scenario.
  • Adjusted B031 usage counting to skip group_name occurrences detected as part of such an if/else.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread bugbear.py
Comment on lines +1225 to +1236
body_contains = any(
isinstance(n, ast.Name) and n.id == name
for n in ast.walk(if_node.body[0])
)
else_contains = (
bool(if_node.orelse)
and any(
isinstance(n, ast.Name) and n.id == name
for n in ast.walk(if_node.orelse[0])
)
)
return body_contains and else_contains
Comment thread bugbear.py
Comment on lines +1269 to +1273
# Skip if this name is in an if-else where the other
# branch also uses it (only one branch executes)
if is_in_if_branch_where_other_branch_has(
node, group_name
):
Comment thread bugbear.py
Comment on lines +1209 to +1214
# Build parent map for this subtree so we can walk up
parent_map: dict[ast.AST, ast.AST] = {}

class ParentTracker(ast.NodeVisitor):
def __init__(self) -> None:
super().__init__()
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.

3 participants