Skip to content

Commit c395e44

Browse files
Avoid PERF rules for iteration-dependent assignments (#5508)
## Summary We need to avoid raising "rewrite as a comprehension" violations in cases like: ```python d = defaultdict(list) for i in [1, 2, 3]: d[i].append(i**2) ``` Closes #5494. Closes #5500.
1 parent 75da72b commit c395e44

File tree

4 files changed

+54
-20
lines changed

4 files changed

+54
-20
lines changed

crates/ruff/resources/test/fixtures/perflint/PERF401.py

+7
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,10 @@ def f():
3030
result = []
3131
for i in items:
3232
result.append(i) # OK
33+
34+
35+
def f():
36+
items = [1, 2, 3, 4]
37+
result = {}
38+
for i in items:
39+
result[i].append(i) # OK

crates/ruff/resources/test/fixtures/perflint/PERF402.py

+7
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,10 @@ def f():
1717
result = []
1818
for i in items:
1919
result.append(i * i) # OK
20+
21+
22+
def f():
23+
items = [1, 2, 3, 4]
24+
result = {}
25+
for i in items:
26+
result[i].append(i * i) # OK

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

+20-10
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use rustpython_parser::ast::{self, Expr, Stmt};
22

33
use ruff_diagnostics::{Diagnostic, Violation};
44
use ruff_macros::{derive_message_formats, violation};
5+
use ruff_python_ast::helpers::any_over_expr;
56

67
use crate::checkers::ast::Checker;
78

@@ -52,6 +53,10 @@ impl Violation for ManualListComprehension {
5253

5354
/// PERF401
5455
pub(crate) fn manual_list_comprehension(checker: &mut Checker, target: &Expr, body: &[Stmt]) {
56+
let Expr::Name(ast::ExprName { id, .. }) = target else {
57+
return;
58+
};
59+
5560
let (stmt, conditional) = match body {
5661
// ```python
5762
// for x in y:
@@ -99,22 +104,27 @@ pub(crate) fn manual_list_comprehension(checker: &mut Checker, target: &Expr, bo
99104

100105
// Ignore direct list copies (e.g., `for x in y: filtered.append(x)`).
101106
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+
if arg.as_name_expr().map_or(false, |arg| arg.id == *id) {
107108
return;
108109
}
109110
}
110111

111-
let Expr::Attribute(ast::ExprAttribute { attr, .. }) = func.as_ref() else {
112+
let Expr::Attribute(ast::ExprAttribute { attr, value, .. }) = func.as_ref() else {
112113
return;
113114
};
114115

115-
if attr.as_str() == "append" {
116-
checker
117-
.diagnostics
118-
.push(Diagnostic::new(ManualListComprehension, *range));
116+
if attr.as_str() != "append" {
117+
return;
118+
}
119+
120+
// Avoid, e.g., `for x in y: filtered[x].append(x * x)`.
121+
if any_over_expr(value, &|expr| {
122+
expr.as_name_expr().map_or(false, |expr| expr.id == *id)
123+
}) {
124+
return;
119125
}
126+
127+
checker
128+
.diagnostics
129+
.push(Diagnostic::new(ManualListComprehension, *range));
120130
}

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

+20-10
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use rustpython_parser::ast::{self, Expr, Stmt};
22

33
use ruff_diagnostics::{Diagnostic, Violation};
44
use ruff_macros::{derive_message_formats, violation};
5+
use ruff_python_ast::helpers::any_over_expr;
56

67
use crate::checkers::ast::Checker;
78

@@ -45,6 +46,10 @@ impl Violation for ManualListCopy {
4546

4647
/// PERF402
4748
pub(crate) fn manual_list_copy(checker: &mut Checker, target: &Expr, body: &[Stmt]) {
49+
let Expr::Name(ast::ExprName { id, .. }) = target else {
50+
return;
51+
};
52+
4853
let [stmt] = body else {
4954
return;
5055
};
@@ -72,21 +77,26 @@ pub(crate) fn manual_list_copy(checker: &mut Checker, target: &Expr, body: &[Stm
7277
};
7378

7479
// 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+
if !arg.as_name_expr().map_or(false, |arg| arg.id == *id) {
8081
return;
8182
}
8283

83-
let Expr::Attribute(ast::ExprAttribute { attr, .. }) = func.as_ref() else {
84+
let Expr::Attribute(ast::ExprAttribute { attr, value, .. }) = func.as_ref() else {
8485
return;
8586
};
8687

87-
if matches!(attr.as_str(), "append" | "insert") {
88-
checker
89-
.diagnostics
90-
.push(Diagnostic::new(ManualListCopy, *range));
88+
if !matches!(attr.as_str(), "append" | "insert") {
89+
return;
90+
}
91+
92+
// Avoid, e.g., `for x in y: filtered[x].append(x * x)`.
93+
if any_over_expr(value, &|expr| {
94+
expr.as_name_expr().map_or(false, |expr| expr.id == *id)
95+
}) {
96+
return;
9197
}
98+
99+
checker
100+
.diagnostics
101+
.push(Diagnostic::new(ManualListCopy, *range));
92102
}

0 commit comments

Comments
 (0)