Skip to content

Commit ed1dd09

Browse files
Refine some perflint rules (#5484)
## Summary Removing some false positives based on running over `zulip`. `PERF401` now also detects cases like: ```py original = list(range(10000)) filtered = [] for i in original: filtered.append(i * i) ``` Previously, these were caught by the list-copy rule, but these too need comprehensions.
1 parent ca497fa commit ed1dd09

File tree

7 files changed

+122
-44
lines changed

7 files changed

+122
-44
lines changed
Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
1-
def foo():
1+
def f():
22
items = [1, 2, 3, 4]
33
result = []
44
for i in items:
55
if i % 2:
66
result.append(i) # PERF401
77

88

9-
def foo():
10-
items = [1,2,3,4]
9+
def f():
10+
items = [1, 2, 3, 4]
11+
result = []
12+
for i in items:
13+
result.append(i * i) # PERF401
14+
15+
16+
def f():
17+
items = [1, 2, 3, 4]
1118
result = []
1219
for i in items:
1320
if i % 2:
@@ -16,3 +23,10 @@ def foo():
1623
result.append(i) # PERF401
1724
else:
1825
result.append(i) # PERF401
26+
27+
28+
def f():
29+
items = [1, 2, 3, 4]
30+
result = []
31+
for i in items:
32+
result.append(i) # OK
Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
1-
def foo():
1+
def f():
22
items = [1, 2, 3, 4]
33
result = []
44
for i in items:
55
result.append(i) # PERF402
66

77

8-
def foo():
8+
def f():
99
items = [1, 2, 3, 4]
1010
result = []
1111
for i in items:
1212
result.insert(0, i) # PERF402
13+
14+
15+
def f():
16+
items = [1, 2, 3, 4]
17+
result = []
18+
for i in items:
19+
result.append(i * i) # OK

crates/ruff/src/checkers/ast/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1525,10 +1525,10 @@ where
15251525
perflint::rules::incorrect_dict_iterator(self, target, iter);
15261526
}
15271527
if self.enabled(Rule::ManualListComprehension) {
1528-
perflint::rules::manual_list_comprehension(self, body);
1528+
perflint::rules::manual_list_comprehension(self, target, body);
15291529
}
15301530
if self.enabled(Rule::ManualListCopy) {
1531-
perflint::rules::manual_list_copy(self, body);
1531+
perflint::rules::manual_list_copy(self, target, body);
15321532
}
15331533
if self.enabled(Rule::UnnecessaryListCast) {
15341534
perflint::rules::unnecessary_list_cast(self, iter);

crates/ruff/src/rules/perflint/rules/manual_list_comprehension.rs

Lines changed: 64 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::checkers::ast::Checker;
99
/// Checks for `for` loops that can be replaced by a list comprehension.
1010
///
1111
/// ## Why is this bad?
12-
/// When creating a filtered list from an existing list using a for-loop,
12+
/// When creating a transformed list from an existing list using a for-loop,
1313
/// prefer a list comprehension. List comprehensions are more readable and
1414
/// more performant.
1515
///
@@ -34,43 +34,87 @@ use crate::checkers::ast::Checker;
3434
/// original = list(range(10000))
3535
/// filtered = [x for x in original if x % 2]
3636
/// ```
37+
///
38+
/// If you're appending to an existing list, use the `extend` method instead:
39+
/// ```python
40+
/// original = list(range(10000))
41+
/// filtered.extend(x for x in original if x % 2)
42+
/// ```
3743
#[violation]
3844
pub struct ManualListComprehension;
3945

4046
impl Violation for ManualListComprehension {
4147
#[derive_message_formats]
4248
fn message(&self) -> String {
43-
format!("Use a list comprehension to create a new filtered list")
49+
format!("Use a list comprehension to create a transformed list")
4450
}
4551
}
4652

4753
/// PERF401
48-
pub(crate) fn manual_list_comprehension(checker: &mut Checker, body: &[Stmt]) {
49-
let [stmt] = body else {
50-
return;
54+
pub(crate) fn manual_list_comprehension(checker: &mut Checker, target: &Expr, body: &[Stmt]) {
55+
let (stmt, conditional) = match body {
56+
// ```python
57+
// for x in y:
58+
// if z:
59+
// filtered.append(x)
60+
// ```
61+
[Stmt::If(ast::StmtIf { body, orelse, .. })] => {
62+
if !orelse.is_empty() {
63+
return;
64+
}
65+
let [stmt] = body.as_slice() else {
66+
return;
67+
};
68+
(stmt, true)
69+
}
70+
// ```python
71+
// for x in y:
72+
// filtered.append(f(x))
73+
// ```
74+
[stmt] => (stmt, false),
75+
_ => return,
5176
};
5277

53-
let Stmt::If(ast::StmtIf { body, .. }) = stmt else {
78+
let Stmt::Expr(ast::StmtExpr { value, .. }) = stmt else {
5479
return;
5580
};
5681

57-
for stmt in body {
58-
let Stmt::Expr(ast::StmtExpr { value, .. }) = stmt else {
59-
continue;
60-
};
82+
let Expr::Call(ast::ExprCall {
83+
func,
84+
range,
85+
args,
86+
keywords,
87+
}) = value.as_ref()
88+
else {
89+
return;
90+
};
6191

62-
let Expr::Call(ast::ExprCall { func, range, .. }) = value.as_ref() else {
63-
continue;
64-
};
92+
if !keywords.is_empty() {
93+
return;
94+
}
6595

66-
let Expr::Attribute(ast::ExprAttribute { attr, .. }) = func.as_ref() else {
67-
continue;
68-
};
96+
let [arg] = args.as_slice() else {
97+
return;
98+
};
6999

70-
if attr.as_str() == "append" {
71-
checker
72-
.diagnostics
73-
.push(Diagnostic::new(ManualListComprehension, *range));
100+
// Ignore direct list copies (e.g., `for x in y: filtered.append(x)`).
101+
if !conditional {
102+
if arg.as_name_expr().map_or(false, |arg| {
103+
target
104+
.as_name_expr()
105+
.map_or(false, |target| arg.id == target.id)
106+
}) {
107+
return;
74108
}
75109
}
110+
111+
let Expr::Attribute(ast::ExprAttribute { attr, .. }) = func.as_ref() else {
112+
return;
113+
};
114+
115+
if attr.as_str() == "append" {
116+
checker
117+
.diagnostics
118+
.push(Diagnostic::new(ManualListComprehension, *range));
119+
}
76120
}

crates/ruff/src/rules/perflint/rules/manual_list_copy.rs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ impl Violation for ManualListCopy {
4444
}
4545

4646
/// PERF402
47-
pub(crate) fn manual_list_copy(checker: &mut Checker, body: &[Stmt]) {
47+
pub(crate) fn manual_list_copy(checker: &mut Checker, target: &Expr, body: &[Stmt]) {
4848
let [stmt] = body else {
4949
return;
5050
};
@@ -53,10 +53,33 @@ pub(crate) fn manual_list_copy(checker: &mut Checker, body: &[Stmt]) {
5353
return;
5454
};
5555

56-
let Expr::Call(ast::ExprCall { func, range, .. }) = value.as_ref() else {
56+
let Expr::Call(ast::ExprCall {
57+
func,
58+
range,
59+
args,
60+
keywords,
61+
}) = value.as_ref()
62+
else {
5763
return;
5864
};
5965

66+
if !keywords.is_empty() {
67+
return;
68+
}
69+
70+
let [arg] = args.as_slice() else {
71+
return;
72+
};
73+
74+
// Only flag direct list copies (e.g., `for x in y: filtered.append(x)`).
75+
if !arg.as_name_expr().map_or(false, |arg| {
76+
target
77+
.as_name_expr()
78+
.map_or(false, |target| arg.id == target.id)
79+
}) {
80+
return;
81+
}
82+
6083
let Expr::Attribute(ast::ExprAttribute { attr, .. }) = func.as_ref() else {
6184
return;
6285
};
Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,20 @@
11
---
22
source: crates/ruff/src/rules/perflint/mod.rs
33
---
4-
PERF401.py:6:13: PERF401 Use a list comprehension to create a new filtered list
4+
PERF401.py:6:13: PERF401 Use a list comprehension to create a transformed list
55
|
66
4 | for i in items:
77
5 | if i % 2:
88
6 | result.append(i) # PERF401
99
| ^^^^^^^^^^^^^^^^ PERF401
1010
|
1111

12-
PERF401.py:14:13: PERF401 Use a list comprehension to create a new filtered list
12+
PERF401.py:13:9: PERF401 Use a list comprehension to create a transformed list
1313
|
14+
11 | result = []
1415
12 | for i in items:
15-
13 | if i % 2:
16-
14 | result.append(i) # PERF401
17-
| ^^^^^^^^^^^^^^^^ PERF401
18-
15 | elif i % 2:
19-
16 | result.append(i) # PERF401
16+
13 | result.append(i * i) # PERF401
17+
| ^^^^^^^^^^^^^^^^^^^^ PERF401
2018
|
2119

2220

crates/ruff/src/rules/perflint/snapshots/ruff__rules__perflint__tests__PERF402_PERF402.py.snap

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,4 @@ PERF402.py:5:9: PERF402 Use `list` or `list.copy` to create a copy of a list
99
| ^^^^^^^^^^^^^^^^ PERF402
1010
|
1111

12-
PERF402.py:12:9: PERF402 Use `list` or `list.copy` to create a copy of a list
13-
|
14-
10 | result = []
15-
11 | for i in items:
16-
12 | result.insert(0, i) # PERF402
17-
| ^^^^^^^^^^^^^^^^^^^ PERF402
18-
|
19-
2012

0 commit comments

Comments
 (0)