From ba78d36a8e1bc6ea2a586c9006ca1c03ee74fcef Mon Sep 17 00:00:00 2001 From: asafamr-mm Date: Fri, 8 Dec 2023 22:55:34 +0200 Subject: [PATCH 1/6] unused async dangling task --- .../resources/test/fixtures/ruff/RUF006.py | 14 +++++++- .../checkers/ast/analyze/deferred_scopes.rs | 9 ++++- .../src/checkers/ast/analyze/statement.rs | 4 ++- .../rules/ruff/rules/asyncio_dangling_task.rs | 34 ++++++++++++++++--- ..._rules__ruff__tests__RUF006_RUF006.py.snap | 16 ++++++--- 5 files changed, 66 insertions(+), 11 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF006.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF006.py index eedce2563153b..5611d32a4a640 100644 --- a/crates/ruff_linter/resources/test/fixtures/ruff/RUF006.py +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF006.py @@ -63,9 +63,21 @@ def f(): tasks = [asyncio.create_task(task) for task in tasks] -# OK (false negative) +# Error +def f(): + task = asyncio.create_task(coordinator.ws_connect()) + + +# OK def f(): task = asyncio.create_task(coordinator.ws_connect()) + background_tasks.add(task) + + +# OK +async def f(): + task = asyncio.create_task(coordinator.ws_connect()) + await task # OK (potential false negative) diff --git a/crates/ruff_linter/src/checkers/ast/analyze/deferred_scopes.rs b/crates/ruff_linter/src/checkers/ast/analyze/deferred_scopes.rs index 27c9a6b7c79e2..ff0820324ac8f 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/deferred_scopes.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/deferred_scopes.rs @@ -5,7 +5,9 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::codes::Rule; -use crate::rules::{flake8_pyi, flake8_type_checking, flake8_unused_arguments, pyflakes, pylint}; +use crate::rules::{ + flake8_pyi, flake8_type_checking, flake8_unused_arguments, pyflakes, pylint, ruff, +}; /// Run lint rules over all deferred scopes in the [`SemanticModel`]. pub(crate) fn deferred_scopes(checker: &mut Checker) { @@ -31,6 +33,7 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) { Rule::UnusedPrivateTypedDict, Rule::UnusedStaticMethodArgument, Rule::UnusedVariable, + Rule::AsyncioDanglingTask, Rule::NoSelfUse, ]) { return; @@ -294,6 +297,10 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) { } } + if checker.enabled(Rule::AsyncioDanglingTask) { + ruff::rules::asyncio_dangling_task_unused(checker, scope, &mut diagnostics); + } + if matches!(scope.kind, ScopeKind::Function(_) | ScopeKind::Module) { if enforce_typing_imports { let runtime_imports: Vec<&Binding> = checker diff --git a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs index 99ea30595f0a6..489b183a90b0a 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs @@ -1560,7 +1560,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { pylint::rules::named_expr_without_context(checker, value); } if checker.enabled(Rule::AsyncioDanglingTask) { - ruff::rules::asyncio_dangling_task(checker, value); + let mut diagnostics: Vec = vec![]; + ruff::rules::asyncio_dangling_task(checker, value, &mut diagnostics); + checker.diagnostics.extend(diagnostics) } if checker.enabled(Rule::RepeatedAppend) { refurb::rules::repeated_append(checker, stmt); diff --git a/crates/ruff_linter/src/rules/ruff/rules/asyncio_dangling_task.rs b/crates/ruff_linter/src/rules/ruff/rules/asyncio_dangling_task.rs index f6f03fc8d955e..c7f3be313e9ec 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/asyncio_dangling_task.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/asyncio_dangling_task.rs @@ -1,10 +1,11 @@ use std::fmt; +use ast::Stmt; use ruff_python_ast::{self as ast, Expr}; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_semantic::analyze::typing; +use ruff_python_semantic::{analyze::typing, Scope}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -66,7 +67,11 @@ impl Violation for AsyncioDanglingTask { } /// RUF006 -pub(crate) fn asyncio_dangling_task(checker: &mut Checker, expr: &Expr) { +pub(crate) fn asyncio_dangling_task( + checker: &Checker, + expr: &Expr, + diagnostics: &mut Vec, +) { let Expr::Call(ast::ExprCall { func, .. }) = expr else { return; }; @@ -81,7 +86,7 @@ pub(crate) fn asyncio_dangling_task(checker: &mut Checker, expr: &Expr) { _ => None, }) { - checker.diagnostics.push(Diagnostic::new( + diagnostics.push(Diagnostic::new( AsyncioDanglingTask { method }, expr.range(), )); @@ -94,7 +99,7 @@ pub(crate) fn asyncio_dangling_task(checker: &mut Checker, expr: &Expr) { if typing::resolve_assignment(value, checker.semantic()).is_some_and(|call_path| { matches!(call_path.as_slice(), ["asyncio", "get_running_loop"]) }) { - checker.diagnostics.push(Diagnostic::new( + diagnostics.push(Diagnostic::new( AsyncioDanglingTask { method: Method::CreateTask, }, @@ -105,6 +110,27 @@ pub(crate) fn asyncio_dangling_task(checker: &mut Checker, expr: &Expr) { } } +pub(crate) fn asyncio_dangling_task_unused( + checker: &Checker, + scope: &Scope, + diagnostics: &mut Vec, +) { + for binding in scope + .binding_ids() + .map(|binding_id| checker.semantic().binding(binding_id)) + .filter(|binding| binding.kind.is_assignment() && !binding.is_used()) + { + let Some(source) = binding.source else { + continue; + }; + let Stmt::Assign(ast::StmtAssign { value, .. }) = checker.semantic().statement(source) + else { + continue; + }; + asyncio_dangling_task(checker, value, diagnostics) + } +} + #[derive(Debug, PartialEq, Eq, Copy, Clone)] enum Method { CreateTask, diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF006_RUF006.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF006_RUF006.py.snap index 11a0bababe525..b6aebd1be81b1 100644 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF006_RUF006.py.snap +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF006_RUF006.py.snap @@ -17,11 +17,19 @@ RUF006.py:11:5: RUF006 Store a reference to the return value of `asyncio.ensure_ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF006 | -RUF006.py:79:5: RUF006 Store a reference to the return value of `asyncio.create_task` +RUF006.py:68:12: RUF006 Store a reference to the return value of `asyncio.create_task` | -77 | def f(): -78 | loop = asyncio.get_running_loop() -79 | loop.create_task(coordinator.ws_connect()) # Error +66 | # Error +67 | def f(): +68 | task = asyncio.create_task(coordinator.ws_connect()) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF006 + | + +RUF006.py:91:5: RUF006 Store a reference to the return value of `asyncio.create_task` + | +89 | def f(): +90 | loop = asyncio.get_running_loop() +91 | loop.create_task(coordinator.ws_connect()) # Error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF006 | From 78618a591125921dde40e1090905c59fe77ac620 Mon Sep 17 00:00:00 2001 From: asafamr-mm Date: Fri, 8 Dec 2023 23:12:00 +0200 Subject: [PATCH 2/6] fmt --- crates/ruff_linter/resources/test/fixtures/ruff/RUF006.py | 2 +- crates/ruff_linter/src/checkers/ast/analyze/deferred_scopes.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF006.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF006.py index 5611d32a4a640..9dee81bad3544 100644 --- a/crates/ruff_linter/resources/test/fixtures/ruff/RUF006.py +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF006.py @@ -68,7 +68,7 @@ def f(): task = asyncio.create_task(coordinator.ws_connect()) -# OK +# OK (potential false negative) def f(): task = asyncio.create_task(coordinator.ws_connect()) background_tasks.add(task) diff --git a/crates/ruff_linter/src/checkers/ast/analyze/deferred_scopes.rs b/crates/ruff_linter/src/checkers/ast/analyze/deferred_scopes.rs index ff0820324ac8f..5bd0ff05cb1ce 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/deferred_scopes.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/deferred_scopes.rs @@ -300,7 +300,7 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) { if checker.enabled(Rule::AsyncioDanglingTask) { ruff::rules::asyncio_dangling_task_unused(checker, scope, &mut diagnostics); } - + if matches!(scope.kind, ScopeKind::Function(_) | ScopeKind::Module) { if enforce_typing_imports { let runtime_imports: Vec<&Binding> = checker From bf22b07bc61c8426a17b36325a54dab7551011e9 Mon Sep 17 00:00:00 2001 From: asafamr-mm Date: Fri, 8 Dec 2023 23:12:45 +0200 Subject: [PATCH 3/6] lint --- crates/ruff_linter/src/checkers/ast/analyze/statement.rs | 2 +- .../ruff_linter/src/rules/ruff/rules/asyncio_dangling_task.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs index 489b183a90b0a..95bca39e70369 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs @@ -1562,7 +1562,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { if checker.enabled(Rule::AsyncioDanglingTask) { let mut diagnostics: Vec = vec![]; ruff::rules::asyncio_dangling_task(checker, value, &mut diagnostics); - checker.diagnostics.extend(diagnostics) + checker.diagnostics.extend(diagnostics); } if checker.enabled(Rule::RepeatedAppend) { refurb::rules::repeated_append(checker, stmt); diff --git a/crates/ruff_linter/src/rules/ruff/rules/asyncio_dangling_task.rs b/crates/ruff_linter/src/rules/ruff/rules/asyncio_dangling_task.rs index c7f3be313e9ec..7a5ca8d9448bf 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/asyncio_dangling_task.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/asyncio_dangling_task.rs @@ -127,7 +127,7 @@ pub(crate) fn asyncio_dangling_task_unused( else { continue; }; - asyncio_dangling_task(checker, value, diagnostics) + asyncio_dangling_task(checker, value, diagnostics); } } From ca9d06d7b87eba002e84e3c89ecef75886c3e8fc Mon Sep 17 00:00:00 2001 From: asafamr-mm Date: Sat, 9 Dec 2023 12:49:40 +0200 Subject: [PATCH 4/6] asyncio_dangling_task return option --- .../src/checkers/ast/analyze/statement.rs | 6 +++--- .../rules/ruff/rules/asyncio_dangling_task.rs | 18 ++++++++---------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs index 95bca39e70369..57e587807cc73 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs @@ -1560,9 +1560,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { pylint::rules::named_expr_without_context(checker, value); } if checker.enabled(Rule::AsyncioDanglingTask) { - let mut diagnostics: Vec = vec![]; - ruff::rules::asyncio_dangling_task(checker, value, &mut diagnostics); - checker.diagnostics.extend(diagnostics); + if let Some(diagnostic) = ruff::rules::asyncio_dangling_task(checker, value) { + checker.diagnostics.push(diagnostic); + } } if checker.enabled(Rule::RepeatedAppend) { refurb::rules::repeated_append(checker, stmt); diff --git a/crates/ruff_linter/src/rules/ruff/rules/asyncio_dangling_task.rs b/crates/ruff_linter/src/rules/ruff/rules/asyncio_dangling_task.rs index 7a5ca8d9448bf..93b0fbd09ecaf 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/asyncio_dangling_task.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/asyncio_dangling_task.rs @@ -67,13 +67,9 @@ impl Violation for AsyncioDanglingTask { } /// RUF006 -pub(crate) fn asyncio_dangling_task( - checker: &Checker, - expr: &Expr, - diagnostics: &mut Vec, -) { +pub(crate) fn asyncio_dangling_task(checker: &Checker, expr: &Expr) -> Option { let Expr::Call(ast::ExprCall { func, .. }) = expr else { - return; + return None; }; // Ex) `asyncio.create_task(...)` @@ -86,11 +82,10 @@ pub(crate) fn asyncio_dangling_task( _ => None, }) { - diagnostics.push(Diagnostic::new( + return Some(Diagnostic::new( AsyncioDanglingTask { method }, expr.range(), )); - return; } // Ex) `loop = asyncio.get_running_loop(); loop.create_task(...)` @@ -99,7 +94,7 @@ pub(crate) fn asyncio_dangling_task( if typing::resolve_assignment(value, checker.semantic()).is_some_and(|call_path| { matches!(call_path.as_slice(), ["asyncio", "get_running_loop"]) }) { - diagnostics.push(Diagnostic::new( + return Some(Diagnostic::new( AsyncioDanglingTask { method: Method::CreateTask, }, @@ -108,6 +103,7 @@ pub(crate) fn asyncio_dangling_task( } } } + None } pub(crate) fn asyncio_dangling_task_unused( @@ -127,7 +123,9 @@ pub(crate) fn asyncio_dangling_task_unused( else { continue; }; - asyncio_dangling_task(checker, value, diagnostics); + if let Some(diagnostic) = asyncio_dangling_task(checker, value) { + diagnostics.push(diagnostic); + } } } From 00bcfc87c8b5d1380f4902d4019205b2840caac6 Mon Sep 17 00:00:00 2001 From: asafamr-mm Date: Sat, 9 Dec 2023 22:04:34 +0200 Subject: [PATCH 5/6] RUF006 unused AnnAssign --- .../resources/test/fixtures/ruff/RUF006.py | 6 ++++++ .../rules/ruff/rules/asyncio_dangling_task.rs | 5 ++++- ...er__rules__ruff__tests__RUF006_RUF006.py.snap | 16 ++++++++++++---- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF006.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF006.py index 9dee81bad3544..17fab70f25d8b 100644 --- a/crates/ruff_linter/resources/test/fixtures/ruff/RUF006.py +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF006.py @@ -68,6 +68,12 @@ def f(): task = asyncio.create_task(coordinator.ws_connect()) +# Error +def f(): + loop = asyncio.get_running_loop() + task: asyncio.Task = loop.create_task(coordinator.ws_connect()) + + # OK (potential false negative) def f(): task = asyncio.create_task(coordinator.ws_connect()) diff --git a/crates/ruff_linter/src/rules/ruff/rules/asyncio_dangling_task.rs b/crates/ruff_linter/src/rules/ruff/rules/asyncio_dangling_task.rs index 93b0fbd09ecaf..8311543ea49db 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/asyncio_dangling_task.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/asyncio_dangling_task.rs @@ -119,7 +119,10 @@ pub(crate) fn asyncio_dangling_task_unused( let Some(source) = binding.source else { continue; }; - let Stmt::Assign(ast::StmtAssign { value, .. }) = checker.semantic().statement(source) + let (Stmt::Assign(ast::StmtAssign { value, .. }) + | Stmt::AnnAssign(ast::StmtAnnAssign { + value: Some(value), .. + })) = checker.semantic().statement(source) else { continue; }; diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF006_RUF006.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF006_RUF006.py.snap index b6aebd1be81b1..def73e5a2e11e 100644 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF006_RUF006.py.snap +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF006_RUF006.py.snap @@ -25,11 +25,19 @@ RUF006.py:68:12: RUF006 Store a reference to the return value of `asyncio.create | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF006 | -RUF006.py:91:5: RUF006 Store a reference to the return value of `asyncio.create_task` +RUF006.py:74:26: RUF006 Store a reference to the return value of `asyncio.create_task` | -89 | def f(): -90 | loop = asyncio.get_running_loop() -91 | loop.create_task(coordinator.ws_connect()) # Error +72 | def f(): +73 | loop = asyncio.get_running_loop() +74 | task: asyncio.Task = loop.create_task(coordinator.ws_connect()) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF006 + | + +RUF006.py:97:5: RUF006 Store a reference to the return value of `asyncio.create_task` + | +95 | def f(): +96 | loop = asyncio.get_running_loop() +97 | loop.create_task(coordinator.ws_connect()) # Error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF006 | From d6efa74b202ed9e80c01a324599b95e61a24eb89 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sat, 9 Dec 2023 16:04:47 -0500 Subject: [PATCH 6/6] Add one failing test --- .../resources/test/fixtures/ruff/RUF006.py | 16 +++++ .../src/checkers/ast/analyze/bindings.rs | 10 ++- .../checkers/ast/analyze/deferred_scopes.rs | 9 +-- .../src/checkers/ast/analyze/statement.rs | 4 +- .../rules/ruff/rules/asyncio_dangling_task.rs | 66 ++++++++----------- 5 files changed, 58 insertions(+), 47 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF006.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF006.py index 17fab70f25d8b..6c7792d5c4391 100644 --- a/crates/ruff_linter/resources/test/fixtures/ruff/RUF006.py +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF006.py @@ -106,3 +106,19 @@ def f(): def f(): loop = asyncio.get_running_loop() loop.do_thing(coordinator.ws_connect()) + + +# OK +async def f(): + task = unused = asyncio.create_task(coordinator.ws_connect()) + await task + + +# OK (false negative) +async def f(): + task = unused = asyncio.create_task(coordinator.ws_connect()) + + +# OK +async def f(): + task[i] = asyncio.create_task(coordinator.ws_connect()) diff --git a/crates/ruff_linter/src/checkers/ast/analyze/bindings.rs b/crates/ruff_linter/src/checkers/ast/analyze/bindings.rs index 0fbc85f5552fa..e279dd5fbf7e9 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/bindings.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/bindings.rs @@ -3,11 +3,12 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::codes::Rule; -use crate::rules::{flake8_import_conventions, flake8_pyi, pyflakes, pylint}; +use crate::rules::{flake8_import_conventions, flake8_pyi, pyflakes, pylint, ruff}; /// Run lint rules over the [`Binding`]s. pub(crate) fn bindings(checker: &mut Checker) { if !checker.any_enabled(&[ + Rule::AsyncioDanglingTask, Rule::InvalidAllFormat, Rule::InvalidAllObject, Rule::NonAsciiName, @@ -71,5 +72,12 @@ pub(crate) fn bindings(checker: &mut Checker) { checker.diagnostics.push(diagnostic); } } + if checker.enabled(Rule::AsyncioDanglingTask) { + if let Some(diagnostic) = + ruff::rules::asyncio_dangling_binding(binding, &checker.semantic) + { + checker.diagnostics.push(diagnostic); + } + } } } diff --git a/crates/ruff_linter/src/checkers/ast/analyze/deferred_scopes.rs b/crates/ruff_linter/src/checkers/ast/analyze/deferred_scopes.rs index 5bd0ff05cb1ce..27c9a6b7c79e2 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/deferred_scopes.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/deferred_scopes.rs @@ -5,9 +5,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::codes::Rule; -use crate::rules::{ - flake8_pyi, flake8_type_checking, flake8_unused_arguments, pyflakes, pylint, ruff, -}; +use crate::rules::{flake8_pyi, flake8_type_checking, flake8_unused_arguments, pyflakes, pylint}; /// Run lint rules over all deferred scopes in the [`SemanticModel`]. pub(crate) fn deferred_scopes(checker: &mut Checker) { @@ -33,7 +31,6 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) { Rule::UnusedPrivateTypedDict, Rule::UnusedStaticMethodArgument, Rule::UnusedVariable, - Rule::AsyncioDanglingTask, Rule::NoSelfUse, ]) { return; @@ -297,10 +294,6 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) { } } - if checker.enabled(Rule::AsyncioDanglingTask) { - ruff::rules::asyncio_dangling_task_unused(checker, scope, &mut diagnostics); - } - if matches!(scope.kind, ScopeKind::Function(_) | ScopeKind::Module) { if enforce_typing_imports { let runtime_imports: Vec<&Binding> = checker diff --git a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs index 1bc613bf52342..5fb77629a28c8 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs @@ -1571,7 +1571,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { pylint::rules::named_expr_without_context(checker, value); } if checker.enabled(Rule::AsyncioDanglingTask) { - if let Some(diagnostic) = ruff::rules::asyncio_dangling_task(checker, value) { + if let Some(diagnostic) = + ruff::rules::asyncio_dangling_task(value, checker.semantic()) + { checker.diagnostics.push(diagnostic); } } diff --git a/crates/ruff_linter/src/rules/ruff/rules/asyncio_dangling_task.rs b/crates/ruff_linter/src/rules/ruff/rules/asyncio_dangling_task.rs index 8311543ea49db..2f339b95c35dd 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/asyncio_dangling_task.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/asyncio_dangling_task.rs @@ -1,15 +1,12 @@ use std::fmt; use ast::Stmt; -use ruff_python_ast::{self as ast, Expr}; - use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_semantic::{analyze::typing, Scope}; +use ruff_python_ast::{self as ast, Expr}; +use ruff_python_semantic::{analyze::typing, Binding, SemanticModel}; use ruff_text_size::Ranged; -use crate::checkers::ast::Checker; - /// ## What it does /// Checks for `asyncio.create_task` and `asyncio.ensure_future` calls /// that do not store a reference to the returned result. @@ -67,20 +64,20 @@ impl Violation for AsyncioDanglingTask { } /// RUF006 -pub(crate) fn asyncio_dangling_task(checker: &Checker, expr: &Expr) -> Option { +pub(crate) fn asyncio_dangling_task(expr: &Expr, semantic: &SemanticModel) -> Option { let Expr::Call(ast::ExprCall { func, .. }) = expr else { return None; }; // Ex) `asyncio.create_task(...)` - if let Some(method) = checker - .semantic() - .resolve_call_path(func) - .and_then(|call_path| match call_path.as_slice() { - ["asyncio", "create_task"] => Some(Method::CreateTask), - ["asyncio", "ensure_future"] => Some(Method::EnsureFuture), - _ => None, - }) + if let Some(method) = + semantic + .resolve_call_path(func) + .and_then(|call_path| match call_path.as_slice() { + ["asyncio", "create_task"] => Some(Method::CreateTask), + ["asyncio", "ensure_future"] => Some(Method::EnsureFuture), + _ => None, + }) { return Some(Diagnostic::new( AsyncioDanglingTask { method }, @@ -91,7 +88,7 @@ pub(crate) fn asyncio_dangling_task(checker: &Checker, expr: &Expr) -> Option Option, -) { - for binding in scope - .binding_ids() - .map(|binding_id| checker.semantic().binding(binding_id)) - .filter(|binding| binding.kind.is_assignment() && !binding.is_used()) - { - let Some(source) = binding.source else { - continue; - }; - let (Stmt::Assign(ast::StmtAssign { value, .. }) - | Stmt::AnnAssign(ast::StmtAnnAssign { - value: Some(value), .. - })) = checker.semantic().statement(source) - else { - continue; - }; - if let Some(diagnostic) = asyncio_dangling_task(checker, value) { - diagnostics.push(diagnostic); +/// RUF006 +pub(crate) fn asyncio_dangling_binding( + binding: &Binding, + semantic: &SemanticModel, +) -> Option { + if binding.is_used() || !binding.kind.is_assignment() { + return None; + } + + let source = binding.source?; + match semantic.statement(source) { + Stmt::Assign(ast::StmtAssign { value, targets, .. }) if targets.len() == 1 => { + asyncio_dangling_task(value, semantic) } + Stmt::AnnAssign(ast::StmtAnnAssign { + value: Some(value), .. + }) => asyncio_dangling_task(value, semantic), + _ => None, } }