Skip to content

Commit e4c2859

Browse files
authored
[flake8-async] Do not lint yield in context manager cancel-scope-no-checkpoint (ASYNC100) (#12896)
For compatibility with upstream, treat `yield` as a checkpoint inside cancel scopes. Closes #12873.
1 parent 6dcd743 commit e4c2859

File tree

2 files changed

+36
-2
lines changed

2 files changed

+36
-2
lines changed

crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC100.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,26 @@ async def func():
8989
async def func():
9090
async with asyncio.timeout(delay=0.2), asyncio.timeout(delay=0.2):
9191
...
92+
93+
94+
# Don't trigger for blocks with a yield statement
95+
async def foo():
96+
with trio.fail_after(1):
97+
yield
98+
99+
100+
async def foo(): # even if only one branch contains a yield, we skip the lint
101+
with trio.fail_after(1):
102+
if something:
103+
...
104+
else:
105+
yield
106+
107+
108+
# https://github.com/astral-sh/ruff/issues/12873
109+
@asynccontextmanager
110+
async def good_code():
111+
with anyio.fail_after(10):
112+
# There's no await keyword here, but we presume that there
113+
# will be in the caller we yield to, so this is safe.
114+
yield

crates/ruff_linter/src/rules/flake8_async/rules/cancel_scope_no_checkpoint.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
use ruff_diagnostics::{Diagnostic, Violation};
22
use ruff_macros::{derive_message_formats, violation};
3-
use ruff_python_ast::helpers::AwaitVisitor;
3+
use ruff_python_ast::helpers::{any_over_body, AwaitVisitor};
44
use ruff_python_ast::visitor::Visitor;
5-
use ruff_python_ast::{StmtWith, WithItem};
5+
use ruff_python_ast::{Expr, StmtWith, WithItem};
66

77
use crate::checkers::ast::Checker;
88
use crate::rules::flake8_async::helpers::MethodName;
99

1010
/// ## What it does
1111
/// Checks for timeout context managers which do not contain a checkpoint.
1212
///
13+
/// For the purposes of this check, `yield` is considered a checkpoint,
14+
/// since checkpoints may occur in the caller to which we yield.
15+
///
1316
/// ## Why is this bad?
1417
/// Some asynchronous context managers, such as `asyncio.timeout` and
1518
/// `trio.move_on_after`, have no effect unless they contain a checkpoint.
@@ -80,6 +83,14 @@ pub(crate) fn cancel_scope_no_checkpoint(
8083
return;
8184
}
8285

86+
// Treat yields as checkpoints, since checkpoints can happen
87+
// in the caller yielded to.
88+
// See: https://flake8-async.readthedocs.io/en/latest/rules.html#async100
89+
// See: https://github.com/astral-sh/ruff/issues/12873
90+
if any_over_body(&with_stmt.body, &Expr::is_yield_expr) {
91+
return;
92+
}
93+
8394
// If the body contains an `await` statement, the context manager is used correctly.
8495
let mut visitor = AwaitVisitor::default();
8596
visitor.visit_body(&with_stmt.body);

0 commit comments

Comments
 (0)