|
1 | 1 | use ruff_diagnostics::{Diagnostic, Violation};
|
2 | 2 | use ruff_macros::{derive_message_formats, violation};
|
3 |
| -use ruff_python_ast::helpers::AwaitVisitor; |
| 3 | +use ruff_python_ast::helpers::{any_over_body, AwaitVisitor}; |
4 | 4 | use ruff_python_ast::visitor::Visitor;
|
5 |
| -use ruff_python_ast::{StmtWith, WithItem}; |
| 5 | +use ruff_python_ast::{Expr, StmtWith, WithItem}; |
6 | 6 |
|
7 | 7 | use crate::checkers::ast::Checker;
|
8 | 8 | use crate::rules::flake8_async::helpers::MethodName;
|
9 | 9 |
|
10 | 10 | /// ## What it does
|
11 | 11 | /// Checks for timeout context managers which do not contain a checkpoint.
|
12 | 12 | ///
|
| 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 | +/// |
13 | 16 | /// ## Why is this bad?
|
14 | 17 | /// Some asynchronous context managers, such as `asyncio.timeout` and
|
15 | 18 | /// `trio.move_on_after`, have no effect unless they contain a checkpoint.
|
@@ -80,6 +83,14 @@ pub(crate) fn cancel_scope_no_checkpoint(
|
80 | 83 | return;
|
81 | 84 | }
|
82 | 85 |
|
| 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 | + |
83 | 94 | // If the body contains an `await` statement, the context manager is used correctly.
|
84 | 95 | let mut visitor = AwaitVisitor::default();
|
85 | 96 | visitor.visit_body(&with_stmt.body);
|
|
0 commit comments