diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC100.py b/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC100.py index 4499657cc2698..24d89f49225cd 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC100.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC100.py @@ -1,3 +1,5 @@ +import anyio +import asyncio import trio @@ -25,3 +27,33 @@ async def func(): with trio.move_at(): async with trio.open_nursery() as nursery: ... + + +async def func(): + with anyio.move_on_after(): + ... + + +async def func(): + with anyio.fail_after(): + ... + + +async def func(): + with anyio.CancelScope(): + ... + + +async def func(): + with anyio.CancelScope(): + ... + + +async def func(): + with asyncio.timeout(): + ... + + +async def func(): + with asyncio.timeout_at(): + ... diff --git a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs index 148c22abb9afc..df40da16ad61f 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs @@ -1313,8 +1313,8 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { if checker.enabled(Rule::UselessWithLock) { pylint::rules::useless_with_lock(checker, with_stmt); } - if checker.enabled(Rule::TrioTimeoutWithoutAwait) { - flake8_async::rules::timeout_without_await(checker, with_stmt, items); + if checker.enabled(Rule::CancelScopeNoCheckpoint) { + flake8_async::rules::cancel_scope_no_checkpoint(checker, with_stmt, items); } } Stmt::While(while_stmt @ ast::StmtWhile { body, orelse, .. }) => { diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index 5c7a2e4456ecf..1060ccaeba908 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -293,7 +293,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Pylint, "W3301") => (RuleGroup::Stable, rules::pylint::rules::NestedMinMax), // flake8-async - (Flake8Async, "100") => (RuleGroup::Stable, rules::flake8_async::rules::TrioTimeoutWithoutAwait), + (Flake8Async, "100") => (RuleGroup::Stable, rules::flake8_async::rules::CancelScopeNoCheckpoint), (Flake8Async, "105") => (RuleGroup::Stable, rules::flake8_async::rules::TrioSyncCall), (Flake8Async, "109") => (RuleGroup::Stable, rules::flake8_async::rules::AsyncFunctionWithTimeout), (Flake8Async, "110") => (RuleGroup::Stable, rules::flake8_async::rules::TrioUnneededSleep), diff --git a/crates/ruff_linter/src/rules/flake8_async/helpers.rs b/crates/ruff_linter/src/rules/flake8_async/helpers.rs index 7695679c937d6..b726d8fda8b84 100644 --- a/crates/ruff_linter/src/rules/flake8_async/helpers.rs +++ b/crates/ruff_linter/src/rules/flake8_async/helpers.rs @@ -12,69 +12,124 @@ pub(super) enum AsyncModule { #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub(super) enum MethodName { - AcloseForcefully, - CancelScope, - CancelShieldedCheckpoint, - Checkpoint, - CheckpointIfCancelled, - FailAfter, - FailAt, - MoveOnAfter, - MoveOnAt, - OpenFile, - OpenProcess, - OpenSslOverTcpListeners, - OpenSslOverTcpStream, - OpenTcpListeners, - OpenTcpStream, - OpenUnixSocket, - PermanentlyDetachCoroutineObject, - ReattachDetachedCoroutineObject, - RunProcess, - ServeListeners, - ServeSslOverTcp, - ServeTcp, - Sleep, - SleepForever, - TemporarilyDetachCoroutineObject, - WaitReadable, - WaitTaskRescheduled, - WaitWritable, + AsyncIoTimeout, + AsyncIoTimeoutAt, + AnyIoMoveOnAfter, + AnyIoFailAfter, + AnyIoCancelScope, + TrioAcloseForcefully, + TrioCancelScope, + TrioCancelShieldedCheckpoint, + TrioCheckpoint, + TrioCheckpointIfCancelled, + TrioFailAfter, + TrioFailAt, + TrioMoveOnAfter, + TrioMoveOnAt, + TrioOpenFile, + TrioOpenProcess, + TrioOpenSslOverTcpListeners, + TrioOpenSslOverTcpStream, + TrioOpenTcpListeners, + TrioOpenTcpStream, + TrioOpenUnixSocket, + TrioPermanentlyDetachCoroutineObject, + TrioReattachDetachedCoroutineObject, + TrioRunProcess, + TrioServeListeners, + TrioServeSslOverTcp, + TrioServeTcp, + TrioSleep, + TrioSleepForever, + TrioTemporarilyDetachCoroutineObject, + TrioWaitReadable, + TrioWaitTaskRescheduled, + TrioWaitWritable, } impl MethodName { /// Returns `true` if the method is async, `false` if it is sync. pub(super) fn is_async(self) -> bool { - match self { - MethodName::AcloseForcefully - | MethodName::CancelShieldedCheckpoint - | MethodName::Checkpoint - | MethodName::CheckpointIfCancelled - | MethodName::OpenFile - | MethodName::OpenProcess - | MethodName::OpenSslOverTcpListeners - | MethodName::OpenSslOverTcpStream - | MethodName::OpenTcpListeners - | MethodName::OpenTcpStream - | MethodName::OpenUnixSocket - | MethodName::PermanentlyDetachCoroutineObject - | MethodName::ReattachDetachedCoroutineObject - | MethodName::RunProcess - | MethodName::ServeListeners - | MethodName::ServeSslOverTcp - | MethodName::ServeTcp - | MethodName::Sleep - | MethodName::SleepForever - | MethodName::TemporarilyDetachCoroutineObject - | MethodName::WaitReadable - | MethodName::WaitTaskRescheduled - | MethodName::WaitWritable => true, + matches!( + self, + MethodName::TrioAcloseForcefully + | MethodName::TrioCancelShieldedCheckpoint + | MethodName::TrioCheckpoint + | MethodName::TrioCheckpointIfCancelled + | MethodName::TrioOpenFile + | MethodName::TrioOpenProcess + | MethodName::TrioOpenSslOverTcpListeners + | MethodName::TrioOpenSslOverTcpStream + | MethodName::TrioOpenTcpListeners + | MethodName::TrioOpenTcpStream + | MethodName::TrioOpenUnixSocket + | MethodName::TrioPermanentlyDetachCoroutineObject + | MethodName::TrioReattachDetachedCoroutineObject + | MethodName::TrioRunProcess + | MethodName::TrioServeListeners + | MethodName::TrioServeSslOverTcp + | MethodName::TrioServeTcp + | MethodName::TrioSleep + | MethodName::TrioSleepForever + | MethodName::TrioTemporarilyDetachCoroutineObject + | MethodName::TrioWaitReadable + | MethodName::TrioWaitTaskRescheduled + | MethodName::TrioWaitWritable + ) + } + + /// Returns `true` if the method a timeout context manager. + pub(super) fn is_timeout_context(self) -> bool { + matches!( + self, + MethodName::AsyncIoTimeout + | MethodName::AsyncIoTimeoutAt + | MethodName::AnyIoMoveOnAfter + | MethodName::AnyIoFailAfter + | MethodName::AnyIoCancelScope + | MethodName::TrioMoveOnAfter + | MethodName::TrioMoveOnAt + | MethodName::TrioFailAfter + | MethodName::TrioFailAt + | MethodName::TrioCancelScope + ) + } - MethodName::MoveOnAfter - | MethodName::MoveOnAt - | MethodName::FailAfter - | MethodName::FailAt - | MethodName::CancelScope => false, + /// Returns associated module + pub(super) fn module(self) -> AsyncModule { + match self { + MethodName::AsyncIoTimeout | MethodName::AsyncIoTimeoutAt => AsyncModule::AsyncIo, + MethodName::AnyIoMoveOnAfter + | MethodName::AnyIoFailAfter + | MethodName::AnyIoCancelScope => AsyncModule::AnyIo, + MethodName::TrioAcloseForcefully + | MethodName::TrioCancelScope + | MethodName::TrioCancelShieldedCheckpoint + | MethodName::TrioCheckpoint + | MethodName::TrioCheckpointIfCancelled + | MethodName::TrioFailAfter + | MethodName::TrioFailAt + | MethodName::TrioMoveOnAfter + | MethodName::TrioMoveOnAt + | MethodName::TrioOpenFile + | MethodName::TrioOpenProcess + | MethodName::TrioOpenSslOverTcpListeners + | MethodName::TrioOpenSslOverTcpStream + | MethodName::TrioOpenTcpListeners + | MethodName::TrioOpenTcpStream + | MethodName::TrioOpenUnixSocket + | MethodName::TrioPermanentlyDetachCoroutineObject + | MethodName::TrioReattachDetachedCoroutineObject + | MethodName::TrioRunProcess + | MethodName::TrioServeListeners + | MethodName::TrioServeSslOverTcp + | MethodName::TrioServeTcp + | MethodName::TrioSleep + | MethodName::TrioSleepForever + | MethodName::TrioTemporarilyDetachCoroutineObject + | MethodName::TrioWaitReadable + | MethodName::TrioWaitTaskRescheduled + | MethodName::TrioWaitWritable => AsyncModule::Trio, } } } @@ -82,42 +137,49 @@ impl MethodName { impl MethodName { pub(super) fn try_from(qualified_name: &QualifiedName<'_>) -> Option { match qualified_name.segments() { - ["trio", "CancelScope"] => Some(Self::CancelScope), - ["trio", "aclose_forcefully"] => Some(Self::AcloseForcefully), - ["trio", "fail_after"] => Some(Self::FailAfter), - ["trio", "fail_at"] => Some(Self::FailAt), + ["asyncio", "timeout"] => Some(Self::AsyncIoTimeout), + ["asyncio", "timeout_at"] => Some(Self::AsyncIoTimeoutAt), + ["anyio", "move_on_after"] => Some(Self::AnyIoMoveOnAfter), + ["anyio", "fail_after"] => Some(Self::AnyIoFailAfter), + ["anyio", "CancelScope"] => Some(Self::AnyIoCancelScope), + ["trio", "CancelScope"] => Some(Self::TrioCancelScope), + ["trio", "aclose_forcefully"] => Some(Self::TrioAcloseForcefully), + ["trio", "fail_after"] => Some(Self::TrioFailAfter), + ["trio", "fail_at"] => Some(Self::TrioFailAt), ["trio", "lowlevel", "cancel_shielded_checkpoint"] => { - Some(Self::CancelShieldedCheckpoint) + Some(Self::TrioCancelShieldedCheckpoint) + } + ["trio", "lowlevel", "checkpoint"] => Some(Self::TrioCheckpoint), + ["trio", "lowlevel", "checkpoint_if_cancelled"] => { + Some(Self::TrioCheckpointIfCancelled) } - ["trio", "lowlevel", "checkpoint"] => Some(Self::Checkpoint), - ["trio", "lowlevel", "checkpoint_if_cancelled"] => Some(Self::CheckpointIfCancelled), - ["trio", "lowlevel", "open_process"] => Some(Self::OpenProcess), + ["trio", "lowlevel", "open_process"] => Some(Self::TrioOpenProcess), ["trio", "lowlevel", "permanently_detach_coroutine_object"] => { - Some(Self::PermanentlyDetachCoroutineObject) + Some(Self::TrioPermanentlyDetachCoroutineObject) } ["trio", "lowlevel", "reattach_detached_coroutine_object"] => { - Some(Self::ReattachDetachedCoroutineObject) + Some(Self::TrioReattachDetachedCoroutineObject) } ["trio", "lowlevel", "temporarily_detach_coroutine_object"] => { - Some(Self::TemporarilyDetachCoroutineObject) + Some(Self::TrioTemporarilyDetachCoroutineObject) } - ["trio", "lowlevel", "wait_readable"] => Some(Self::WaitReadable), - ["trio", "lowlevel", "wait_task_rescheduled"] => Some(Self::WaitTaskRescheduled), - ["trio", "lowlevel", "wait_writable"] => Some(Self::WaitWritable), - ["trio", "move_on_after"] => Some(Self::MoveOnAfter), - ["trio", "move_on_at"] => Some(Self::MoveOnAt), - ["trio", "open_file"] => Some(Self::OpenFile), - ["trio", "open_ssl_over_tcp_listeners"] => Some(Self::OpenSslOverTcpListeners), - ["trio", "open_ssl_over_tcp_stream"] => Some(Self::OpenSslOverTcpStream), - ["trio", "open_tcp_listeners"] => Some(Self::OpenTcpListeners), - ["trio", "open_tcp_stream"] => Some(Self::OpenTcpStream), - ["trio", "open_unix_socket"] => Some(Self::OpenUnixSocket), - ["trio", "run_process"] => Some(Self::RunProcess), - ["trio", "serve_listeners"] => Some(Self::ServeListeners), - ["trio", "serve_ssl_over_tcp"] => Some(Self::ServeSslOverTcp), - ["trio", "serve_tcp"] => Some(Self::ServeTcp), - ["trio", "sleep"] => Some(Self::Sleep), - ["trio", "sleep_forever"] => Some(Self::SleepForever), + ["trio", "lowlevel", "wait_readable"] => Some(Self::TrioWaitReadable), + ["trio", "lowlevel", "wait_task_rescheduled"] => Some(Self::TrioWaitTaskRescheduled), + ["trio", "lowlevel", "wait_writable"] => Some(Self::TrioWaitWritable), + ["trio", "move_on_after"] => Some(Self::TrioMoveOnAfter), + ["trio", "move_on_at"] => Some(Self::TrioMoveOnAt), + ["trio", "open_file"] => Some(Self::TrioOpenFile), + ["trio", "open_ssl_over_tcp_listeners"] => Some(Self::TrioOpenSslOverTcpListeners), + ["trio", "open_ssl_over_tcp_stream"] => Some(Self::TrioOpenSslOverTcpStream), + ["trio", "open_tcp_listeners"] => Some(Self::TrioOpenTcpListeners), + ["trio", "open_tcp_stream"] => Some(Self::TrioOpenTcpStream), + ["trio", "open_unix_socket"] => Some(Self::TrioOpenUnixSocket), + ["trio", "run_process"] => Some(Self::TrioRunProcess), + ["trio", "serve_listeners"] => Some(Self::TrioServeListeners), + ["trio", "serve_ssl_over_tcp"] => Some(Self::TrioServeSslOverTcp), + ["trio", "serve_tcp"] => Some(Self::TrioServeTcp), + ["trio", "sleep"] => Some(Self::TrioSleep), + ["trio", "sleep_forever"] => Some(Self::TrioSleepForever), _ => None, } } @@ -126,42 +188,51 @@ impl MethodName { impl std::fmt::Display for MethodName { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - MethodName::AcloseForcefully => write!(f, "trio.aclose_forcefully"), - MethodName::CancelScope => write!(f, "trio.CancelScope"), - MethodName::CancelShieldedCheckpoint => { + MethodName::AsyncIoTimeout => write!(f, "asyncio.timeout"), + MethodName::AsyncIoTimeoutAt => write!(f, "asyncio.timeout_at"), + MethodName::AnyIoMoveOnAfter => write!(f, "anyio.move_on_after"), + MethodName::AnyIoFailAfter => write!(f, "anyio.fail_after"), + MethodName::AnyIoCancelScope => write!(f, "anyio.CancelScope"), + MethodName::TrioAcloseForcefully => write!(f, "trio.aclose_forcefully"), + MethodName::TrioCancelScope => write!(f, "trio.CancelScope"), + MethodName::TrioCancelShieldedCheckpoint => { write!(f, "trio.lowlevel.cancel_shielded_checkpoint") } - MethodName::Checkpoint => write!(f, "trio.lowlevel.checkpoint"), - MethodName::CheckpointIfCancelled => write!(f, "trio.lowlevel.checkpoint_if_cancelled"), - MethodName::FailAfter => write!(f, "trio.fail_after"), - MethodName::FailAt => write!(f, "trio.fail_at"), - MethodName::MoveOnAfter => write!(f, "trio.move_on_after"), - MethodName::MoveOnAt => write!(f, "trio.move_on_at"), - MethodName::OpenFile => write!(f, "trio.open_file"), - MethodName::OpenProcess => write!(f, "trio.lowlevel.open_process"), - MethodName::OpenSslOverTcpListeners => write!(f, "trio.open_ssl_over_tcp_listeners"), - MethodName::OpenSslOverTcpStream => write!(f, "trio.open_ssl_over_tcp_stream"), - MethodName::OpenTcpListeners => write!(f, "trio.open_tcp_listeners"), - MethodName::OpenTcpStream => write!(f, "trio.open_tcp_stream"), - MethodName::OpenUnixSocket => write!(f, "trio.open_unix_socket"), - MethodName::PermanentlyDetachCoroutineObject => { + MethodName::TrioCheckpoint => write!(f, "trio.lowlevel.checkpoint"), + MethodName::TrioCheckpointIfCancelled => { + write!(f, "trio.lowlevel.checkpoint_if_cancelled") + } + MethodName::TrioFailAfter => write!(f, "trio.fail_after"), + MethodName::TrioFailAt => write!(f, "trio.fail_at"), + MethodName::TrioMoveOnAfter => write!(f, "trio.move_on_after"), + MethodName::TrioMoveOnAt => write!(f, "trio.move_on_at"), + MethodName::TrioOpenFile => write!(f, "trio.open_file"), + MethodName::TrioOpenProcess => write!(f, "trio.lowlevel.open_process"), + MethodName::TrioOpenSslOverTcpListeners => { + write!(f, "trio.open_ssl_over_tcp_listeners") + } + MethodName::TrioOpenSslOverTcpStream => write!(f, "trio.open_ssl_over_tcp_stream"), + MethodName::TrioOpenTcpListeners => write!(f, "trio.open_tcp_listeners"), + MethodName::TrioOpenTcpStream => write!(f, "trio.open_tcp_stream"), + MethodName::TrioOpenUnixSocket => write!(f, "trio.open_unix_socket"), + MethodName::TrioPermanentlyDetachCoroutineObject => { write!(f, "trio.lowlevel.permanently_detach_coroutine_object") } - MethodName::ReattachDetachedCoroutineObject => { + MethodName::TrioReattachDetachedCoroutineObject => { write!(f, "trio.lowlevel.reattach_detached_coroutine_object") } - MethodName::RunProcess => write!(f, "trio.run_process"), - MethodName::ServeListeners => write!(f, "trio.serve_listeners"), - MethodName::ServeSslOverTcp => write!(f, "trio.serve_ssl_over_tcp"), - MethodName::ServeTcp => write!(f, "trio.serve_tcp"), - MethodName::Sleep => write!(f, "trio.sleep"), - MethodName::SleepForever => write!(f, "trio.sleep_forever"), - MethodName::TemporarilyDetachCoroutineObject => { + MethodName::TrioRunProcess => write!(f, "trio.run_process"), + MethodName::TrioServeListeners => write!(f, "trio.serve_listeners"), + MethodName::TrioServeSslOverTcp => write!(f, "trio.serve_ssl_over_tcp"), + MethodName::TrioServeTcp => write!(f, "trio.serve_tcp"), + MethodName::TrioSleep => write!(f, "trio.sleep"), + MethodName::TrioSleepForever => write!(f, "trio.sleep_forever"), + MethodName::TrioTemporarilyDetachCoroutineObject => { write!(f, "trio.lowlevel.temporarily_detach_coroutine_object") } - MethodName::WaitReadable => write!(f, "trio.lowlevel.wait_readable"), - MethodName::WaitTaskRescheduled => write!(f, "trio.lowlevel.wait_task_rescheduled"), - MethodName::WaitWritable => write!(f, "trio.lowlevel.wait_writable"), + MethodName::TrioWaitReadable => write!(f, "trio.lowlevel.wait_readable"), + MethodName::TrioWaitTaskRescheduled => write!(f, "trio.lowlevel.wait_task_rescheduled"), + MethodName::TrioWaitWritable => write!(f, "trio.lowlevel.wait_writable"), } } } diff --git a/crates/ruff_linter/src/rules/flake8_async/mod.rs b/crates/ruff_linter/src/rules/flake8_async/mod.rs index e2ccdd7376833..2cff08d959414 100644 --- a/crates/ruff_linter/src/rules/flake8_async/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_async/mod.rs @@ -15,7 +15,7 @@ mod tests { use crate::test::test_path; use crate::{assert_messages, settings}; - #[test_case(Rule::TrioTimeoutWithoutAwait, Path::new("ASYNC100.py"))] + #[test_case(Rule::CancelScopeNoCheckpoint, Path::new("ASYNC100.py"))] #[test_case(Rule::TrioSyncCall, Path::new("ASYNC105.py"))] #[test_case(Rule::AsyncFunctionWithTimeout, Path::new("ASYNC109_0.py"))] #[test_case(Rule::AsyncFunctionWithTimeout, Path::new("ASYNC109_1.py"))] @@ -38,6 +38,7 @@ mod tests { Ok(()) } + #[test_case(Rule::CancelScopeNoCheckpoint, Path::new("ASYNC100.py"))] #[test_case(Rule::AsyncFunctionWithTimeout, Path::new("ASYNC109_0.py"))] #[test_case(Rule::AsyncFunctionWithTimeout, Path::new("ASYNC109_1.py"))] fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> { diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/timeout_without_await.rs b/crates/ruff_linter/src/rules/flake8_async/rules/cancel_scope_no_checkpoint.rs similarity index 54% rename from crates/ruff_linter/src/rules/flake8_async/rules/timeout_without_await.rs rename to crates/ruff_linter/src/rules/flake8_async/rules/cancel_scope_no_checkpoint.rs index f60b2002d4871..8495bd90cbaf5 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/timeout_without_await.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/cancel_scope_no_checkpoint.rs @@ -3,40 +3,44 @@ use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::helpers::AwaitVisitor; use ruff_python_ast::visitor::Visitor; use ruff_python_ast::{StmtWith, WithItem}; -use ruff_python_semantic::Modules; use crate::checkers::ast::Checker; -use crate::rules::flake8_async::helpers::MethodName; +use crate::rules::flake8_async::helpers::{AsyncModule, MethodName}; +use crate::settings::types::PreviewMode; /// ## What it does -/// Checks for trio functions that should contain await but don't. +/// Checks for timeout context managers which do not contain a checkpoint. /// /// ## Why is this bad? -/// Some trio context managers, such as `trio.fail_after` and +/// Some asynchronous context managers, such as `asyncio.timeout` and /// `trio.move_on_after`, have no effect unless they contain an `await` -/// statement. The use of such functions without an `await` statement is +/// statement. The use of such context managers without an `await` statement is /// likely a mistake. /// /// ## Example /// ```python /// async def func(): -/// with trio.move_on_after(2): +/// with asyncio.timeout(2): /// do_something() /// ``` /// /// Use instead: /// ```python /// async def func(): -/// with trio.move_on_after(2): +/// with asyncio.timeout(2): /// do_something() /// await awaitable() /// ``` +/// +/// [`asyncio` timeouts]: https://docs.python.org/3/library/asyncio-task.html#timeouts +/// [`anyio` timeouts]: https://anyio.readthedocs.io/en/stable/cancellation.html +/// [`trio` timeouts]: https://trio.readthedocs.io/en/stable/reference-core.html#cancellation-and-timeouts #[violation] -pub struct TrioTimeoutWithoutAwait { +pub struct CancelScopeNoCheckpoint { method_name: MethodName, } -impl Violation for TrioTimeoutWithoutAwait { +impl Violation for CancelScopeNoCheckpoint { #[derive_message_formats] fn message(&self) -> String { let Self { method_name } = self; @@ -45,15 +49,11 @@ impl Violation for TrioTimeoutWithoutAwait { } /// ASYNC100 -pub(crate) fn timeout_without_await( +pub(crate) fn cancel_scope_no_checkpoint( checker: &mut Checker, with_stmt: &StmtWith, with_items: &[WithItem], ) { - if !checker.semantic().seen_module(Modules::TRIO) { - return; - } - let Some(method_name) = with_items.iter().find_map(|item| { let call = item.context_expr.as_call_expr()?; let qualified_name = checker @@ -64,14 +64,7 @@ pub(crate) fn timeout_without_await( return; }; - if !matches!( - method_name, - MethodName::MoveOnAfter - | MethodName::MoveOnAt - | MethodName::FailAfter - | MethodName::FailAt - | MethodName::CancelScope - ) { + if !method_name.is_timeout_context() { return; } @@ -81,8 +74,17 @@ pub(crate) fn timeout_without_await( return; } - checker.diagnostics.push(Diagnostic::new( - TrioTimeoutWithoutAwait { method_name }, - with_stmt.range, - )); + if matches!(checker.settings.preview, PreviewMode::Disabled) { + if matches!(method_name.module(), AsyncModule::Trio) { + checker.diagnostics.push(Diagnostic::new( + CancelScopeNoCheckpoint { method_name }, + with_stmt.range, + )); + } + } else { + checker.diagnostics.push(Diagnostic::new( + CancelScopeNoCheckpoint { method_name }, + with_stmt.range, + )); + } } diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/mod.rs b/crates/ruff_linter/src/rules/flake8_async/rules/mod.rs index 1a1950c21a72c..54ab5c2dc0541 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/mod.rs @@ -3,9 +3,9 @@ pub(crate) use blocking_http_call::*; pub(crate) use blocking_open_call::*; pub(crate) use blocking_process_invocation::*; pub(crate) use blocking_sleep::*; +pub(crate) use cancel_scope_no_checkpoint::*; pub(crate) use sleep_forever_call::*; pub(crate) use sync_call::*; -pub(crate) use timeout_without_await::*; pub(crate) use unneeded_sleep::*; pub(crate) use zero_sleep_call::*; @@ -14,8 +14,8 @@ mod blocking_http_call; mod blocking_open_call; mod blocking_process_invocation; mod blocking_sleep; +mod cancel_scope_no_checkpoint; mod sleep_forever_call; mod sync_call; -mod timeout_without_await; mod unneeded_sleep; mod zero_sleep_call; diff --git a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC100_ASYNC100.py.snap b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC100_ASYNC100.py.snap index fe22d6a3c34ab..f4bddeb95c02c 100644 --- a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC100_ASYNC100.py.snap +++ b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__ASYNC100_ASYNC100.py.snap @@ -1,20 +1,20 @@ --- source: crates/ruff_linter/src/rules/flake8_async/mod.rs --- -ASYNC100.py:5:5: ASYNC100 A `with trio.fail_after(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint. +ASYNC100.py:7:5: ASYNC100 A `with trio.fail_after(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint. | -4 | async def func(): -5 | with trio.fail_after(): +6 | async def func(): +7 | with trio.fail_after(): | _____^ -6 | | ... +8 | | ... | |___________^ ASYNC100 | -ASYNC100.py:15:5: ASYNC100 A `with trio.move_on_after(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint. +ASYNC100.py:17:5: ASYNC100 A `with trio.move_on_after(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint. | -14 | async def func(): -15 | with trio.move_on_after(): +16 | async def func(): +17 | with trio.move_on_after(): | _____^ -16 | | ... +18 | | ... | |___________^ ASYNC100 | diff --git a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__preview__ASYNC100_ASYNC100.py.snap b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__preview__ASYNC100_ASYNC100.py.snap new file mode 100644 index 0000000000000..a805c2c3e3b18 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__preview__ASYNC100_ASYNC100.py.snap @@ -0,0 +1,74 @@ +--- +source: crates/ruff_linter/src/rules/flake8_async/mod.rs +--- +ASYNC100.py:7:5: ASYNC100 A `with trio.fail_after(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint. + | +6 | async def func(): +7 | with trio.fail_after(): + | _____^ +8 | | ... + | |___________^ ASYNC100 + | + +ASYNC100.py:17:5: ASYNC100 A `with trio.move_on_after(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint. + | +16 | async def func(): +17 | with trio.move_on_after(): + | _____^ +18 | | ... + | |___________^ ASYNC100 + | + +ASYNC100.py:33:5: ASYNC100 A `with anyio.move_on_after(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint. + | +32 | async def func(): +33 | with anyio.move_on_after(): + | _____^ +34 | | ... + | |___________^ ASYNC100 + | + +ASYNC100.py:38:5: ASYNC100 A `with anyio.fail_after(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint. + | +37 | async def func(): +38 | with anyio.fail_after(): + | _____^ +39 | | ... + | |___________^ ASYNC100 + | + +ASYNC100.py:43:5: ASYNC100 A `with anyio.CancelScope(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint. + | +42 | async def func(): +43 | with anyio.CancelScope(): + | _____^ +44 | | ... + | |___________^ ASYNC100 + | + +ASYNC100.py:48:5: ASYNC100 A `with anyio.CancelScope(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint. + | +47 | async def func(): +48 | with anyio.CancelScope(): + | _____^ +49 | | ... + | |___________^ ASYNC100 + | + +ASYNC100.py:53:5: ASYNC100 A `with asyncio.timeout(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint. + | +52 | async def func(): +53 | with asyncio.timeout(): + | _____^ +54 | | ... + | |___________^ ASYNC100 + | + +ASYNC100.py:58:5: ASYNC100 A `with asyncio.timeout_at(...):` context does not contain any `await` statements. This makes it pointless, as the timeout can only be triggered by a checkpoint. + | +57 | async def func(): +58 | with asyncio.timeout_at(): + | _____^ +59 | | ... + | |___________^ ASYNC100 + |