Skip to content

B012: don't flag break/continue in an async for loop inside finally - #602

Merged
cooperlees merged 1 commit into
PyCQA:mainfrom
spicysharkco:fix/b012-async-for
Oct 21, 2026
Merged

cooperlees merged 1 commit into
PyCQA:mainfrom
spicysharkco:fix/b012-async-for

Conversation

@spicysharkco

Copy link
Copy Markdown

B012 does not report break or continue in a for or while loop inside a finally block, since they only leave that loop (#106, fixed in #107). An async for loop is missing from that exemption.

async def f(xs):
    try:
        pass
    finally:
        async for x in xs:
            if x:
                break
            continue

flake8 --select=B012 file.py reports:

file.py:7:17: B012 return/continue/break inside finally blocks cause exceptions to be silenced. ...
file.py:8:13: B012 return/continue/break inside finally blocks cause exceptions to be silenced. ...

The same loop written as for x in xs: in a plain def is clean. Neither statement can silence an exception from the try block, so both reports are false positives. The README text for B012 is about break, continue or return that "will silence exceptions or override return values from the try or except blocks".

Cause: _loop in check_for_b012 only treats ast.While and ast.For as loops (bugbear.py#L1415).

Fix: add ast.AsyncFor to that tuple. A return inside the loop is still reported.

Test: a new async def case in tests/eval_files/b012.py with break and continue (no warning) and return (B012). pytest tests/test_bugbear.py -k b012 fails before the fix with two extra B012 reports and passes after. Full tests/test_bugbear.py: 123 passed, 4 skipped (Python 3.12, flake8 7.4.1). The pre-commit hooks pass. I added a line under UNRELEASED in README.rst.

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.

🟡 Changes recommended

The exemption also hides valid warnings for exception-suppressing exits in an async loop’s else suite.

1 open issue
What changed in this PR

Extends B012’s loop exemption to async for inside finally, matching existing synchronous-loop handling.

Changes:

  • Exempts async-loop break and continue while retaining warnings for return.
  • Adds regression coverage and an unreleased changelog entry.
File Description
tests/​eval_files/​b012.py Adds async-loop diagnostic cases.
README.rst Documents the B012 change.
bugbear.py Adds ast.AsyncFor to loop handling.

Review effort: Balanced

Comment thread bugbear.py
Comment on lines +1415 to 1416
if isinstance(node, (ast.While, ast.For, ast.AsyncFor)):
bad_node_types = (ast.Return,)
B012 skips break and continue in a for or while loop that sits inside
a finally block, since they only leave that loop. An async for loop
got no such exemption, so the same code was reported once it became
async.

Treat AsyncFor like For and While there. A return inside the loop is
still reported.

@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.

Thanks!

@cooperlees
cooperlees merged commit b3eccf1 into PyCQA:main Oct 21, 2026
7 checks passed
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