From 0a6d1ac73db639e59bc5899c38ebff1adb774c68 Mon Sep 17 00:00:00 2001 From: augustelalande Date: Sun, 7 Jul 2024 02:04:11 -0400 Subject: [PATCH 1/3] update async100 to match upstream --- .../test/fixtures/flake8_async/ASYNC100.py | 32 +++ .../src/checkers/ast/analyze/statement.rs | 4 +- crates/ruff_linter/src/codes.rs | 2 +- .../src/rules/flake8_async/helpers.rs | 259 ++++++++++-------- .../ruff_linter/src/rules/flake8_async/mod.rs | 2 +- ...await.rs => cancel_scope_no_checkpoint.rs} | 36 +-- .../src/rules/flake8_async/rules/mod.rs | 4 +- ...e8_async__tests__ASYNC100_ASYNC100.py.snap | 70 ++++- 8 files changed, 261 insertions(+), 148 deletions(-) rename crates/ruff_linter/src/rules/flake8_async/rules/{timeout_without_await.rs => cancel_scope_no_checkpoint.rs} (68%) 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 e92fee7e8d45e..925cf84733b8c 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs @@ -1308,8 +1308,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 69449360e34cc..add75fdcabd11 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::TrioAsyncFunctionWithTimeout), (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 1eabbc0548bd0..52baf7e773a88 100644 --- a/crates/ruff_linter/src/rules/flake8_async/helpers.rs +++ b/crates/ruff_linter/src/rules/flake8_async/helpers.rs @@ -2,69 +2,88 @@ use ruff_python_ast::name::QualifiedName; #[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, + 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 => true, - MethodName::MoveOnAfter - | MethodName::MoveOnAt - | MethodName::FailAfter - | MethodName::FailAt - | MethodName::CancelScope => false, + _ => false, + } + } + + /// Returns `true` if the method a timeout context manager. + pub(super) fn is_timeout_context(self) -> bool { + match self { + MethodName::AsyncIOTimeout + | MethodName::AsyncIOTimeoutAt + | MethodName::AnyIOMoveOnAfter + | MethodName::AnyIOFailAfter + | MethodName::AnyIOCancelScope + | MethodName::TrioMoveOnAfter + | MethodName::TrioMoveOnAt + | MethodName::TrioFailAfter + | MethodName::TrioFailAt + | MethodName::TrioCancelScope => true, + + _ => false, } } } @@ -72,42 +91,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, } } @@ -116,42 +142,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 70092042479a8..9e66fe0a95514 100644 --- a/crates/ruff_linter/src/rules/flake8_async/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_async/mod.rs @@ -14,7 +14,7 @@ mod tests { use crate::settings::LinterSettings; use crate::test::test_path; - #[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::TrioAsyncFunctionWithTimeout, Path::new("ASYNC109.py"))] #[test_case(Rule::TrioUnneededSleep, Path::new("ASYNC110.py"))] 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 68% 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..88c3a4c249cbd 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,43 @@ 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; /// ## 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 +48,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 +63,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; } @@ -82,7 +74,7 @@ pub(crate) fn timeout_without_await( } checker.diagnostics.push(Diagnostic::new( - TrioTimeoutWithoutAwait { method_name }, + 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..a805c2c3e3b18 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,74 @@ --- 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 + | + +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 | From 9df9e73eee6eb4f3ff6b71a49c1ff3cb6806ccc6 Mon Sep 17 00:00:00 2001 From: augustelalande Date: Sun, 7 Jul 2024 02:13:29 -0400 Subject: [PATCH 2/3] clippy --- .../src/rules/flake8_async/helpers.rs | 76 +++++++++---------- 1 file changed, 37 insertions(+), 39 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_async/helpers.rs b/crates/ruff_linter/src/rules/flake8_async/helpers.rs index 52baf7e773a88..f46da51aedee7 100644 --- a/crates/ruff_linter/src/rules/flake8_async/helpers.rs +++ b/crates/ruff_linter/src/rules/flake8_async/helpers.rs @@ -40,51 +40,49 @@ pub(super) enum MethodName { impl MethodName { /// Returns `true` if the method is async, `false` if it is sync. pub(super) fn is_async(self) -> bool { - match self { + 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 => true, - - _ => false, - } + | 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 { - match self { + matches!( + self, MethodName::AsyncIOTimeout - | MethodName::AsyncIOTimeoutAt - | MethodName::AnyIOMoveOnAfter - | MethodName::AnyIOFailAfter - | MethodName::AnyIOCancelScope - | MethodName::TrioMoveOnAfter - | MethodName::TrioMoveOnAt - | MethodName::TrioFailAfter - | MethodName::TrioFailAt - | MethodName::TrioCancelScope => true, - - _ => false, - } + | MethodName::AsyncIOTimeoutAt + | MethodName::AnyIOMoveOnAfter + | MethodName::AnyIOFailAfter + | MethodName::AnyIOCancelScope + | MethodName::TrioMoveOnAfter + | MethodName::TrioMoveOnAt + | MethodName::TrioFailAfter + | MethodName::TrioFailAt + | MethodName::TrioCancelScope + ) } } From 13d453f0ba5aa90816e7ba678ef8af8f486ca17a Mon Sep 17 00:00:00 2001 From: augustelalande Date: Tue, 9 Jul 2024 13:51:11 -0400 Subject: [PATCH 3/3] gate changes behind preview --- .../src/rules/flake8_async/helpers.rs | 78 ++++++++++++++----- .../ruff_linter/src/rules/flake8_async/mod.rs | 1 + .../rules/cancel_scope_no_checkpoint.rs | 26 +++++-- ...e8_async__tests__ASYNC100_ASYNC100.py.snap | 54 ------------- ..._tests__preview__ASYNC100_ASYNC100.py.snap | 74 ++++++++++++++++++ 5 files changed, 151 insertions(+), 82 deletions(-) create mode 100644 crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__preview__ASYNC100_ASYNC100.py.snap diff --git a/crates/ruff_linter/src/rules/flake8_async/helpers.rs b/crates/ruff_linter/src/rules/flake8_async/helpers.rs index 3de646b7b5c9d..b726d8fda8b84 100644 --- a/crates/ruff_linter/src/rules/flake8_async/helpers.rs +++ b/crates/ruff_linter/src/rules/flake8_async/helpers.rs @@ -12,11 +12,11 @@ pub(super) enum AsyncModule { #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub(super) enum MethodName { - AsyncIOTimeout, - AsyncIOTimeoutAt, - AnyIOMoveOnAfter, - AnyIOFailAfter, - AnyIOCancelScope, + AsyncIoTimeout, + AsyncIoTimeoutAt, + AnyIoMoveOnAfter, + AnyIoFailAfter, + AnyIoCancelScope, TrioAcloseForcefully, TrioCancelScope, TrioCancelShieldedCheckpoint, @@ -82,11 +82,11 @@ impl MethodName { pub(super) fn is_timeout_context(self) -> bool { matches!( self, - MethodName::AsyncIOTimeout - | MethodName::AsyncIOTimeoutAt - | MethodName::AnyIOMoveOnAfter - | MethodName::AnyIOFailAfter - | MethodName::AnyIOCancelScope + MethodName::AsyncIoTimeout + | MethodName::AsyncIoTimeoutAt + | MethodName::AnyIoMoveOnAfter + | MethodName::AnyIoFailAfter + | MethodName::AnyIoCancelScope | MethodName::TrioMoveOnAfter | MethodName::TrioMoveOnAt | MethodName::TrioFailAfter @@ -94,16 +94,54 @@ impl MethodName { | MethodName::TrioCancelScope ) } + + /// 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, + } + } } impl MethodName { pub(super) fn try_from(qualified_name: &QualifiedName<'_>) -> Option { match qualified_name.segments() { - ["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), + ["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), @@ -150,11 +188,11 @@ impl MethodName { impl std::fmt::Display for MethodName { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - 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::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 => { diff --git a/crates/ruff_linter/src/rules/flake8_async/mod.rs b/crates/ruff_linter/src/rules/flake8_async/mod.rs index 251941b9df5a3..2cff08d959414 100644 --- a/crates/ruff_linter/src/rules/flake8_async/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_async/mod.rs @@ -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/cancel_scope_no_checkpoint.rs b/crates/ruff_linter/src/rules/flake8_async/rules/cancel_scope_no_checkpoint.rs index 88c3a4c249cbd..8495bd90cbaf5 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/cancel_scope_no_checkpoint.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/cancel_scope_no_checkpoint.rs @@ -5,7 +5,8 @@ use ruff_python_ast::visitor::Visitor; use ruff_python_ast::{StmtWith, WithItem}; 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 timeout context managers which do not contain a checkpoint. @@ -31,9 +32,9 @@ use crate::rules::flake8_async::helpers::MethodName; /// 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 +/// [`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 CancelScopeNoCheckpoint { method_name: MethodName, @@ -73,8 +74,17 @@ pub(crate) fn cancel_scope_no_checkpoint( return; } - checker.diagnostics.push(Diagnostic::new( - CancelScopeNoCheckpoint { 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/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 a805c2c3e3b18..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 @@ -18,57 +18,3 @@ ASYNC100.py:17:5: ASYNC100 A `with trio.move_on_after(...):` context does not co 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 - | 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 + |