Skip to content

Commit 7e652e8

Browse files
[flake8_comprehensions] Handled special case for C400 which also matches C416 (#10419)
## Summary Short-circuit implementation mentioned in #10403. I implemented this by extending C400: - Made `UnnecessaryGeneratorList` have information of whether the the short-circuiting occurred (to put diagnostic) - Add additional check for whether in `unnecessary_generator_list` function. Please give me suggestions if you think this isn't the best way to handle this :) ## Test Plan Extended `C400.py` a little, and written the cases where: - Code could be converted to one single conversion to `list` e.g. `list(x for x in range(3))` -> `list(range(3))` - Code couldn't be converted to one single conversion to `list` e.g. `list(2 * x for x in range(3))` -> `[2 * x for x in range(3)]` - `list` function is not built-in, and should not modify the code in any way.
1 parent 9675e18 commit 7e652e8

File tree

3 files changed

+155
-49
lines changed

3 files changed

+155
-49
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
1+
# Cannot combine with C416. Should use list comprehension here.
2+
even_nums = list(2 * x for x in range(3))
3+
odd_nums = list(
4+
2 * x + 1 for x in range(3)
5+
)
6+
7+
8+
# Short-circuit case, combine with C416 and should produce x = list(range(3))
19
x = list(x for x in range(3))
210
x = list(
311
x for x in range(3)
412
)
513

6-
14+
# Not built-in list.
715
def list(*args, **kwargs):
816
return None
917

1018

19+
list(2 * x for x in range(3))
1120
list(x for x in range(3))
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
22
use ruff_macros::{derive_message_formats, violation};
33
use ruff_python_ast as ast;
4+
use ruff_python_ast::comparable::ComparableExpr;
5+
use ruff_python_ast::ExprGenerator;
46
use ruff_text_size::{Ranged, TextSize};
57

68
use crate::checkers::ast::Checker;
@@ -9,37 +11,53 @@ use super::helpers;
911

1012
/// ## What it does
1113
/// Checks for unnecessary generators that can be rewritten as `list`
12-
/// comprehensions.
14+
/// comprehensions (or with `list` directly).
1315
///
1416
/// ## Why is this bad?
1517
/// It is unnecessary to use `list` around a generator expression, since
1618
/// there are equivalent comprehensions for these types. Using a
1719
/// comprehension is clearer and more idiomatic.
1820
///
21+
/// Further, if the comprehension can be removed entirely, as in the case of
22+
/// `list(x for x in foo)`, it's better to use `list(foo)` directly, since it's
23+
/// even more direct.
24+
///
1925
/// ## Examples
2026
/// ```python
2127
/// list(f(x) for x in foo)
28+
/// list(x for x in foo)
2229
/// ```
2330
///
2431
/// Use instead:
2532
/// ```python
2633
/// [f(x) for x in foo]
34+
/// list(foo)
2735
/// ```
2836
///
2937
/// ## Fix safety
3038
/// This rule's fix is marked as unsafe, as it may occasionally drop comments
3139
/// when rewriting the call. In most cases, though, comments will be preserved.
3240
#[violation]
33-
pub struct UnnecessaryGeneratorList;
41+
pub struct UnnecessaryGeneratorList {
42+
short_circuit: bool,
43+
}
3444

3545
impl AlwaysFixableViolation for UnnecessaryGeneratorList {
3646
#[derive_message_formats]
3747
fn message(&self) -> String {
38-
format!("Unnecessary generator (rewrite as a `list` comprehension)")
48+
if self.short_circuit {
49+
format!("Unnecessary generator (rewrite using `list()`")
50+
} else {
51+
format!("Unnecessary generator (rewrite as a `list` comprehension)")
52+
}
3953
}
4054

4155
fn fix_title(&self) -> String {
42-
"Rewrite as a `list` comprehension".to_string()
56+
if self.short_circuit {
57+
"Rewrite using `list()`".to_string()
58+
} else {
59+
"Rewrite as a `list` comprehension".to_string()
60+
}
4361
}
4462
}
4563

@@ -56,28 +74,59 @@ pub(crate) fn unnecessary_generator_list(checker: &mut Checker, call: &ast::Expr
5674
if !checker.semantic().is_builtin("list") {
5775
return;
5876
}
59-
if argument.is_generator_expr() {
60-
let mut diagnostic = Diagnostic::new(UnnecessaryGeneratorList, call.range());
6177

62-
// Convert `list(x for x in y)` to `[x for x in y]`.
63-
diagnostic.set_fix({
64-
// Replace `list(` with `[`.
65-
let call_start = Edit::replacement(
66-
"[".to_string(),
67-
call.start(),
68-
call.arguments.start() + TextSize::from(1),
69-
);
78+
let Some(ExprGenerator {
79+
elt, generators, ..
80+
}) = argument.as_generator_expr()
81+
else {
82+
return;
83+
};
84+
85+
// Short-circuit: given `list(x for x in y)`, generate `list(y)` (in lieu of `[x for x in y]`).
86+
if let [generator] = generators.as_slice() {
87+
if generator.ifs.is_empty() && !generator.is_async {
88+
if ComparableExpr::from(elt) == ComparableExpr::from(&generator.target) {
89+
let mut diagnostic = Diagnostic::new(
90+
UnnecessaryGeneratorList {
91+
short_circuit: true,
92+
},
93+
call.range(),
94+
);
95+
let iterator = format!("list({})", checker.locator().slice(generator.iter.range()));
96+
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
97+
iterator,
98+
call.range(),
99+
)));
100+
checker.diagnostics.push(diagnostic);
101+
return;
102+
}
103+
}
104+
}
105+
106+
// Convert `list(f(x) for x in y)` to `[f(x) for x in y]`.
107+
let mut diagnostic = Diagnostic::new(
108+
UnnecessaryGeneratorList {
109+
short_circuit: false,
110+
},
111+
call.range(),
112+
);
113+
diagnostic.set_fix({
114+
// Replace `list(` with `[`.
115+
let call_start = Edit::replacement(
116+
"[".to_string(),
117+
call.start(),
118+
call.arguments.start() + TextSize::from(1),
119+
);
70120

71-
// Replace `)` with `]`.
72-
let call_end = Edit::replacement(
73-
"]".to_string(),
74-
call.arguments.end() - TextSize::from(1),
75-
call.end(),
76-
);
121+
// Replace `)` with `]`.
122+
let call_end = Edit::replacement(
123+
"]".to_string(),
124+
call.arguments.end() - TextSize::from(1),
125+
call.end(),
126+
);
77127

78-
Fix::unsafe_edits(call_start, [call_end])
79-
});
128+
Fix::unsafe_edits(call_start, [call_end])
129+
});
80130

81-
checker.diagnostics.push(diagnostic);
82-
}
131+
checker.diagnostics.push(diagnostic);
83132
}
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,90 @@
11
---
22
source: crates/ruff_linter/src/rules/flake8_comprehensions/mod.rs
33
---
4-
C400.py:1:5: C400 [*] Unnecessary generator (rewrite as a `list` comprehension)
4+
C400.py:2:13: C400 [*] Unnecessary generator (rewrite as a `list` comprehension)
55
|
6-
1 | x = list(x for x in range(3))
7-
| ^^^^^^^^^^^^^^^^^^^^^^^^^ C400
8-
2 | x = list(
9-
3 | x for x in range(3)
6+
1 | # Cannot combine with C416. Should use list comprehension here.
7+
2 | even_nums = list(2 * x for x in range(3))
8+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C400
9+
3 | odd_nums = list(
10+
4 | 2 * x + 1 for x in range(3)
1011
|
1112
= help: Rewrite as a `list` comprehension
1213

1314
Unsafe fix
14-
1 |-x = list(x for x in range(3))
15-
1 |+x = [x for x in range(3)]
16-
2 2 | x = list(
17-
3 3 | x for x in range(3)
18-
4 4 | )
15+
1 1 | # Cannot combine with C416. Should use list comprehension here.
16+
2 |-even_nums = list(2 * x for x in range(3))
17+
2 |+even_nums = [2 * x for x in range(3)]
18+
3 3 | odd_nums = list(
19+
4 4 | 2 * x + 1 for x in range(3)
20+
5 5 | )
1921

20-
C400.py:2:5: C400 [*] Unnecessary generator (rewrite as a `list` comprehension)
22+
C400.py:3:12: C400 [*] Unnecessary generator (rewrite as a `list` comprehension)
2123
|
22-
1 | x = list(x for x in range(3))
23-
2 | x = list(
24-
| _____^
25-
3 | | x for x in range(3)
26-
4 | | )
24+
1 | # Cannot combine with C416. Should use list comprehension here.
25+
2 | even_nums = list(2 * x for x in range(3))
26+
3 | odd_nums = list(
27+
| ____________^
28+
4 | | 2 * x + 1 for x in range(3)
29+
5 | | )
2730
| |_^ C400
2831
|
2932
= help: Rewrite as a `list` comprehension
3033

3134
Unsafe fix
32-
1 1 | x = list(x for x in range(3))
33-
2 |-x = list(
34-
2 |+x = [
35-
3 3 | x for x in range(3)
36-
4 |-)
37-
4 |+]
38-
5 5 |
35+
1 1 | # Cannot combine with C416. Should use list comprehension here.
36+
2 2 | even_nums = list(2 * x for x in range(3))
37+
3 |-odd_nums = list(
38+
3 |+odd_nums = [
39+
4 4 | 2 * x + 1 for x in range(3)
40+
5 |-)
41+
5 |+]
3942
6 6 |
40-
7 7 | def list(*args, **kwargs):
43+
7 7 |
44+
8 8 | # Short-circuit case, combine with C416 and should produce x = list(range(3))
4145

46+
C400.py:9:5: C400 [*] Unnecessary generator (rewrite using `list()`
47+
|
48+
8 | # Short-circuit case, combine with C416 and should produce x = list(range(3))
49+
9 | x = list(x for x in range(3))
50+
| ^^^^^^^^^^^^^^^^^^^^^^^^^ C400
51+
10 | x = list(
52+
11 | x for x in range(3)
53+
|
54+
= help: Rewrite using `list()`
4255

56+
Unsafe fix
57+
6 6 |
58+
7 7 |
59+
8 8 | # Short-circuit case, combine with C416 and should produce x = list(range(3))
60+
9 |-x = list(x for x in range(3))
61+
9 |+x = list(range(3))
62+
10 10 | x = list(
63+
11 11 | x for x in range(3)
64+
12 12 | )
65+
66+
C400.py:10:5: C400 [*] Unnecessary generator (rewrite using `list()`
67+
|
68+
8 | # Short-circuit case, combine with C416 and should produce x = list(range(3))
69+
9 | x = list(x for x in range(3))
70+
10 | x = list(
71+
| _____^
72+
11 | | x for x in range(3)
73+
12 | | )
74+
| |_^ C400
75+
13 |
76+
14 | # Not built-in list.
77+
|
78+
= help: Rewrite using `list()`
79+
80+
Unsafe fix
81+
7 7 |
82+
8 8 | # Short-circuit case, combine with C416 and should produce x = list(range(3))
83+
9 9 | x = list(x for x in range(3))
84+
10 |-x = list(
85+
11 |- x for x in range(3)
86+
12 |-)
87+
10 |+x = list(range(3))
88+
13 11 |
89+
14 12 | # Not built-in list.
90+
15 13 | def list(*args, **kwargs):

0 commit comments

Comments
 (0)