[`perflint`] fix invalid hoist in `perf401` (#14369)

Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
w0nder1ng 2024-12-12 03:11:09 -05:00 committed by GitHub
parent 033ecf5a4b
commit 2eac00c60f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 864 additions and 204 deletions

View File

@ -74,7 +74,7 @@ def f():
result.append(i) # Ok
def f():
async def f():
items = [1, 2, 3, 4]
result = []
async for i in items:
@ -82,17 +82,24 @@ def f():
result.append(i) # PERF401
def f():
async def f():
items = [1, 2, 3, 4]
result = []
async for i in items:
result.append(i) # PERF401
async def f():
items = [1, 2, 3, 4]
result = [1, 2]
async for i in items:
result.append(i) # PERF401
def f():
result, _ = [1,2,3,4], ...
result, _ = [1, 2, 3, 4], ...
for i in range(10):
result.append(i*2) # PERF401
result.append(i * 2) # PERF401
def f():
@ -100,23 +107,24 @@ def f():
if True:
for i in range(10): # single-line comment 1 should be protected
# single-line comment 2 should be protected
if i % 2: # single-line comment 3 should be protected
result.append(i) # PERF401
if i % 2: # single-line comment 3 should be protected
result.append(i) # PERF401
def f():
result = [] # comment after assignment should be protected
result = [] # comment after assignment should be protected
for i in range(10): # single-line comment 1 should be protected
# single-line comment 2 should be protected
if i % 2: # single-line comment 3 should be protected
result.append(i) # PERF401
if i % 2: # single-line comment 3 should be protected
result.append(i) # PERF401
def f():
result = []
for i in range(10):
"""block comment stops the fix"""
result.append(i*2) # Ok
result.append(i * 2) # Ok
def f(param):
# PERF401
@ -125,3 +133,107 @@ def f(param):
new_layers = []
for value in param:
new_layers.append(value * 3)
def f():
result = []
var = 1
for _ in range(10):
result.append(var + 1) # PERF401
def f():
# make sure that `tmp` is not deleted
tmp = 1; result = [] # commment should be protected
for i in range(10):
result.append(i + 1) # PERF401
def f():
# make sure that `tmp` is not deleted
result = []; tmp = 1 # commment should be protected
for i in range(10):
result.append(i + 1) # PERF401
def f():
result = [] # comment should be protected
for i in range(10):
result.append(i * 2) # PERF401
def f():
result = []
result.append(1)
for i in range(10):
result.append(i * 2) # PERF401
def f():
result = []
result += [1]
for i in range(10):
result.append(i * 2) # PERF401
def f():
result = []
for val in range(5):
result.append(val * 2) # Ok
print(val)
def f():
result = []
for val in range(5):
result.append(val * 2) # PERF401
val = 1
print(val)
def f():
i = [1, 2, 3]
result = []
for i in i:
result.append(i + 1) # PERF401
def f():
result = []
for i in range( # Comment 1 should not be duplicated
(
2 # Comment 2
+ 1
)
): # Comment 3
if i % 2: # Comment 4
result.append(
(
i + 1,
# Comment 5
2,
)
) # PERF401
def f():
result: list[int] = []
for i in range(10):
result.append(i * 2) # PERF401
def f():
a, b = [1, 2, 3], [4, 5, 6]
result = []
for i in a, b:
result.append(i[0] + i[1]) # PERF401
return result
def f():
values = [1, 2, 3]
result = []
for a in values:
print(a)
for a in values:
result.append(a + 1) # PERF401

View File

@ -14,7 +14,6 @@ pub(crate) fn deferred_for_loops(checker: &mut Checker) {
let Stmt::For(stmt_for) = checker.semantic.current_statement() else {
unreachable!("Expected Stmt::For");
};
if checker.enabled(Rule::UnusedLoopControlVariable) {
flake8_bugbear::rules::unused_loop_control_variable(checker, stmt_for);
}
@ -36,6 +35,9 @@ pub(crate) fn deferred_for_loops(checker: &mut Checker) {
if checker.enabled(Rule::DictIndexMissingItems) {
pylint::rules::dict_index_missing_items(checker, stmt_for);
}
if checker.enabled(Rule::ManualListComprehension) {
perflint::rules::manual_list_comprehension(checker, stmt_for);
}
}
}
}

View File

@ -1366,6 +1366,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
Rule::UnnecessaryEnumerate,
Rule::UnusedLoopControlVariable,
Rule::YieldInForLoop,
Rule::ManualListComprehension,
]) {
checker.analyze.for_loops.push(checker.semantic.snapshot());
}
@ -1390,9 +1391,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
if checker.enabled(Rule::DictIterMissingItems) {
pylint::rules::dict_iter_missing_items(checker, target, iter);
}
if checker.enabled(Rule::ManualListComprehension) {
perflint::rules::manual_list_comprehension(checker, for_stmt);
}
if checker.enabled(Rule::ManualListCopy) {
perflint::rules::manual_list_copy(checker, for_stmt);
}

View File

@ -1,16 +1,15 @@
use ruff_python_ast::{self as ast, Arguments, Expr, Stmt};
use anyhow::{anyhow, Result};
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, ViolationMetadata};
use ruff_python_ast::comparable::ComparableExpr;
use ruff_python_ast::helpers::any_over_expr;
use ruff_python_semantic::{analyze::typing::is_list, Binding};
use ruff_python_trivia::PythonWhitespace;
use ruff_source_file::LineRanges;
use ruff_text_size::{Ranged, TextRange};
use ruff_python_ast::{self as ast, Arguments, Expr};
use crate::checkers::ast::Checker;
use anyhow::{anyhow, Result};
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, ViolationMetadata};
use ruff_python_ast::helpers::any_over_expr;
use ruff_python_semantic::{analyze::typing::is_list, Binding};
use ruff_python_trivia::{BackwardsTokenizer, PythonWhitespace, SimpleTokenKind, SimpleTokenizer};
use ruff_source_file::LineRanges;
use ruff_text_size::{Ranged, TextRange};
/// ## What it does
/// Checks for `for` loops that can be replaced by a list comprehension.
@ -93,7 +92,11 @@ impl Violation for ManualListComprehension {
/// PERF401
pub(crate) fn manual_list_comprehension(checker: &mut Checker, for_stmt: &ast::StmtFor) {
let Expr::Name(ast::ExprName { id, .. }) = &*for_stmt.target else {
let Expr::Name(ast::ExprName {
id: for_stmt_target_id,
..
}) = &*for_stmt.target
else {
return;
};
@ -103,7 +106,7 @@ pub(crate) fn manual_list_comprehension(checker: &mut Checker, for_stmt: &ast::S
// if z:
// filtered.append(x)
// ```
[Stmt::If(ast::StmtIf {
[ast::Stmt::If(ast::StmtIf {
body,
elif_else_clauses,
test,
@ -125,7 +128,7 @@ pub(crate) fn manual_list_comprehension(checker: &mut Checker, for_stmt: &ast::S
_ => return,
};
let Stmt::Expr(ast::StmtExpr { value, .. }) = stmt else {
let ast::Stmt::Expr(ast::StmtExpr { value, .. }) = stmt else {
return;
};
@ -151,53 +154,53 @@ pub(crate) fn manual_list_comprehension(checker: &mut Checker, for_stmt: &ast::S
return;
};
let Expr::Attribute(ast::ExprAttribute { attr, value, .. }) = func.as_ref() else {
let Expr::Attribute(ast::ExprAttribute { attr, value, .. }) = &**func else {
return;
};
if attr.as_str() != "append" {
return;
}
// Avoid non-list values.
let Some(list_name) = value.as_name_expr() else {
return;
};
// Ignore direct list copies (e.g., `for x in y: filtered.append(x)`), unless it's async, which
// `manual-list-copy` doesn't cover.
if !for_stmt.is_async {
if if_test.is_none() {
if arg.as_name_expr().is_some_and(|arg| arg.id == *id) {
if arg
.as_name_expr()
.is_some_and(|arg| arg.id == *for_stmt_target_id)
{
return;
}
}
}
// Avoid, e.g., `for x in y: filtered[x].append(x * x)`.
if any_over_expr(value, &|expr| {
expr.as_name_expr().is_some_and(|expr| expr.id == *id)
}) {
return;
}
// Avoid, e.g., `for x in y: filtered.append(filtered[-1] * 2)`.
if any_over_expr(arg, &|expr| {
ComparableExpr::from(expr) == ComparableExpr::from(value)
expr.as_name_expr()
.is_some_and(|expr| expr.id == list_name.id)
}) {
return;
}
// Avoid non-list values.
let Some(name) = value.as_name_expr() else {
return;
};
let Some(binding) = checker
let Some(list_binding) = checker
.semantic()
.only_binding(name)
.only_binding(list_name)
.map(|id| checker.semantic().binding(id))
else {
return;
};
if !is_list(binding, checker.semantic()) {
if !is_list(list_binding, checker.semantic()) {
return;
}
// Avoid if the value is used in the conditional test, e.g.,
// Avoid if the list is used in the conditional test, e.g.,
//
// ```python
// for x in y:
@ -213,28 +216,80 @@ pub(crate) fn manual_list_comprehension(checker: &mut Checker, for_stmt: &ast::S
// ```
if if_test.is_some_and(|test| {
any_over_expr(test, &|expr| {
expr.as_name_expr().is_some_and(|expr| expr.id == name.id)
expr.as_name_expr()
.is_some_and(|expr| expr.id == list_name.id)
})
}) {
return;
}
let binding_stmt = binding
.statement(checker.semantic())
.and_then(|stmt| stmt.as_assign_stmt());
// Avoid if the for-loop target is used outside the for loop, e.g.,
//
// ```python
// for x in y:
// filtered.append(x)
// print(x)
// ```
//
// If this were a comprehension, x would no longer have the correct scope:
//
// ```python
// filtered = [x for x in y]
// print(x)
// ```
let last_target_binding = checker
.semantic()
.lookup_symbol(for_stmt_target_id)
.expect("for loop target must exist");
let target_binding = {
let mut bindings = [last_target_binding].into_iter().chain(
checker
.semantic()
.shadowed_bindings(checker.semantic().scope_id, last_target_binding)
.filter_map(|shadowed| shadowed.same_scope().then_some(shadowed.shadowed_id())),
);
bindings
.find_map(|binding_id| {
let binding = checker.semantic().binding(binding_id);
binding
.statement(checker.semantic())
.and_then(ast::Stmt::as_for_stmt)
.is_some_and(|stmt| stmt.range == for_stmt.range)
.then_some(binding)
})
.expect("for target binding must exist")
};
// If any references to the loop target variable are after the loop,
// then converting it into a comprehension would cause a NameError
if target_binding
.references()
.map(|reference| checker.semantic().reference(reference))
.any(|other_reference| for_stmt.end() < other_reference.start())
{
return;
}
let list_binding_stmt = list_binding.statement(checker.semantic());
let list_binding_value = list_binding_stmt.and_then(|binding_stmt| match binding_stmt {
ast::Stmt::AnnAssign(assign) => assign.value.as_deref(),
ast::Stmt::Assign(assign) => Some(&assign.value),
_ => None,
});
// If the variable is an empty list literal, then we might be able to replace it with a full list comprehension
// otherwise, it has to be replaced with a `list.extend`
let binding_is_empty_list =
binding_stmt.is_some_and(|binding_stmt| match binding_stmt.value.as_list_expr() {
list_binding_value.is_some_and(|binding_value| match binding_value.as_list_expr() {
Some(list_expr) => list_expr.elts.is_empty(),
None => false,
});
// If the for loop does not have the same parent element as the binding, then it cannot always be
// deleted and replaced with a list comprehension. This does not apply when using an extend.
// deleted and replaced with a list comprehension. This does not apply when using `extend`.
let assignment_in_same_statement = {
binding.source.is_some_and(|binding_source| {
list_binding.source.is_some_and(|binding_source| {
let for_loop_parent = checker.semantic().current_statement_parent_id();
let binding_parent = checker.semantic().parent_statement_id(binding_source);
for_loop_parent == binding_parent
@ -243,20 +298,33 @@ pub(crate) fn manual_list_comprehension(checker: &mut Checker, for_stmt: &ast::S
// If the binding is not a single name expression, it could be replaced with a list comprehension,
// but not necessarily, so this needs to be manually fixed. This does not apply when using an extend.
let binding_has_one_target = {
match binding_stmt.map(|binding_stmt| binding_stmt.targets.as_slice()) {
Some([only_target]) => only_target.is_name_expr(),
_ => false,
}
};
let binding_has_one_target = list_binding_stmt.is_some_and(|binding_stmt| match binding_stmt {
ast::Stmt::AnnAssign(_) => true,
ast::Stmt::Assign(assign) => assign.targets.len() == 1,
_ => false,
});
// If the binding gets used in between the assignment and the for loop, a list comprehension is no longer safe
let binding_unused_between = list_binding_stmt.is_some_and(|binding_stmt| {
let from_assign_to_loop = TextRange::new(binding_stmt.end(), for_stmt.start());
// Test if there's any reference to the list symbol between its definition and the for loop.
// if there's at least one, then it's been accessed in the middle somewhere, so it's not safe to change into a list comprehension
!list_binding
.references()
.map(|ref_id| checker.semantic().reference(ref_id).range())
.any(|text_range| from_assign_to_loop.contains_range(text_range))
});
// A list extend works in every context, while a list comprehension only works when all the criteria are true
let comprehension_type =
if binding_is_empty_list && assignment_in_same_statement && binding_has_one_target {
ComprehensionType::ListComprehension
} else {
ComprehensionType::Extend
};
let comprehension_type = if binding_is_empty_list
&& assignment_in_same_statement
&& binding_has_one_target
&& binding_unused_between
{
ComprehensionType::ListComprehension
} else {
ComprehensionType::Extend
};
let mut diagnostic = Diagnostic::new(
ManualListComprehension {
@ -271,7 +339,7 @@ pub(crate) fn manual_list_comprehension(checker: &mut Checker, for_stmt: &ast::S
diagnostic.try_set_fix(|| {
convert_to_list_extend(
comprehension_type,
binding,
list_binding,
for_stmt,
if_test.map(std::convert::AsRef::as_ref),
arg,
@ -291,13 +359,31 @@ fn convert_to_list_extend(
to_append: &Expr,
checker: &Checker,
) -> Result<Fix> {
let semantic = checker.semantic();
let locator = checker.locator();
let if_str = match if_test {
Some(test) => format!(" if {}", locator.slice(test.range())),
None => String::new(),
};
let for_iter_str = locator.slice(for_stmt.iter.range());
// if the loop target was an implicit tuple, add parentheses around it
// ```python
// for i in a, b:
// ...
// ```
// becomes
// [... for i in (a, b)]
let for_iter_str = if for_stmt
.iter
.as_ref()
.as_tuple_expr()
.is_some_and(|expr| !expr.parenthesized)
{
format!("({})", locator.slice(for_stmt.iter.range()))
} else {
locator.slice(for_stmt.iter.range()).to_string()
};
let for_type = if for_stmt.is_async {
"async for"
} else {
@ -312,29 +398,40 @@ fn convert_to_list_extend(
.comment_ranges()
.comments_in_range(range)
.iter()
// Ignore comments inside of the append or iterator, since these are preserved
.filter(|comment| {
!to_append.range().contains_range(**comment)
&& !for_stmt.iter.range().contains_range(**comment)
})
.map(|range| locator.slice(range).trim_whitespace_start())
.collect()
};
let for_stmt_end = for_stmt.range.end();
let variable_name = locator.slice(binding);
let for_loop_inline_comments: Vec<&str> = comment_strings_in_range(for_stmt.range);
let for_loop_trailing_comment =
comment_strings_in_range(TextRange::new(for_stmt_end, locator.line_end(for_stmt_end)));
let newline = checker.stylist().line_ending().as_str();
let indent = locator.slice(TextRange::new(
locator.line_start(for_stmt.range.start()),
for_stmt.range.start(),
));
match fix_type {
ComprehensionType::Extend => {
let variable_name = checker.locator().slice(binding.range);
let generator_str = if for_stmt.is_async {
// generators do not implement __iter__, so `async for` requires the generator to be a list
format!("[{generator_str}]")
} else {
generator_str
};
let comprehension_body = format!("{variable_name}.extend({generator_str})");
let indent_range = TextRange::new(
locator.line_start(for_stmt.range.start()),
for_stmt.range.start(),
);
let indentation = if for_loop_inline_comments.is_empty() {
String::new()
} else {
format!("{newline}{}", locator.slice(indent_range))
format!("{newline}{indent}")
};
let text_to_replace = format!(
"{}{indentation}{comprehension_body}",
@ -346,46 +443,98 @@ fn convert_to_list_extend(
)))
}
ComprehensionType::ListComprehension => {
let binding_stmt = binding
.statement(checker.semantic())
.and_then(|stmt| stmt.as_assign_stmt())
let binding_stmt = binding.statement(semantic);
let binding_stmt_range = binding_stmt
.and_then(|stmt| match stmt {
ast::Stmt::AnnAssign(assign) => Some(assign.range),
ast::Stmt::Assign(assign) => Some(assign.range),
_ => None,
})
.ok_or(anyhow!(
"Binding must have a statement to convert into a list comprehension"
))?;
let empty_list_to_replace = binding_stmt.value.as_list_expr().ok_or(anyhow!(
"Assignment value must be an empty list literal in order to replace with a list comprehension"
))?;
// If the binding has multiple statements on its line, the fix would be substantially more complicated
let (semicolon_before, after_semicolon) = {
// determine whether there's a semicolon either before or after the binding statement.
// Since it's a binding statement, we can just check whether there's a semicolon immediately
// after the whitespace in front of or behind it
let mut after_tokenizer =
SimpleTokenizer::starts_at(binding_stmt_range.end(), locator.contents())
.skip_trivia();
let comprehension_body = format!("[{generator_str}]");
let after_semicolon = if after_tokenizer
.next()
.is_some_and(|token| token.kind() == SimpleTokenKind::Semi)
{
after_tokenizer.next()
} else {
None
};
let indent_range = TextRange::new(
locator.line_start(binding_stmt.range.start()),
binding_stmt.range.start(),
);
let semicolon_before = BackwardsTokenizer::up_to(
binding_stmt_range.start(),
locator.contents(),
checker.comment_ranges(),
)
.skip_trivia()
.next()
.filter(|token| token.kind() == SimpleTokenKind::Semi);
let mut for_loop_comments = for_loop_inline_comments;
for_loop_comments.extend(for_loop_trailing_comment);
(semicolon_before, after_semicolon)
};
// If there are multiple binding statements in one line, we don't want to accidentally delete them
// Instead, we just delete the binding statement and leave any comments where they are
let (binding_stmt_deletion_range, binding_is_multiple_stmts) =
match (semicolon_before, after_semicolon) {
// ```python
// a = []
// ```
(None, None) => (locator.full_lines_range(binding_stmt_range), false),
let indentation = if for_loop_comments.is_empty() {
// ```python
// a = 1; b = []
// ^^^^^^^^
// a = 1; b = []; c = 3
// ^^^^^^^^
// ```
(Some(semicolon_before), Some(_) | None) => (
TextRange::new(semicolon_before.start(), binding_stmt_range.end()),
true,
),
// ```python
// a = []; b = 3
// ^^^^^^^
// ```
(None, Some(after_semicolon)) => (
TextRange::new(binding_stmt_range.start(), after_semicolon.start()),
true,
),
};
let annotations = match binding_stmt.and_then(|stmt| stmt.as_ann_assign_stmt()) {
Some(assign) => format!(": {}", locator.slice(assign.annotation.range())),
None => String::new(),
};
let mut comments_to_move = for_loop_inline_comments;
if !binding_is_multiple_stmts {
comments_to_move.extend(comment_strings_in_range(binding_stmt_deletion_range));
}
let indentation = if comments_to_move.is_empty() {
String::new()
} else {
format!("{newline}{}", locator.slice(indent_range))
format!("{newline}{indent}")
};
let leading_comments = format!("{}{indentation}", for_loop_comments.join(&indentation));
let leading_comments = format!("{}{indentation}", comments_to_move.join(&indentation));
let comprehension_body =
format!("{leading_comments}{variable_name}{annotations} = [{generator_str}]");
let mut additional_fixes = vec![Edit::range_deletion(
locator.full_lines_range(for_stmt.range),
)];
// if comments are empty, trying to insert them panics
if !leading_comments.is_empty() {
additional_fixes.push(Edit::insertion(
leading_comments,
binding_stmt.range.start(),
));
}
Ok(Fix::unsafe_edits(
Edit::range_replacement(comprehension_body, empty_list_to_replace.range),
additional_fixes,
Edit::range_deletion(binding_stmt_deletion_range),
[Edit::range_replacement(comprehension_body, for_stmt.range)],
))
}
}

View File

@ -38,38 +38,156 @@ PERF401.py:89:9: PERF401 Use an async list comprehension to create a transformed
|
= help: Replace for loop with list comprehension
PERF401.py:95:9: PERF401 Use `list.extend` to create a transformed list
PERF401.py:96:9: PERF401 Use `list.extend` with an async comprehension to create a transformed list
|
93 | result, _ = [1,2,3,4], ...
94 | for i in range(10):
95 | result.append(i*2) # PERF401
| ^^^^^^^^^^^^^^^^^^ PERF401
94 | result = [1, 2]
95 | async for i in items:
96 | result.append(i) # PERF401
| ^^^^^^^^^^^^^^^^ PERF401
|
= help: Replace for loop with list.extend
PERF401.py:104:17: PERF401 Use `list.extend` to create a transformed list
PERF401.py:102:9: PERF401 Use `list.extend` to create a transformed list
|
102 | # single-line comment 2 should be protected
103 | if i % 2: # single-line comment 3 should be protected
104 | result.append(i) # PERF401
100 | result, _ = [1, 2, 3, 4], ...
101 | for i in range(10):
102 | result.append(i * 2) # PERF401
| ^^^^^^^^^^^^^^^^^^^^ PERF401
|
= help: Replace for loop with list.extend
PERF401.py:111:17: PERF401 Use `list.extend` to create a transformed list
|
109 | # single-line comment 2 should be protected
110 | if i % 2: # single-line comment 3 should be protected
111 | result.append(i) # PERF401
| ^^^^^^^^^^^^^^^^ PERF401
|
= help: Replace for loop with list.extend
PERF401.py:112:13: PERF401 Use a list comprehension to create a transformed list
PERF401.py:119:13: PERF401 Use a list comprehension to create a transformed list
|
110 | # single-line comment 2 should be protected
111 | if i % 2: # single-line comment 3 should be protected
112 | result.append(i) # PERF401
117 | # single-line comment 2 should be protected
118 | if i % 2: # single-line comment 3 should be protected
119 | result.append(i) # PERF401
| ^^^^^^^^^^^^^^^^ PERF401
|
= help: Replace for loop with list comprehension
PERF401.py:127:13: PERF401 Use a list comprehension to create a transformed list
PERF401.py:135:13: PERF401 Use a list comprehension to create a transformed list
|
125 | new_layers = []
126 | for value in param:
127 | new_layers.append(value * 3)
133 | new_layers = []
134 | for value in param:
135 | new_layers.append(value * 3)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PERF401
|
= help: Replace for loop with list comprehension
PERF401.py:142:9: PERF401 Use a list comprehension to create a transformed list
|
140 | var = 1
141 | for _ in range(10):
142 | result.append(var + 1) # PERF401
| ^^^^^^^^^^^^^^^^^^^^^^ PERF401
|
= help: Replace for loop with list comprehension
PERF401.py:149:9: PERF401 Use a list comprehension to create a transformed list
|
147 | tmp = 1; result = [] # commment should be protected
148 | for i in range(10):
149 | result.append(i + 1) # PERF401
| ^^^^^^^^^^^^^^^^^^^^ PERF401
|
= help: Replace for loop with list comprehension
PERF401.py:156:9: PERF401 Use a list comprehension to create a transformed list
|
154 | result = []; tmp = 1 # commment should be protected
155 | for i in range(10):
156 | result.append(i + 1) # PERF401
| ^^^^^^^^^^^^^^^^^^^^ PERF401
|
= help: Replace for loop with list comprehension
PERF401.py:162:9: PERF401 Use a list comprehension to create a transformed list
|
160 | result = [] # comment should be protected
161 | for i in range(10):
162 | result.append(i * 2) # PERF401
| ^^^^^^^^^^^^^^^^^^^^ PERF401
|
= help: Replace for loop with list comprehension
PERF401.py:169:9: PERF401 Use `list.extend` to create a transformed list
|
167 | result.append(1)
168 | for i in range(10):
169 | result.append(i * 2) # PERF401
| ^^^^^^^^^^^^^^^^^^^^ PERF401
|
= help: Replace for loop with list.extend
PERF401.py:189:9: PERF401 Use a list comprehension to create a transformed list
|
187 | result = []
188 | for val in range(5):
189 | result.append(val * 2) # PERF401
| ^^^^^^^^^^^^^^^^^^^^^^ PERF401
190 | val = 1
191 | print(val)
|
= help: Replace for loop with list comprehension
PERF401.py:198:9: PERF401 Use a list comprehension to create a transformed list
|
196 | result = []
197 | for i in i:
198 | result.append(i + 1) # PERF401
| ^^^^^^^^^^^^^^^^^^^^ PERF401
|
= help: Replace for loop with list comprehension
PERF401.py:210:13: PERF401 Use a list comprehension to create a transformed list
|
208 | ): # Comment 3
209 | if i % 2: # Comment 4
210 | result.append(
| _____________^
211 | | (
212 | | i + 1,
213 | | # Comment 5
214 | | 2,
215 | | )
216 | | ) # PERF401
| |_____________^ PERF401
|
= help: Replace for loop with list comprehension
PERF401.py:222:9: PERF401 Use a list comprehension to create a transformed list
|
220 | result: list[int] = []
221 | for i in range(10):
222 | result.append(i * 2) # PERF401
| ^^^^^^^^^^^^^^^^^^^^ PERF401
|
= help: Replace for loop with list comprehension
PERF401.py:229:9: PERF401 Use a list comprehension to create a transformed list
|
227 | result = []
228 | for i in a, b:
229 | result.append(i[0] + i[1]) # PERF401
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ PERF401
230 | return result
|
= help: Replace for loop with list comprehension
PERF401.py:239:9: PERF401 Use a list comprehension to create a transformed list
|
237 | print(a)
238 | for a in values:
239 | result.append(a + 1) # PERF401
| ^^^^^^^^^^^^^^^^^^^^ PERF401
|
= help: Replace for loop with list comprehension

View File

@ -18,11 +18,10 @@ PERF401.py:6:13: PERF401 [*] Use a list comprehension to create a transformed li
4 |- for i in items:
5 |- if i % 2:
6 |- result.append(i) # PERF401
3 |+ # PERF401
4 |+ result = [i for i in items if i % 2]
7 5 |
8 6 |
9 7 | def f():
3 |+ result = [i for i in items if i % 2] # PERF401
7 4 |
8 5 |
9 6 | def f():
PERF401.py:13:9: PERF401 [*] Use a list comprehension to create a transformed list
|
@ -40,11 +39,10 @@ PERF401.py:13:9: PERF401 [*] Use a list comprehension to create a transformed li
11 |- result = []
12 |- for i in items:
13 |- result.append(i * i) # PERF401
11 |+ # PERF401
12 |+ result = [i * i for i in items]
14 13 |
15 14 |
16 15 | def f():
11 |+ result = [i * i for i in items] # PERF401
14 12 |
15 13 |
16 14 | def f():
PERF401.py:82:13: PERF401 [*] Use an async list comprehension to create a transformed list
|
@ -57,17 +55,16 @@ PERF401.py:82:13: PERF401 [*] Use an async list comprehension to create a transf
Unsafe fix
76 76 |
77 77 | def f():
77 77 | async def f():
78 78 | items = [1, 2, 3, 4]
79 |- result = []
80 |- async for i in items:
81 |- if i % 2:
82 |- result.append(i) # PERF401
79 |+ # PERF401
80 |+ result = [i async for i in items if i % 2]
83 81 |
84 82 |
85 83 | def f():
79 |+ result = [i async for i in items if i % 2] # PERF401
83 80 |
84 81 |
85 82 | async def f():
PERF401.py:89:9: PERF401 [*] Use an async list comprehension to create a transformed list
|
@ -80,103 +77,387 @@ PERF401.py:89:9: PERF401 [*] Use an async list comprehension to create a transfo
Unsafe fix
84 84 |
85 85 | def f():
85 85 | async def f():
86 86 | items = [1, 2, 3, 4]
87 |- result = []
88 |- async for i in items:
89 |- result.append(i) # PERF401
87 |+ # PERF401
88 |+ result = [i async for i in items]
90 89 |
91 90 |
92 91 | def f():
87 |+ result = [i async for i in items] # PERF401
90 88 |
91 89 |
92 90 | async def f():
PERF401.py:95:9: PERF401 [*] Use `list.extend` to create a transformed list
PERF401.py:96:9: PERF401 [*] Use `list.extend` with an async comprehension to create a transformed list
|
93 | result, _ = [1,2,3,4], ...
94 | for i in range(10):
95 | result.append(i*2) # PERF401
| ^^^^^^^^^^^^^^^^^^ PERF401
94 | result = [1, 2]
95 | async for i in items:
96 | result.append(i) # PERF401
| ^^^^^^^^^^^^^^^^ PERF401
|
= help: Replace for loop with list.extend
Unsafe fix
91 91 |
92 92 | def f():
93 93 | result, _ = [1,2,3,4], ...
94 |- for i in range(10):
95 |- result.append(i*2) # PERF401
94 |+ result.extend(i*2 for i in range(10)) # PERF401
96 95 |
92 92 | async def f():
93 93 | items = [1, 2, 3, 4]
94 94 | result = [1, 2]
95 |- async for i in items:
96 |- result.append(i) # PERF401
95 |+ result.extend([i async for i in items]) # PERF401
97 96 |
98 97 | def f():
98 97 |
99 98 | def f():
PERF401.py:104:17: PERF401 [*] Use `list.extend` to create a transformed list
PERF401.py:102:9: PERF401 [*] Use `list.extend` to create a transformed list
|
102 | # single-line comment 2 should be protected
103 | if i % 2: # single-line comment 3 should be protected
104 | result.append(i) # PERF401
100 | result, _ = [1, 2, 3, 4], ...
101 | for i in range(10):
102 | result.append(i * 2) # PERF401
| ^^^^^^^^^^^^^^^^^^^^ PERF401
|
= help: Replace for loop with list.extend
Unsafe fix
98 98 |
99 99 | def f():
100 100 | result, _ = [1, 2, 3, 4], ...
101 |- for i in range(10):
102 |- result.append(i * 2) # PERF401
101 |+ result.extend(i * 2 for i in range(10)) # PERF401
103 102 |
104 103 |
105 104 | def f():
PERF401.py:111:17: PERF401 [*] Use `list.extend` to create a transformed list
|
109 | # single-line comment 2 should be protected
110 | if i % 2: # single-line comment 3 should be protected
111 | result.append(i) # PERF401
| ^^^^^^^^^^^^^^^^ PERF401
|
= help: Replace for loop with list.extend
Unsafe fix
98 98 | def f():
99 99 | result = []
100 100 | if True:
101 |- for i in range(10): # single-line comment 1 should be protected
102 |- # single-line comment 2 should be protected
103 |- if i % 2: # single-line comment 3 should be protected
104 |- result.append(i) # PERF401
101 |+ # single-line comment 1 should be protected
102 |+ # single-line comment 2 should be protected
103 |+ # single-line comment 3 should be protected
104 |+ result.extend(i for i in range(10) if i % 2) # PERF401
105 105 |
106 106 |
107 107 | def f():
105 105 | def f():
106 106 | result = []
107 107 | if True:
108 |- for i in range(10): # single-line comment 1 should be protected
109 |- # single-line comment 2 should be protected
110 |- if i % 2: # single-line comment 3 should be protected
111 |- result.append(i) # PERF401
108 |+ # single-line comment 1 should be protected
109 |+ # single-line comment 2 should be protected
110 |+ # single-line comment 3 should be protected
111 |+ result.extend(i for i in range(10) if i % 2) # PERF401
112 112 |
113 113 |
114 114 | def f():
PERF401.py:112:13: PERF401 [*] Use a list comprehension to create a transformed list
PERF401.py:119:13: PERF401 [*] Use a list comprehension to create a transformed list
|
110 | # single-line comment 2 should be protected
111 | if i % 2: # single-line comment 3 should be protected
112 | result.append(i) # PERF401
117 | # single-line comment 2 should be protected
118 | if i % 2: # single-line comment 3 should be protected
119 | result.append(i) # PERF401
| ^^^^^^^^^^^^^^^^ PERF401
|
= help: Replace for loop with list comprehension
Unsafe fix
105 105 |
106 106 |
107 107 | def f():
108 |- result = [] # comment after assignment should be protected
109 |- for i in range(10): # single-line comment 1 should be protected
110 |- # single-line comment 2 should be protected
111 |- if i % 2: # single-line comment 3 should be protected
112 |- result.append(i) # PERF401
108 |+ # single-line comment 1 should be protected
109 |+ # single-line comment 2 should be protected
110 |+ # single-line comment 3 should be protected
111 |+ # PERF401
112 |+ result = [i for i in range(10) if i % 2] # comment after assignment should be protected
112 112 |
113 113 |
114 114 |
115 115 | def f():
114 114 | def f():
115 |- result = [] # comment after assignment should be protected
116 |- for i in range(10): # single-line comment 1 should be protected
117 |- # single-line comment 2 should be protected
118 |- if i % 2: # single-line comment 3 should be protected
119 |- result.append(i) # PERF401
115 |+ # single-line comment 1 should be protected
116 |+ # single-line comment 2 should be protected
117 |+ # single-line comment 3 should be protected
118 |+ # comment after assignment should be protected
119 |+ result = [i for i in range(10) if i % 2] # PERF401
120 120 |
121 121 |
122 122 | def f():
PERF401.py:127:13: PERF401 [*] Use a list comprehension to create a transformed list
PERF401.py:135:13: PERF401 [*] Use a list comprehension to create a transformed list
|
125 | new_layers = []
126 | for value in param:
127 | new_layers.append(value * 3)
133 | new_layers = []
134 | for value in param:
135 | new_layers.append(value * 3)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PERF401
|
= help: Replace for loop with list comprehension
Unsafe fix
122 122 | # PERF401
123 123 | # make sure the fix does not panic if there is no comments
124 124 | if param:
125 |- new_layers = []
126 |- for value in param:
127 |- new_layers.append(value * 3)
125 |+ new_layers = [value * 3 for value in param]
130 130 | # PERF401
131 131 | # make sure the fix does not panic if there is no comments
132 132 | if param:
133 |- new_layers = []
134 |- for value in param:
135 |- new_layers.append(value * 3)
133 |+ new_layers = [value * 3 for value in param]
136 134 |
137 135 |
138 136 | def f():
PERF401.py:142:9: PERF401 [*] Use a list comprehension to create a transformed list
|
140 | var = 1
141 | for _ in range(10):
142 | result.append(var + 1) # PERF401
| ^^^^^^^^^^^^^^^^^^^^^^ PERF401
|
= help: Replace for loop with list comprehension
Unsafe fix
136 136 |
137 137 |
138 138 | def f():
139 |- result = []
140 139 | var = 1
141 |- for _ in range(10):
142 |- result.append(var + 1) # PERF401
140 |+ result = [var + 1 for _ in range(10)] # PERF401
143 141 |
144 142 |
145 143 | def f():
PERF401.py:149:9: PERF401 [*] Use a list comprehension to create a transformed list
|
147 | tmp = 1; result = [] # commment should be protected
148 | for i in range(10):
149 | result.append(i + 1) # PERF401
| ^^^^^^^^^^^^^^^^^^^^ PERF401
|
= help: Replace for loop with list comprehension
Unsafe fix
144 144 |
145 145 | def f():
146 146 | # make sure that `tmp` is not deleted
147 |- tmp = 1; result = [] # commment should be protected
148 |- for i in range(10):
149 |- result.append(i + 1) # PERF401
147 |+ tmp = 1 # commment should be protected
148 |+ result = [i + 1 for i in range(10)] # PERF401
150 149 |
151 150 |
152 151 | def f():
PERF401.py:156:9: PERF401 [*] Use a list comprehension to create a transformed list
|
154 | result = []; tmp = 1 # commment should be protected
155 | for i in range(10):
156 | result.append(i + 1) # PERF401
| ^^^^^^^^^^^^^^^^^^^^ PERF401
|
= help: Replace for loop with list comprehension
Unsafe fix
151 151 |
152 152 | def f():
153 153 | # make sure that `tmp` is not deleted
154 |- result = []; tmp = 1 # commment should be protected
155 |- for i in range(10):
156 |- result.append(i + 1) # PERF401
154 |+ tmp = 1 # commment should be protected
155 |+ result = [i + 1 for i in range(10)] # PERF401
157 156 |
158 157 |
159 158 | def f():
PERF401.py:162:9: PERF401 [*] Use a list comprehension to create a transformed list
|
160 | result = [] # comment should be protected
161 | for i in range(10):
162 | result.append(i * 2) # PERF401
| ^^^^^^^^^^^^^^^^^^^^ PERF401
|
= help: Replace for loop with list comprehension
Unsafe fix
157 157 |
158 158 |
159 159 | def f():
160 |- result = [] # comment should be protected
161 |- for i in range(10):
162 |- result.append(i * 2) # PERF401
160 |+ # comment should be protected
161 |+ result = [i * 2 for i in range(10)] # PERF401
163 162 |
164 163 |
165 164 | def f():
PERF401.py:169:9: PERF401 [*] Use `list.extend` to create a transformed list
|
167 | result.append(1)
168 | for i in range(10):
169 | result.append(i * 2) # PERF401
| ^^^^^^^^^^^^^^^^^^^^ PERF401
|
= help: Replace for loop with list.extend
Unsafe fix
165 165 | def f():
166 166 | result = []
167 167 | result.append(1)
168 |- for i in range(10):
169 |- result.append(i * 2) # PERF401
168 |+ result.extend(i * 2 for i in range(10)) # PERF401
170 169 |
171 170 |
172 171 | def f():
PERF401.py:189:9: PERF401 [*] Use a list comprehension to create a transformed list
|
187 | result = []
188 | for val in range(5):
189 | result.append(val * 2) # PERF401
| ^^^^^^^^^^^^^^^^^^^^^^ PERF401
190 | val = 1
191 | print(val)
|
= help: Replace for loop with list comprehension
Unsafe fix
184 184 |
185 185 |
186 186 | def f():
187 |- result = []
188 |- for val in range(5):
189 |- result.append(val * 2) # PERF401
187 |+ result = [val * 2 for val in range(5)] # PERF401
190 188 | val = 1
191 189 | print(val)
192 190 |
PERF401.py:198:9: PERF401 [*] Use a list comprehension to create a transformed list
|
196 | result = []
197 | for i in i:
198 | result.append(i + 1) # PERF401
| ^^^^^^^^^^^^^^^^^^^^ PERF401
|
= help: Replace for loop with list comprehension
Unsafe fix
193 193 |
194 194 | def f():
195 195 | i = [1, 2, 3]
196 |- result = []
197 |- for i in i:
198 |- result.append(i + 1) # PERF401
196 |+ result = [i + 1 for i in i] # PERF401
199 197 |
200 198 |
201 199 | def f():
PERF401.py:210:13: PERF401 [*] Use a list comprehension to create a transformed list
|
208 | ): # Comment 3
209 | if i % 2: # Comment 4
210 | result.append(
| _____________^
211 | | (
212 | | i + 1,
213 | | # Comment 5
214 | | 2,
215 | | )
216 | | ) # PERF401
| |_____________^ PERF401
|
= help: Replace for loop with list comprehension
Unsafe fix
199 199 |
200 200 |
201 201 | def f():
202 |- result = []
203 |- for i in range( # Comment 1 should not be duplicated
202 |+ # Comment 3
203 |+ # Comment 4
204 |+ result = [(
205 |+ i + 1,
206 |+ # Comment 5
207 |+ 2,
208 |+ ) for i in range( # Comment 1 should not be duplicated
204 209 | (
205 210 | 2 # Comment 2
206 211 | + 1
207 212 | )
208 |- ): # Comment 3
209 |- if i % 2: # Comment 4
210 |- result.append(
211 |- (
212 |- i + 1,
213 |- # Comment 5
214 |- 2,
215 |- )
216 |- ) # PERF401
213 |+ ) if i % 2] # PERF401
217 214 |
218 215 |
219 216 | def f():
PERF401.py:222:9: PERF401 [*] Use a list comprehension to create a transformed list
|
220 | result: list[int] = []
221 | for i in range(10):
222 | result.append(i * 2) # PERF401
| ^^^^^^^^^^^^^^^^^^^^ PERF401
|
= help: Replace for loop with list comprehension
Unsafe fix
217 217 |
218 218 |
219 219 | def f():
220 |- result: list[int] = []
221 |- for i in range(10):
222 |- result.append(i * 2) # PERF401
220 |+ result: list[int] = [i * 2 for i in range(10)] # PERF401
223 221 |
224 222 |
225 223 | def f():
PERF401.py:229:9: PERF401 [*] Use a list comprehension to create a transformed list
|
227 | result = []
228 | for i in a, b:
229 | result.append(i[0] + i[1]) # PERF401
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ PERF401
230 | return result
|
= help: Replace for loop with list comprehension
Unsafe fix
224 224 |
225 225 | def f():
226 226 | a, b = [1, 2, 3], [4, 5, 6]
227 |- result = []
228 |- for i in a, b:
229 |- result.append(i[0] + i[1]) # PERF401
227 |+ result = [i[0] + i[1] for i in (a, b)] # PERF401
230 228 | return result
231 229 |
232 230 |
PERF401.py:239:9: PERF401 [*] Use a list comprehension to create a transformed list
|
237 | print(a)
238 | for a in values:
239 | result.append(a + 1) # PERF401
| ^^^^^^^^^^^^^^^^^^^^ PERF401
|
= help: Replace for loop with list comprehension
Unsafe fix
232 232 |
233 233 | def f():
234 234 | values = [1, 2, 3]
235 |- result = []
236 235 | for a in values:
237 236 | print(a)
238 |- for a in values:
239 |- result.append(a + 1) # PERF401
237 |+ result = [a + 1 for a in values] # PERF401