Ignore list-copy recommendations for async `for` loops (#11250)

## Summary

Removes these from `PERF402`, but adds them to `PERF401`, with a custom
message to use an `async` comprehension.

Closes https://github.com/astral-sh/ruff/issues/10787.
This commit is contained in:
Charlie Marsh 2024-05-02 11:48:52 -07:00 committed by GitHub
parent 64700d296f
commit 3a7c01b365
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 69 additions and 17 deletions

View File

@ -72,3 +72,18 @@ def f():
result = Foo() result = Foo()
for i in items: for i in items:
result.append(i) # Ok result.append(i) # Ok
def f():
items = [1, 2, 3, 4]
result = []
async for i in items:
if i % 2:
result.append(i) # PERF401
def f():
items = [1, 2, 3, 4]
result = []
async for i in items:
result.append(i) # PERF401

View File

@ -43,3 +43,10 @@ def f():
for path in ("foo", "bar"): for path in ("foo", "bar"):
sys.path.append(path) # OK sys.path.append(path) # OK
def f():
items = [1, 2, 3, 4]
result = []
async for i in items:
result.append(i) # PERF402

View File

@ -1323,10 +1323,10 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
pylint::rules::dict_iter_missing_items(checker, target, iter); pylint::rules::dict_iter_missing_items(checker, target, iter);
} }
if checker.enabled(Rule::ManualListComprehension) { if checker.enabled(Rule::ManualListComprehension) {
perflint::rules::manual_list_comprehension(checker, target, body); perflint::rules::manual_list_comprehension(checker, for_stmt);
} }
if checker.enabled(Rule::ManualListCopy) { if checker.enabled(Rule::ManualListCopy) {
perflint::rules::manual_list_copy(checker, target, body); perflint::rules::manual_list_copy(checker, for_stmt);
} }
if checker.enabled(Rule::ManualDictComprehension) { if checker.enabled(Rule::ManualDictComprehension) {
perflint::rules::manual_dict_comprehension(checker, target, body); perflint::rules::manual_dict_comprehension(checker, target, body);

View File

@ -44,22 +44,28 @@ use crate::checkers::ast::Checker;
/// filtered.extend(x for x in original if x % 2) /// filtered.extend(x for x in original if x % 2)
/// ``` /// ```
#[violation] #[violation]
pub struct ManualListComprehension; pub struct ManualListComprehension {
is_async: bool,
}
impl Violation for ManualListComprehension { impl Violation for ManualListComprehension {
#[derive_message_formats] #[derive_message_formats]
fn message(&self) -> String { fn message(&self) -> String {
format!("Use a list comprehension to create a transformed list") let ManualListComprehension { is_async } = self;
match is_async {
false => format!("Use a list comprehension to create a transformed list"),
true => format!("Use an async list comprehension to create a transformed list"),
}
} }
} }
/// PERF401 /// PERF401
pub(crate) fn manual_list_comprehension(checker: &mut Checker, target: &Expr, body: &[Stmt]) { pub(crate) fn manual_list_comprehension(checker: &mut Checker, for_stmt: &ast::StmtFor) {
let Expr::Name(ast::ExprName { id, .. }) = target else { let Expr::Name(ast::ExprName { id, .. }) = &*for_stmt.target else {
return; return;
}; };
let (stmt, if_test) = match body { let (stmt, if_test) = match &*for_stmt.body {
// ```python // ```python
// for x in y: // for x in y:
// if z: // if z:
@ -121,10 +127,13 @@ pub(crate) fn manual_list_comprehension(checker: &mut Checker, target: &Expr, bo
return; return;
} }
// Ignore direct list copies (e.g., `for x in y: filtered.append(x)`). // Ignore direct list copies (e.g., `for x in y: filtered.append(x)`), unless it's async, which
if if_test.is_none() { // `manual-list-copy` doesn't cover.
if arg.as_name_expr().is_some_and(|arg| arg.id == *id) { if !for_stmt.is_async {
return; if if_test.is_none() {
if arg.as_name_expr().is_some_and(|arg| arg.id == *id) {
return;
}
} }
} }
@ -179,7 +188,10 @@ pub(crate) fn manual_list_comprehension(checker: &mut Checker, target: &Expr, bo
return; return;
} }
checker checker.diagnostics.push(Diagnostic::new(
.diagnostics ManualListComprehension {
.push(Diagnostic::new(ManualListComprehension, *range)); is_async: for_stmt.is_async,
},
*range,
));
} }

View File

@ -45,12 +45,16 @@ impl Violation for ManualListCopy {
} }
/// PERF402 /// PERF402
pub(crate) fn manual_list_copy(checker: &mut Checker, target: &Expr, body: &[Stmt]) { pub(crate) fn manual_list_copy(checker: &mut Checker, for_stmt: &ast::StmtFor) {
let Expr::Name(ast::ExprName { id, .. }) = target else { if for_stmt.is_async {
return;
}
let Expr::Name(ast::ExprName { id, .. }) = &*for_stmt.target else {
return; return;
}; };
let [stmt] = body else { let [stmt] = &*for_stmt.body else {
return; return;
}; };

View File

@ -17,4 +17,18 @@ PERF401.py:13:9: PERF401 Use a list comprehension to create a transformed list
| ^^^^^^^^^^^^^^^^^^^^ PERF401 | ^^^^^^^^^^^^^^^^^^^^ PERF401
| |
PERF401.py:82:13: PERF401 Use an async list comprehension to create a transformed list
|
80 | async for i in items:
81 | if i % 2:
82 | result.append(i) # PERF401
| ^^^^^^^^^^^^^^^^ PERF401
|
PERF401.py:89:9: PERF401 Use an async list comprehension to create a transformed list
|
87 | result = []
88 | async for i in items:
89 | result.append(i) # PERF401
| ^^^^^^^^^^^^^^^^ PERF401
|