From 652f279b480f17b6630749e9aa60f7d7cb26755d Mon Sep 17 00:00:00 2001 From: augustelalande Date: Tue, 9 Jul 2024 22:22:23 -0400 Subject: [PATCH 1/3] update async115 to match upstream --- .../test/fixtures/flake8_async/ASYNC115.py | 69 +++++ .../src/checkers/ast/analyze/expression.rs | 4 +- crates/ruff_linter/src/codes.rs | 2 +- .../ruff_linter/src/rules/flake8_async/mod.rs | 3 +- .../flake8_async/rules/async_zero_sleep.rs | 118 +++++++++ .../src/rules/flake8_async/rules/mod.rs | 4 +- .../flake8_async/rules/zero_sleep_call.rs | 92 ------- ..._tests__preview__ASYNC115_ASYNC115.py.snap | 248 ++++++++++++++++++ 8 files changed, 442 insertions(+), 98 deletions(-) create mode 100644 crates/ruff_linter/src/rules/flake8_async/rules/async_zero_sleep.rs delete mode 100644 crates/ruff_linter/src/rules/flake8_async/rules/zero_sleep_call.rs create mode 100644 crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__preview__ASYNC115_ASYNC115.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC115.py b/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC115.py index fd4f42d156e60..235a64f053674 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC115.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC115.py @@ -76,3 +76,72 @@ async def main() -> None: sleep = 10 trio.run(main) + + +async def func(): + import anyio + from anyio import sleep + + await anyio.sleep(0) # ASYNC115 + await anyio.sleep(1) # OK + await anyio.sleep(0, 1) # OK + await anyio.sleep(...) # OK + await anyio.sleep() # OK + + anyio.sleep(0) # ASYNC115 + foo = 0 + anyio.sleep(foo) # OK + anyio.sleep(1) # OK + time.sleep(0) # OK + + sleep(0) # ASYNC115 + + bar = "bar" + anyio.sleep(bar) + + x, y = 0, 2000 + anyio.sleep(x) # OK + anyio.sleep(y) # OK + + (a, b, [c, (d, e)]) = (1, 2, (0, [4, 0])) + anyio.sleep(c) # OK + anyio.sleep(d) # OK + anyio.sleep(e) # OK + + m_x, m_y = 0 + anyio.sleep(m_y) # OK + anyio.sleep(m_x) # OK + + m_a = m_b = 0 + anyio.sleep(m_a) # OK + anyio.sleep(m_b) # OK + + m_c = (m_d, m_e) = (0, 0) + anyio.sleep(m_c) # OK + anyio.sleep(m_d) # OK + anyio.sleep(m_e) # OK + + +def func(): + import anyio + + anyio.run(anyio.sleep(0)) # ASYNC115 + + +def func(): + import anyio + + if (walrus := 0) == 0: + anyio.sleep(walrus) # OK + + +def func(): + import anyio + + async def main() -> None: + sleep = 0 + for _ in range(2): + await anyio.sleep(sleep) # OK + sleep = 10 + + anyio.run(main) diff --git a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs index 5e362d96ae2bc..d6a9214a2fa49 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs @@ -975,8 +975,8 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { if checker.enabled(Rule::TrioSyncCall) { flake8_async::rules::sync_call(checker, call); } - if checker.enabled(Rule::TrioZeroSleepCall) { - flake8_async::rules::zero_sleep_call(checker, call); + if checker.enabled(Rule::AsyncZeroSleep) { + flake8_async::rules::async_zero_sleep(checker, call); } if checker.enabled(Rule::UnnecessaryDunderCall) { pylint::rules::unnecessary_dunder_call(checker, call); diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index 45e92b40e370a..64b5b453bd80e 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -297,7 +297,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (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::AsyncBusyWait), - (Flake8Async, "115") => (RuleGroup::Stable, rules::flake8_async::rules::TrioZeroSleepCall), + (Flake8Async, "115") => (RuleGroup::Stable, rules::flake8_async::rules::AsyncZeroSleep), (Flake8Async, "116") => (RuleGroup::Preview, rules::flake8_async::rules::SleepForeverCall), (Flake8Async, "210") => (RuleGroup::Stable, rules::flake8_async::rules::BlockingHttpCallInAsyncFunction), (Flake8Async, "220") => (RuleGroup::Stable, rules::flake8_async::rules::CreateSubprocessInAsyncFunction), diff --git a/crates/ruff_linter/src/rules/flake8_async/mod.rs b/crates/ruff_linter/src/rules/flake8_async/mod.rs index bb74a7764a799..07c12792cbadd 100644 --- a/crates/ruff_linter/src/rules/flake8_async/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_async/mod.rs @@ -20,7 +20,7 @@ mod tests { #[test_case(Rule::AsyncFunctionWithTimeout, Path::new("ASYNC109_0.py"))] #[test_case(Rule::AsyncFunctionWithTimeout, Path::new("ASYNC109_1.py"))] #[test_case(Rule::AsyncBusyWait, Path::new("ASYNC110.py"))] - #[test_case(Rule::TrioZeroSleepCall, Path::new("ASYNC115.py"))] + #[test_case(Rule::AsyncZeroSleep, Path::new("ASYNC115.py"))] #[test_case(Rule::SleepForeverCall, Path::new("ASYNC116.py"))] #[test_case(Rule::BlockingHttpCallInAsyncFunction, Path::new("ASYNC210.py"))] #[test_case(Rule::CreateSubprocessInAsyncFunction, Path::new("ASYNC22x.py"))] @@ -42,6 +42,7 @@ mod tests { #[test_case(Rule::AsyncFunctionWithTimeout, Path::new("ASYNC109_0.py"))] #[test_case(Rule::AsyncFunctionWithTimeout, Path::new("ASYNC109_1.py"))] #[test_case(Rule::AsyncBusyWait, Path::new("ASYNC110.py"))] + #[test_case(Rule::AsyncZeroSleep, Path::new("ASYNC115.py"))] fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!( "preview__{}_{}", diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/async_zero_sleep.rs b/crates/ruff_linter/src/rules/flake8_async/rules/async_zero_sleep.rs new file mode 100644 index 0000000000000..e3ddb7b740403 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_async/rules/async_zero_sleep.rs @@ -0,0 +1,118 @@ +use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; +use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::{self as ast, Expr, ExprCall, Int, Number}; +use ruff_text_size::Ranged; + +use crate::checkers::ast::Checker; +use crate::importer::ImportRequest; +use crate::rules::flake8_async::helpers::AsyncModule; +use crate::settings::types::PreviewMode; + +/// ## What it does +/// Checks for uses of `trio.sleep(0)` or `anyio.sleep(0)`. +/// +/// ## Why is this bad? +/// `trio.sleep(0)` is equivalent to calling `trio.lowlevel.checkpoint()`. +/// However, the latter better conveys the intent of the code. +/// +/// ## Example +/// ```python +/// import trio +/// +/// +/// async def func(): +/// await trio.sleep(0) +/// ``` +/// +/// Use instead: +/// ```python +/// import trio +/// +/// +/// async def func(): +/// await trio.lowlevel.checkpoint() +/// ``` +#[violation] +pub struct AsyncZeroSleep { + module: AsyncModule, +} + +impl AlwaysFixableViolation for AsyncZeroSleep { + #[derive_message_formats] + fn message(&self) -> String { + let Self { module } = self; + format!("Use `{module}.lowlevel.checkpoint()` instead of `{module}.sleep(0)`") + } + + fn fix_title(&self) -> String { + let Self { module } = self; + format!("Replace with `{module}.lowlevel.checkpoint()`") + } +} + +/// ASYNC115 +pub(crate) fn async_zero_sleep(checker: &mut Checker, call: &ExprCall) { + if call.arguments.len() != 1 { + return; + } + + let Some(arg) = call.arguments.find_argument("seconds", 0) else { + return; + }; + + match arg { + Expr::NumberLiteral(ast::ExprNumberLiteral { value, .. }) => { + if !matches!(value, Number::Int(Int::ZERO)) { + return; + } + } + _ => return, + } + + let Some(qualified_name) = checker + .semantic() + .resolve_qualified_name(call.func.as_ref()) + else { + return; + }; + + if matches!(checker.settings.preview, PreviewMode::Disabled) { + if matches!(qualified_name.segments(), ["trio", "sleep"]) { + let mut diagnostic = Diagnostic::new( + AsyncZeroSleep { + module: AsyncModule::Trio, + }, + call.range(), + ); + diagnostic.try_set_fix(|| { + let (import_edit, binding) = checker.importer().get_or_import_symbol( + &ImportRequest::import_from("trio", "lowlevel"), + call.func.start(), + checker.semantic(), + )?; + let reference_edit = + Edit::range_replacement(format!("{binding}.checkpoint"), call.func.range()); + let arg_edit = Edit::range_replacement("()".to_string(), call.arguments.range()); + Ok(Fix::safe_edits(import_edit, [reference_edit, arg_edit])) + }); + checker.diagnostics.push(diagnostic); + } + } else { + if matches!(qualified_name.segments(), ["trio" | "anyio", "sleep"]) { + let module = AsyncModule::try_from(&qualified_name).unwrap(); + let mut diagnostic = Diagnostic::new(AsyncZeroSleep { module }, call.range()); + diagnostic.try_set_fix(|| { + let (import_edit, binding) = checker.importer().get_or_import_symbol( + &ImportRequest::import_from(&module.to_string(), "lowlevel"), + call.func.start(), + checker.semantic(), + )?; + let reference_edit = + Edit::range_replacement(format!("{binding}.checkpoint"), call.func.range()); + let arg_edit = Edit::range_replacement("()".to_string(), call.arguments.range()); + Ok(Fix::safe_edits(import_edit, [reference_edit, arg_edit])) + }); + checker.diagnostics.push(diagnostic); + } + } +} 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 f3af8a8dc1ce3..6937c4e5b10ed 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/mod.rs @@ -1,5 +1,6 @@ pub(crate) use async_busy_wait::*; pub(crate) use async_function_with_timeout::*; +pub(crate) use async_zero_sleep::*; pub(crate) use blocking_http_call::*; pub(crate) use blocking_open_call::*; pub(crate) use blocking_process_invocation::*; @@ -7,10 +8,10 @@ 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 zero_sleep_call::*; mod async_busy_wait; mod async_function_with_timeout; +mod async_zero_sleep; mod blocking_http_call; mod blocking_open_call; mod blocking_process_invocation; @@ -18,4 +19,3 @@ mod blocking_sleep; mod cancel_scope_no_checkpoint; mod sleep_forever_call; mod sync_call; -mod zero_sleep_call; diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/zero_sleep_call.rs b/crates/ruff_linter/src/rules/flake8_async/rules/zero_sleep_call.rs deleted file mode 100644 index f1d23f618e289..0000000000000 --- a/crates/ruff_linter/src/rules/flake8_async/rules/zero_sleep_call.rs +++ /dev/null @@ -1,92 +0,0 @@ -use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; -use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::{self as ast, Expr, ExprCall, Int, Number}; -use ruff_python_semantic::Modules; -use ruff_text_size::Ranged; - -use crate::checkers::ast::Checker; -use crate::importer::ImportRequest; - -/// ## What it does -/// Checks for uses of `trio.sleep(0)`. -/// -/// ## Why is this bad? -/// `trio.sleep(0)` is equivalent to calling `trio.lowlevel.checkpoint()`. -/// However, the latter better conveys the intent of the code. -/// -/// ## Example -/// ```python -/// import trio -/// -/// -/// async def func(): -/// await trio.sleep(0) -/// ``` -/// -/// Use instead: -/// ```python -/// import trio -/// -/// -/// async def func(): -/// await trio.lowlevel.checkpoint() -/// ``` -#[violation] -pub struct TrioZeroSleepCall; - -impl AlwaysFixableViolation for TrioZeroSleepCall { - #[derive_message_formats] - fn message(&self) -> String { - format!("Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)`") - } - - fn fix_title(&self) -> String { - format!("Replace with `trio.lowlevel.checkpoint()`") - } -} - -/// ASYNC115 -pub(crate) fn zero_sleep_call(checker: &mut Checker, call: &ExprCall) { - if !checker.semantic().seen_module(Modules::TRIO) { - return; - } - - if call.arguments.len() != 1 { - return; - } - - let Some(arg) = call.arguments.find_argument("seconds", 0) else { - return; - }; - - if !checker - .semantic() - .resolve_qualified_name(call.func.as_ref()) - .is_some_and(|qualified_name| matches!(qualified_name.segments(), ["trio", "sleep"])) - { - return; - } - - match arg { - Expr::NumberLiteral(ast::ExprNumberLiteral { value, .. }) => { - if !matches!(value, Number::Int(Int::ZERO)) { - return; - } - } - _ => return, - } - - let mut diagnostic = Diagnostic::new(TrioZeroSleepCall, call.range()); - diagnostic.try_set_fix(|| { - let (import_edit, binding) = checker.importer().get_or_import_symbol( - &ImportRequest::import_from("trio", "lowlevel"), - call.func.start(), - checker.semantic(), - )?; - let reference_edit = - Edit::range_replacement(format!("{binding}.checkpoint"), call.func.range()); - let arg_edit = Edit::range_replacement("()".to_string(), call.arguments.range()); - Ok(Fix::safe_edits(import_edit, [reference_edit, arg_edit])) - }); - checker.diagnostics.push(diagnostic); -} diff --git a/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__preview__ASYNC115_ASYNC115.py.snap b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__preview__ASYNC115_ASYNC115.py.snap new file mode 100644 index 0000000000000..3e40da955dd3e --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_async/snapshots/ruff_linter__rules__flake8_async__tests__preview__ASYNC115_ASYNC115.py.snap @@ -0,0 +1,248 @@ +--- +source: crates/ruff_linter/src/rules/flake8_async/mod.rs +--- +ASYNC115.py:5:11: ASYNC115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)` + | +3 | from trio import sleep +4 | +5 | await trio.sleep(0) # ASYNC115 + | ^^^^^^^^^^^^^ ASYNC115 +6 | await trio.sleep(1) # OK +7 | await trio.sleep(0, 1) # OK + | + = help: Replace with `trio.lowlevel.checkpoint()` + +ℹ Safe fix +2 2 | import trio +3 3 | from trio import sleep +4 4 | +5 |- await trio.sleep(0) # ASYNC115 + 5 |+ await trio.lowlevel.checkpoint() # ASYNC115 +6 6 | await trio.sleep(1) # OK +7 7 | await trio.sleep(0, 1) # OK +8 8 | await trio.sleep(...) # OK + +ASYNC115.py:11:5: ASYNC115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)` + | + 9 | await trio.sleep() # OK +10 | +11 | trio.sleep(0) # ASYNC115 + | ^^^^^^^^^^^^^ ASYNC115 +12 | foo = 0 +13 | trio.sleep(foo) # OK + | + = help: Replace with `trio.lowlevel.checkpoint()` + +ℹ Safe fix +8 8 | await trio.sleep(...) # OK +9 9 | await trio.sleep() # OK +10 10 | +11 |- trio.sleep(0) # ASYNC115 + 11 |+ trio.lowlevel.checkpoint() # ASYNC115 +12 12 | foo = 0 +13 13 | trio.sleep(foo) # OK +14 14 | trio.sleep(1) # OK + +ASYNC115.py:17:5: ASYNC115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)` + | +15 | time.sleep(0) # OK +16 | +17 | sleep(0) # ASYNC115 + | ^^^^^^^^ ASYNC115 +18 | +19 | bar = "bar" + | + = help: Replace with `trio.lowlevel.checkpoint()` + +ℹ Safe fix +14 14 | trio.sleep(1) # OK +15 15 | time.sleep(0) # OK +16 16 | +17 |- sleep(0) # ASYNC115 + 17 |+ trio.lowlevel.checkpoint() # ASYNC115 +18 18 | +19 19 | bar = "bar" +20 20 | trio.sleep(bar) + +ASYNC115.py:48:14: ASYNC115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)` + | +46 | import trio +47 | +48 | trio.run(trio.sleep(0)) # ASYNC115 + | ^^^^^^^^^^^^^ ASYNC115 + | + = help: Replace with `trio.lowlevel.checkpoint()` + +ℹ Safe fix +45 45 | def func(): +46 46 | import trio +47 47 | +48 |- trio.run(trio.sleep(0)) # ASYNC115 + 48 |+ trio.run(trio.lowlevel.checkpoint()) # ASYNC115 +49 49 | +50 50 | +51 51 | from trio import Event, sleep + +ASYNC115.py:55:5: ASYNC115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)` + | +54 | def func(): +55 | sleep(0) # ASYNC115 + | ^^^^^^^^ ASYNC115 + | + = help: Replace with `trio.lowlevel.checkpoint()` + +ℹ Safe fix +48 48 | trio.run(trio.sleep(0)) # ASYNC115 +49 49 | +50 50 | +51 |-from trio import Event, sleep + 51 |+from trio import Event, sleep, lowlevel +52 52 | +53 53 | +54 54 | def func(): +55 |- sleep(0) # ASYNC115 + 55 |+ lowlevel.checkpoint() # ASYNC115 +56 56 | +57 57 | +58 58 | async def func(): + +ASYNC115.py:59:11: ASYNC115 [*] Use `trio.lowlevel.checkpoint()` instead of `trio.sleep(0)` + | +58 | async def func(): +59 | await sleep(seconds=0) # ASYNC115 + | ^^^^^^^^^^^^^^^^ ASYNC115 + | + = help: Replace with `trio.lowlevel.checkpoint()` + +ℹ Safe fix +48 48 | trio.run(trio.sleep(0)) # ASYNC115 +49 49 | +50 50 | +51 |-from trio import Event, sleep + 51 |+from trio import Event, sleep, lowlevel +52 52 | +53 53 | +54 54 | def func(): +-------------------------------------------------------------------------------- +56 56 | +57 57 | +58 58 | async def func(): +59 |- await sleep(seconds=0) # ASYNC115 + 59 |+ await lowlevel.checkpoint() # ASYNC115 +60 60 | +61 61 | +62 62 | def func(): + +ASYNC115.py:85:11: ASYNC115 [*] Use `asyncio.lowlevel.checkpoint()` instead of `asyncio.sleep(0)` + | +83 | from anyio import sleep +84 | +85 | await anyio.sleep(0) # ASYNC115 + | ^^^^^^^^^^^^^^ ASYNC115 +86 | await anyio.sleep(1) # OK +87 | await anyio.sleep(0, 1) # OK + | + = help: Replace with `asyncio.lowlevel.checkpoint()` + +ℹ Safe fix +49 49 | +50 50 | +51 51 | from trio import Event, sleep + 52 |+from asyncio import lowlevel +52 53 | +53 54 | +54 55 | def func(): +-------------------------------------------------------------------------------- +82 83 | import anyio +83 84 | from anyio import sleep +84 85 | +85 |- await anyio.sleep(0) # ASYNC115 + 86 |+ await lowlevel.checkpoint() # ASYNC115 +86 87 | await anyio.sleep(1) # OK +87 88 | await anyio.sleep(0, 1) # OK +88 89 | await anyio.sleep(...) # OK + +ASYNC115.py:91:5: ASYNC115 [*] Use `asyncio.lowlevel.checkpoint()` instead of `asyncio.sleep(0)` + | +89 | await anyio.sleep() # OK +90 | +91 | anyio.sleep(0) # ASYNC115 + | ^^^^^^^^^^^^^^ ASYNC115 +92 | foo = 0 +93 | anyio.sleep(foo) # OK + | + = help: Replace with `asyncio.lowlevel.checkpoint()` + +ℹ Safe fix +49 49 | +50 50 | +51 51 | from trio import Event, sleep + 52 |+from asyncio import lowlevel +52 53 | +53 54 | +54 55 | def func(): +-------------------------------------------------------------------------------- +88 89 | await anyio.sleep(...) # OK +89 90 | await anyio.sleep() # OK +90 91 | +91 |- anyio.sleep(0) # ASYNC115 + 92 |+ lowlevel.checkpoint() # ASYNC115 +92 93 | foo = 0 +93 94 | anyio.sleep(foo) # OK +94 95 | anyio.sleep(1) # OK + +ASYNC115.py:97:5: ASYNC115 [*] Use `asyncio.lowlevel.checkpoint()` instead of `asyncio.sleep(0)` + | +95 | time.sleep(0) # OK +96 | +97 | sleep(0) # ASYNC115 + | ^^^^^^^^ ASYNC115 +98 | +99 | bar = "bar" + | + = help: Replace with `asyncio.lowlevel.checkpoint()` + +ℹ Safe fix +49 49 | +50 50 | +51 51 | from trio import Event, sleep + 52 |+from asyncio import lowlevel +52 53 | +53 54 | +54 55 | def func(): +-------------------------------------------------------------------------------- +94 95 | anyio.sleep(1) # OK +95 96 | time.sleep(0) # OK +96 97 | +97 |- sleep(0) # ASYNC115 + 98 |+ lowlevel.checkpoint() # ASYNC115 +98 99 | +99 100 | bar = "bar" +100 101 | anyio.sleep(bar) + +ASYNC115.py:128:15: ASYNC115 [*] Use `asyncio.lowlevel.checkpoint()` instead of `asyncio.sleep(0)` + | +126 | import anyio +127 | +128 | anyio.run(anyio.sleep(0)) # ASYNC115 + | ^^^^^^^^^^^^^^ ASYNC115 + | + = help: Replace with `asyncio.lowlevel.checkpoint()` + +ℹ Safe fix +49 49 | +50 50 | +51 51 | from trio import Event, sleep + 52 |+from asyncio import lowlevel +52 53 | +53 54 | +54 55 | def func(): +-------------------------------------------------------------------------------- +125 126 | def func(): +126 127 | import anyio +127 128 | +128 |- anyio.run(anyio.sleep(0)) # ASYNC115 + 129 |+ anyio.run(lowlevel.checkpoint()) # ASYNC115 +129 130 | +130 131 | +131 132 | def func(): From cdb2eaa0c33f01d241408947615e0e8a513dabec Mon Sep 17 00:00:00 2001 From: augustelalande Date: Tue, 9 Jul 2024 22:33:44 -0400 Subject: [PATCH 2/3] address regression --- .../src/rules/flake8_async/rules/async_zero_sleep.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/async_zero_sleep.rs b/crates/ruff_linter/src/rules/flake8_async/rules/async_zero_sleep.rs index e3ddb7b740403..906ea5fe0042e 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/async_zero_sleep.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/async_zero_sleep.rs @@ -1,6 +1,7 @@ use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{self as ast, Expr, ExprCall, Int, Number}; +use ruff_python_semantic::Modules; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -52,6 +53,12 @@ impl AlwaysFixableViolation for AsyncZeroSleep { /// ASYNC115 pub(crate) fn async_zero_sleep(checker: &mut Checker, call: &ExprCall) { + if !(checker.semantic().seen_module(Modules::TRIO) + || checker.semantic().seen_module(Modules::ANYIO)) + { + return; + } + if call.arguments.len() != 1 { return; } From f0ec2303f99ae2e42878517596884565172aa278 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Wed, 10 Jul 2024 09:22:54 +0200 Subject: [PATCH 3/3] Remove `unwrap`, share more code between preview and non-preivew --- .../flake8_async/rules/async_zero_sleep.rs | 63 ++++++++----------- 1 file changed, 25 insertions(+), 38 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_async/rules/async_zero_sleep.rs b/crates/ruff_linter/src/rules/flake8_async/rules/async_zero_sleep.rs index 906ea5fe0042e..9f9ef57cbd7d6 100644 --- a/crates/ruff_linter/src/rules/flake8_async/rules/async_zero_sleep.rs +++ b/crates/ruff_linter/src/rules/flake8_async/rules/async_zero_sleep.rs @@ -7,7 +7,6 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::importer::ImportRequest; use crate::rules::flake8_async::helpers::AsyncModule; -use crate::settings::types::PreviewMode; /// ## What it does /// Checks for uses of `trio.sleep(0)` or `anyio.sleep(0)`. @@ -83,43 +82,31 @@ pub(crate) fn async_zero_sleep(checker: &mut Checker, call: &ExprCall) { return; }; - if matches!(checker.settings.preview, PreviewMode::Disabled) { - if matches!(qualified_name.segments(), ["trio", "sleep"]) { - let mut diagnostic = Diagnostic::new( - AsyncZeroSleep { - module: AsyncModule::Trio, - }, - call.range(), - ); - diagnostic.try_set_fix(|| { - let (import_edit, binding) = checker.importer().get_or_import_symbol( - &ImportRequest::import_from("trio", "lowlevel"), - call.func.start(), - checker.semantic(), - )?; - let reference_edit = - Edit::range_replacement(format!("{binding}.checkpoint"), call.func.range()); - let arg_edit = Edit::range_replacement("()".to_string(), call.arguments.range()); - Ok(Fix::safe_edits(import_edit, [reference_edit, arg_edit])) - }); - checker.diagnostics.push(diagnostic); - } - } else { - if matches!(qualified_name.segments(), ["trio" | "anyio", "sleep"]) { - let module = AsyncModule::try_from(&qualified_name).unwrap(); - let mut diagnostic = Diagnostic::new(AsyncZeroSleep { module }, call.range()); - diagnostic.try_set_fix(|| { - let (import_edit, binding) = checker.importer().get_or_import_symbol( - &ImportRequest::import_from(&module.to_string(), "lowlevel"), - call.func.start(), - checker.semantic(), - )?; - let reference_edit = - Edit::range_replacement(format!("{binding}.checkpoint"), call.func.range()); - let arg_edit = Edit::range_replacement("()".to_string(), call.arguments.range()); - Ok(Fix::safe_edits(import_edit, [reference_edit, arg_edit])) - }); - checker.diagnostics.push(diagnostic); + if let Some(module) = AsyncModule::try_from(&qualified_name) { + let is_relevant_module = if checker.settings.preview.is_enabled() { + matches!(module, AsyncModule::Trio | AsyncModule::AnyIo) + } else { + matches!(module, AsyncModule::Trio) + }; + + let is_sleep = is_relevant_module && matches!(qualified_name.segments(), [_, "sleep"]); + + if !is_sleep { + return; } + + let mut diagnostic = Diagnostic::new(AsyncZeroSleep { module }, call.range()); + diagnostic.try_set_fix(|| { + let (import_edit, binding) = checker.importer().get_or_import_symbol( + &ImportRequest::import_from(&module.to_string(), "lowlevel"), + call.func.start(), + checker.semantic(), + )?; + let reference_edit = + Edit::range_replacement(format!("{binding}.checkpoint"), call.func.range()); + let arg_edit = Edit::range_replacement("()".to_string(), call.arguments.range()); + Ok(Fix::safe_edits(import_edit, [reference_edit, arg_edit])) + }); + checker.diagnostics.push(diagnostic); } }