Redirect `PIE802` to `C419` (#3971)

This commit is contained in:
Charlie Marsh 2023-04-13 22:12:32 -04:00 committed by GitHub
parent 289289bfd3
commit c457752f36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 275 additions and 266 deletions

View File

@ -1,4 +1,3 @@
# PIE802
any([x.id for x in bar]) any([x.id for x in bar])
all([x.id for x in bar]) all([x.id for x in bar])
any( # first comment any( # first comment
@ -15,5 +14,6 @@ all(x.id for x in bar)
any(x.id for x in bar) any(x.id for x in bar)
all((x.id for x in bar)) all((x.id for x in bar))
async def f() -> bool: async def f() -> bool:
return all([await use_greeting(greeting) for greeting in await greetings()]) return all([await use_greeting(greeting) for greeting in await greetings()])

View File

@ -2585,13 +2585,6 @@ where
if self.settings.rules.enabled(Rule::UnnecessaryDictKwargs) { if self.settings.rules.enabled(Rule::UnnecessaryDictKwargs) {
flake8_pie::rules::unnecessary_dict_kwargs(self, expr, keywords); flake8_pie::rules::unnecessary_dict_kwargs(self, expr, keywords);
} }
if self
.settings
.rules
.enabled(Rule::UnnecessaryComprehensionAnyAll)
{
flake8_pie::rules::unnecessary_comprehension_any_all(self, expr, func, args);
}
// flake8-bandit // flake8-bandit
if self.settings.rules.enabled(Rule::ExecBuiltin) { if self.settings.rules.enabled(Rule::ExecBuiltin) {
@ -2792,6 +2785,15 @@ where
args, args,
); );
} }
if self
.settings
.rules
.enabled(Rule::UnnecessaryComprehensionAnyAll)
{
flake8_comprehensions::rules::unnecessary_comprehension_any_all(
self, expr, func, args, keywords,
);
}
// flake8-boolean-trap // flake8-boolean-trap
if self if self

View File

@ -264,6 +264,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Flake8Comprehensions, "16") => Rule::UnnecessaryComprehension, (Flake8Comprehensions, "16") => Rule::UnnecessaryComprehension,
(Flake8Comprehensions, "17") => Rule::UnnecessaryMap, (Flake8Comprehensions, "17") => Rule::UnnecessaryMap,
(Flake8Comprehensions, "18") => Rule::UnnecessaryLiteralWithinDictCall, (Flake8Comprehensions, "18") => Rule::UnnecessaryLiteralWithinDictCall,
(Flake8Comprehensions, "19") => Rule::UnnecessaryComprehensionAnyAll,
// flake8-debugger // flake8-debugger
(Flake8Debugger, "0") => Rule::Debugger, (Flake8Debugger, "0") => Rule::Debugger,
@ -617,7 +618,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Flake8Pie, "794") => Rule::DuplicateClassFieldDefinition, (Flake8Pie, "794") => Rule::DuplicateClassFieldDefinition,
(Flake8Pie, "796") => Rule::NonUniqueEnums, (Flake8Pie, "796") => Rule::NonUniqueEnums,
(Flake8Pie, "800") => Rule::UnnecessarySpread, (Flake8Pie, "800") => Rule::UnnecessarySpread,
(Flake8Pie, "802") => Rule::UnnecessaryComprehensionAnyAll,
(Flake8Pie, "804") => Rule::UnnecessaryDictKwargs, (Flake8Pie, "804") => Rule::UnnecessaryDictKwargs,
(Flake8Pie, "807") => Rule::ReimplementedListBuiltin, (Flake8Pie, "807") => Rule::ReimplementedListBuiltin,
(Flake8Pie, "810") => Rule::MultipleStartsEndsWith, (Flake8Pie, "810") => Rule::MultipleStartsEndsWith,

View File

@ -228,23 +228,24 @@ ruff_macros::register_rules!(
// flake8-blind-except // flake8-blind-except
rules::flake8_blind_except::rules::BlindExcept, rules::flake8_blind_except::rules::BlindExcept,
// flake8-comprehensions // flake8-comprehensions
rules::flake8_comprehensions::rules::UnnecessaryCallAroundSorted,
rules::flake8_comprehensions::rules::UnnecessaryCollectionCall,
rules::flake8_comprehensions::rules::UnnecessaryComprehension,
rules::flake8_comprehensions::rules::UnnecessaryComprehensionAnyAll,
rules::flake8_comprehensions::rules::UnnecessaryDoubleCastOrProcess,
rules::flake8_comprehensions::rules::UnnecessaryGeneratorDict,
rules::flake8_comprehensions::rules::UnnecessaryGeneratorList, rules::flake8_comprehensions::rules::UnnecessaryGeneratorList,
rules::flake8_comprehensions::rules::UnnecessaryGeneratorSet, rules::flake8_comprehensions::rules::UnnecessaryGeneratorSet,
rules::flake8_comprehensions::rules::UnnecessaryGeneratorDict,
rules::flake8_comprehensions::rules::UnnecessaryListComprehensionSet,
rules::flake8_comprehensions::rules::UnnecessaryListComprehensionDict,
rules::flake8_comprehensions::rules::UnnecessaryLiteralSet,
rules::flake8_comprehensions::rules::UnnecessaryLiteralDict,
rules::flake8_comprehensions::rules::UnnecessaryCollectionCall,
rules::flake8_comprehensions::rules::UnnecessaryLiteralWithinTupleCall,
rules::flake8_comprehensions::rules::UnnecessaryLiteralWithinListCall,
rules::flake8_comprehensions::rules::UnnecessaryListCall, rules::flake8_comprehensions::rules::UnnecessaryListCall,
rules::flake8_comprehensions::rules::UnnecessaryCallAroundSorted, rules::flake8_comprehensions::rules::UnnecessaryListComprehensionDict,
rules::flake8_comprehensions::rules::UnnecessaryDoubleCastOrProcess, rules::flake8_comprehensions::rules::UnnecessaryListComprehensionSet,
rules::flake8_comprehensions::rules::UnnecessarySubscriptReversal, rules::flake8_comprehensions::rules::UnnecessaryLiteralDict,
rules::flake8_comprehensions::rules::UnnecessaryComprehension, rules::flake8_comprehensions::rules::UnnecessaryLiteralSet,
rules::flake8_comprehensions::rules::UnnecessaryMap,
rules::flake8_comprehensions::rules::UnnecessaryLiteralWithinDictCall, rules::flake8_comprehensions::rules::UnnecessaryLiteralWithinDictCall,
rules::flake8_comprehensions::rules::UnnecessaryLiteralWithinListCall,
rules::flake8_comprehensions::rules::UnnecessaryLiteralWithinTupleCall,
rules::flake8_comprehensions::rules::UnnecessaryMap,
rules::flake8_comprehensions::rules::UnnecessarySubscriptReversal,
// flake8-debugger // flake8-debugger
rules::flake8_debugger::rules::Debugger, rules::flake8_debugger::rules::Debugger,
// mccabe // mccabe
@ -570,7 +571,6 @@ ruff_macros::register_rules!(
rules::flake8_pie::rules::UnnecessaryDictKwargs, rules::flake8_pie::rules::UnnecessaryDictKwargs,
rules::flake8_pie::rules::ReimplementedListBuiltin, rules::flake8_pie::rules::ReimplementedListBuiltin,
rules::flake8_pie::rules::MultipleStartsEndsWith, rules::flake8_pie::rules::MultipleStartsEndsWith,
rules::flake8_pie::rules::UnnecessaryComprehensionAnyAll,
// flake8-commas // flake8-commas
rules::flake8_commas::rules::MissingTrailingComma, rules::flake8_commas::rules::MissingTrailingComma,
rules::flake8_commas::rules::TrailingCommaOnBareTuple, rules::flake8_commas::rules::TrailingCommaOnBareTuple,

View File

@ -92,5 +92,6 @@ static REDIRECTS: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
("TYP001", "TCH001"), ("TYP001", "TCH001"),
// TODO(charlie): Remove by 2023-06-01. // TODO(charlie): Remove by 2023-06-01.
("RUF004", "B026"), ("RUF004", "B026"),
("PIE802", "C419"),
]) ])
}); });

View File

@ -1201,3 +1201,47 @@ pub fn fix_unnecessary_literal_within_dict_call(
expr.end_location.unwrap(), expr.end_location.unwrap(),
)) ))
} }
/// (C419) Convert `[i for i in a]` into `i for i in a`
pub fn fix_unnecessary_comprehension_any_all(
locator: &Locator,
stylist: &Stylist,
expr: &rustpython_parser::ast::Expr,
) -> Result<Edit> {
// Expr(ListComp) -> Expr(GeneratorExp)
let module_text = locator.slice(expr);
let mut tree = match_module(module_text)?;
let body = match_expr(&mut tree)?;
let call = match_call(body)?;
let Expression::ListComp(list_comp) = &call.args[0].value else {
bail!(
"Expected Expression::ListComp"
);
};
call.args[0].value = Expression::GeneratorExp(Box::new(GeneratorExp {
elt: list_comp.elt.clone(),
for_in: list_comp.for_in.clone(),
lpar: list_comp.lpar.clone(),
rpar: list_comp.rpar.clone(),
}));
if let Some(comma) = &call.args[0].comma {
call.args[0].whitespace_after_arg = comma.whitespace_after.clone();
call.args[0].comma = None;
}
let mut state = CodegenState {
default_newline: &stylist.line_ending(),
default_indent: stylist.indentation(),
..CodegenState::default()
};
tree.codegen(&mut state);
Ok(Edit::replacement(
state.to_string(),
expr.location,
expr.end_location.unwrap(),
))
}

View File

@ -16,23 +16,24 @@ mod tests {
use crate::settings::Settings; use crate::settings::Settings;
use crate::test::test_path; use crate::test::test_path;
#[test_case(Rule::UnnecessaryCallAroundSorted, Path::new("C413.py"); "C413")]
#[test_case(Rule::UnnecessaryCollectionCall, Path::new("C408.py"); "C408")]
#[test_case(Rule::UnnecessaryComprehension, Path::new("C416.py"); "C416")]
#[test_case(Rule::UnnecessaryComprehensionAnyAll, Path::new("C419.py"); "C419")]
#[test_case(Rule::UnnecessaryDoubleCastOrProcess, Path::new("C414.py"); "C414")]
#[test_case(Rule::UnnecessaryGeneratorDict, Path::new("C402.py"); "C402")]
#[test_case(Rule::UnnecessaryGeneratorList, Path::new("C400.py"); "C400")] #[test_case(Rule::UnnecessaryGeneratorList, Path::new("C400.py"); "C400")]
#[test_case(Rule::UnnecessaryGeneratorSet, Path::new("C401.py"); "C401")] #[test_case(Rule::UnnecessaryGeneratorSet, Path::new("C401.py"); "C401")]
#[test_case(Rule::UnnecessaryGeneratorDict, Path::new("C402.py"); "C402")]
#[test_case(Rule::UnnecessaryListComprehensionSet, Path::new("C403.py"); "C403")]
#[test_case(Rule::UnnecessaryListComprehensionDict, Path::new("C404.py"); "C404")]
#[test_case(Rule::UnnecessaryLiteralSet, Path::new("C405.py"); "C405")]
#[test_case(Rule::UnnecessaryLiteralDict, Path::new("C406.py"); "C406")]
#[test_case(Rule::UnnecessaryCollectionCall, Path::new("C408.py"); "C408")]
#[test_case(Rule::UnnecessaryLiteralWithinTupleCall, Path::new("C409.py"); "C409")]
#[test_case(Rule::UnnecessaryLiteralWithinListCall, Path::new("C410.py"); "C410")]
#[test_case(Rule::UnnecessaryListCall, Path::new("C411.py"); "C411")] #[test_case(Rule::UnnecessaryListCall, Path::new("C411.py"); "C411")]
#[test_case(Rule::UnnecessaryCallAroundSorted, Path::new("C413.py"); "C413")] #[test_case(Rule::UnnecessaryListComprehensionDict, Path::new("C404.py"); "C404")]
#[test_case(Rule::UnnecessaryDoubleCastOrProcess, Path::new("C414.py"); "C414")] #[test_case(Rule::UnnecessaryListComprehensionSet, Path::new("C403.py"); "C403")]
#[test_case(Rule::UnnecessarySubscriptReversal, Path::new("C415.py"); "C415")] #[test_case(Rule::UnnecessaryLiteralDict, Path::new("C406.py"); "C406")]
#[test_case(Rule::UnnecessaryComprehension, Path::new("C416.py"); "C416")] #[test_case(Rule::UnnecessaryLiteralSet, Path::new("C405.py"); "C405")]
#[test_case(Rule::UnnecessaryMap, Path::new("C417.py"); "C417")]
#[test_case(Rule::UnnecessaryLiteralWithinDictCall, Path::new("C418.py"); "C418")] #[test_case(Rule::UnnecessaryLiteralWithinDictCall, Path::new("C418.py"); "C418")]
#[test_case(Rule::UnnecessaryLiteralWithinListCall, Path::new("C410.py"); "C410")]
#[test_case(Rule::UnnecessaryLiteralWithinTupleCall, Path::new("C409.py"); "C409")]
#[test_case(Rule::UnnecessaryMap, Path::new("C417.py"); "C417")]
#[test_case(Rule::UnnecessarySubscriptReversal, Path::new("C415.py"); "C415")]
fn rules(rule_code: Rule, path: &Path) -> Result<()> { fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path( let diagnostics = test_path(

View File

@ -5,6 +5,9 @@ pub use unnecessary_collection_call::{unnecessary_collection_call, UnnecessaryCo
pub use unnecessary_comprehension::{ pub use unnecessary_comprehension::{
unnecessary_dict_comprehension, unnecessary_list_set_comprehension, UnnecessaryComprehension, unnecessary_dict_comprehension, unnecessary_list_set_comprehension, UnnecessaryComprehension,
}; };
pub use unnecessary_comprehension_any_all::{
unnecessary_comprehension_any_all, UnnecessaryComprehensionAnyAll,
};
pub use unnecessary_double_cast_or_process::{ pub use unnecessary_double_cast_or_process::{
unnecessary_double_cast_or_process, UnnecessaryDoubleCastOrProcess, unnecessary_double_cast_or_process, UnnecessaryDoubleCastOrProcess,
}; };
@ -38,6 +41,7 @@ mod helpers;
mod unnecessary_call_around_sorted; mod unnecessary_call_around_sorted;
mod unnecessary_collection_call; mod unnecessary_collection_call;
mod unnecessary_comprehension; mod unnecessary_comprehension;
mod unnecessary_comprehension_any_all;
mod unnecessary_double_cast_or_process; mod unnecessary_double_cast_or_process;
mod unnecessary_generator_dict; mod unnecessary_generator_dict;
mod unnecessary_generator_list; mod unnecessary_generator_list;

View File

@ -0,0 +1,94 @@
use rustpython_parser::ast::{Expr, ExprKind, Keyword};
use ruff_diagnostics::AlwaysAutofixableViolation;
use ruff_diagnostics::Diagnostic;
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::any_over_expr;
use ruff_python_ast::types::Range;
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
use crate::rules::flake8_comprehensions::fixes;
/// ## What it does
/// Checks for unnecessary list comprehensions passed to `any` and `all`.
///
/// ## Why is this bad?
/// `any` and `all` take any iterators, including generators. Converting a generator to a list
/// by way of a list comprehension is unnecessary and reduces performance due to the
/// overhead of creating the list.
///
/// For example, compare the performance of `all` with a list comprehension against that
/// of a generator (~40x faster here):
///
/// ```console
/// In [1]: %timeit all([i for i in range(1000)])
/// 8.14 µs ± 25.4 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
///
/// In [2]: %timeit all(i for i in range(1000))
/// 212 ns ± 0.892 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
/// ```
///
/// ## Examples
/// ```python
/// any([x.id for x in bar])
/// all([x.id for x in bar])
/// ```
///
/// Use instead:
/// ```python
/// any(x.id for x in bar)
/// all(x.id for x in bar)
/// ```
#[violation]
pub struct UnnecessaryComprehensionAnyAll;
impl AlwaysAutofixableViolation for UnnecessaryComprehensionAnyAll {
#[derive_message_formats]
fn message(&self) -> String {
format!("Unnecessary list comprehension.")
}
fn autofix_title(&self) -> String {
"Remove unnecessary list comprehension".to_string()
}
}
/// C419
pub fn unnecessary_comprehension_any_all(
checker: &mut Checker,
expr: &Expr,
func: &Expr,
args: &[Expr],
keywords: &[Keyword],
) {
if !keywords.is_empty() {
return;
}
let ExprKind::Name { id, .. } = &func.node else {
return;
};
if (matches!(id.as_str(), "all" | "any")) && args.len() == 1 {
let (ExprKind::ListComp { elt, .. } | ExprKind::SetComp { elt, .. }) = &args[0].node else {
return;
};
if is_async_generator(elt) {
return;
}
if !checker.ctx.is_builtin(id) {
return;
}
let mut diagnostic = Diagnostic::new(UnnecessaryComprehensionAnyAll, Range::from(&args[0]));
if checker.patch(diagnostic.kind.rule()) {
diagnostic.try_set_fix(|| {
fixes::fix_unnecessary_comprehension_any_all(checker.locator, checker.stylist, expr)
});
}
checker.diagnostics.push(diagnostic);
}
}
/// Return `true` if the `Expr` contains an `await` expression.
fn is_async_generator(expr: &Expr) -> bool {
any_over_expr(expr, &|expr| matches!(expr.node, ExprKind::Await { .. }))
}

View File

@ -0,0 +1,91 @@
---
source: crates/ruff/src/rules/flake8_comprehensions/mod.rs
---
C419.py:1:5: C419 [*] Unnecessary list comprehension.
|
1 | any([x.id for x in bar])
| ^^^^^^^^^^^^^^^^^^^ C419
2 | all([x.id for x in bar])
3 | any( # first comment
|
= help: Remove unnecessary list comprehension
Suggested fix
1 |-any([x.id for x in bar])
1 |+any(x.id for x in bar)
2 2 | all([x.id for x in bar])
3 3 | any( # first comment
4 4 | [x.id for x in bar], # second comment
C419.py:2:5: C419 [*] Unnecessary list comprehension.
|
2 | any([x.id for x in bar])
3 | all([x.id for x in bar])
| ^^^^^^^^^^^^^^^^^^^ C419
4 | any( # first comment
5 | [x.id for x in bar], # second comment
|
= help: Remove unnecessary list comprehension
Suggested fix
1 1 | any([x.id for x in bar])
2 |-all([x.id for x in bar])
2 |+all(x.id for x in bar)
3 3 | any( # first comment
4 4 | [x.id for x in bar], # second comment
5 5 | ) # third comment
C419.py:4:5: C419 [*] Unnecessary list comprehension.
|
4 | all([x.id for x in bar])
5 | any( # first comment
6 | [x.id for x in bar], # second comment
| ^^^^^^^^^^^^^^^^^^^ C419
7 | ) # third comment
8 | all( # first comment
|
= help: Remove unnecessary list comprehension
Suggested fix
1 1 | any([x.id for x in bar])
2 2 | all([x.id for x in bar])
3 3 | any( # first comment
4 |- [x.id for x in bar], # second comment
4 |+ x.id for x in bar # second comment
5 5 | ) # third comment
6 6 | all( # first comment
7 7 | [x.id for x in bar], # second comment
C419.py:7:5: C419 [*] Unnecessary list comprehension.
|
7 | ) # third comment
8 | all( # first comment
9 | [x.id for x in bar], # second comment
| ^^^^^^^^^^^^^^^^^^^ C419
10 | ) # third comment
11 | any({x.id for x in bar})
|
= help: Remove unnecessary list comprehension
Suggested fix
4 4 | [x.id for x in bar], # second comment
5 5 | ) # third comment
6 6 | all( # first comment
7 |- [x.id for x in bar], # second comment
7 |+ x.id for x in bar # second comment
8 8 | ) # third comment
9 9 | any({x.id for x in bar})
10 10 |
C419.py:9:5: C419 [*] Unnecessary list comprehension.
|
9 | [x.id for x in bar], # second comment
10 | ) # third comment
11 | any({x.id for x in bar})
| ^^^^^^^^^^^^^^^^^^^ C419
12 |
13 | # OK
|
= help: Remove unnecessary list comprehension

View File

@ -1,50 +0,0 @@
use anyhow::{bail, Result};
use libcst_native::{Codegen, CodegenState, Expression, GeneratorExp};
use ruff_diagnostics::Edit;
use ruff_python_ast::source_code::{Locator, Stylist};
use crate::cst::matchers::{match_call, match_expression};
/// (PIE802) Convert `[i for i in a]` into `i for i in a`
pub fn fix_unnecessary_comprehension_any_all(
locator: &Locator,
stylist: &Stylist,
expr: &rustpython_parser::ast::Expr,
) -> Result<Edit> {
// Expr(ListComp) -> Expr(GeneratorExp)
let expression_text = locator.slice(expr);
let mut tree = match_expression(expression_text)?;
let call = match_call(&mut tree)?;
let Expression::ListComp(list_comp) = &call.args[0].value else {
bail!(
"Expected Expression::ListComp"
);
};
call.args[0].value = Expression::GeneratorExp(Box::new(GeneratorExp {
elt: list_comp.elt.clone(),
for_in: list_comp.for_in.clone(),
lpar: list_comp.lpar.clone(),
rpar: list_comp.rpar.clone(),
}));
if let Some(comma) = &call.args[0].comma {
call.args[0].whitespace_after_arg = comma.whitespace_after.clone();
call.args[0].comma = None;
}
let mut state = CodegenState {
default_newline: &stylist.line_ending(),
default_indent: stylist.indentation(),
..CodegenState::default()
};
tree.codegen(&mut state);
Ok(Edit::replacement(
state.to_string(),
expr.location,
expr.end_location.unwrap(),
))
}

View File

@ -1,5 +1,4 @@
//! Rules from [flake8-pie](https://pypi.org/project/flake8-pie/). //! Rules from [flake8-pie](https://pypi.org/project/flake8-pie/).
mod fixes;
pub(crate) mod rules; pub(crate) mod rules;
#[cfg(test)] #[cfg(test)]
@ -21,7 +20,6 @@ mod tests {
#[test_case(Rule::UnnecessarySpread, Path::new("PIE800.py"); "PIE800")] #[test_case(Rule::UnnecessarySpread, Path::new("PIE800.py"); "PIE800")]
#[test_case(Rule::ReimplementedListBuiltin, Path::new("PIE807.py"); "PIE807")] #[test_case(Rule::ReimplementedListBuiltin, Path::new("PIE807.py"); "PIE807")]
#[test_case(Rule::NonUniqueEnums, Path::new("PIE796.py"); "PIE796")] #[test_case(Rule::NonUniqueEnums, Path::new("PIE796.py"); "PIE796")]
#[test_case(Rule::UnnecessaryComprehensionAnyAll, Path::new("PIE802.py"); "PIE802")]
fn rules(rule_code: Rule, path: &Path) -> Result<()> { fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path( let diagnostics = test_path(

View File

@ -12,7 +12,7 @@ use ruff_diagnostics::{AlwaysAutofixableViolation, Violation};
use ruff_diagnostics::{Diagnostic, Edit}; use ruff_diagnostics::{Diagnostic, Edit};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::comparable::ComparableExpr; use ruff_python_ast::comparable::ComparableExpr;
use ruff_python_ast::helpers::{any_over_expr, create_expr, match_trailing_comment, unparse_expr}; use ruff_python_ast::helpers::{create_expr, match_trailing_comment, unparse_expr};
use ruff_python_ast::types::{Range, RefEquality}; use ruff_python_ast::types::{Range, RefEquality};
use ruff_python_stdlib::identifiers::is_identifier; use ruff_python_stdlib::identifiers::is_identifier;
@ -21,8 +21,6 @@ use crate::checkers::ast::Checker;
use crate::message::Location; use crate::message::Location;
use crate::registry::AsRule; use crate::registry::AsRule;
use super::fixes;
#[violation] #[violation]
pub struct UnnecessaryPass; pub struct UnnecessaryPass;
@ -66,50 +64,6 @@ impl Violation for NonUniqueEnums {
} }
} }
/// ## What it does
/// Checks for unnecessary list comprehensions passed to `any` and `all`.
///
/// ## Why is this bad?
/// `any` and `all` take any iterators, including generators. Converting a generator to a list
/// by way of a list comprehension is unnecessary and reduces performance due to the
/// overhead of creating the list.
///
/// For example, compare the performance of `all` with a list comprehension against that
/// of a generator (~40x faster here):
///
/// ```console
/// In [1]: %timeit all([i for i in range(1000)])
/// 8.14 µs ± 25.4 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
///
/// In [2]: %timeit all(i for i in range(1000))
/// 212 ns ± 0.892 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
/// ```
///
/// ## Examples
/// ```python
/// any([x.id for x in bar])
/// all([x.id for x in bar])
/// ```
///
/// Use instead:
/// ```python
/// any(x.id for x in bar)
/// all(x.id for x in bar)
/// ```
#[violation]
pub struct UnnecessaryComprehensionAnyAll;
impl AlwaysAutofixableViolation for UnnecessaryComprehensionAnyAll {
#[derive_message_formats]
fn message(&self) -> String {
format!("Unnecessary list comprehension.")
}
fn autofix_title(&self) -> String {
"Remove unnecessary list comprehension".to_string()
}
}
#[violation] #[violation]
pub struct UnnecessarySpread; pub struct UnnecessarySpread;
@ -331,41 +285,6 @@ pub fn unnecessary_spread(checker: &mut Checker, keys: &[Option<Expr>], values:
} }
} }
/// Return `true` if the `Expr` contains an `await` expression.
fn is_async_generator(expr: &Expr) -> bool {
any_over_expr(expr, &|expr| matches!(expr.node, ExprKind::Await { .. }))
}
/// PIE802
pub fn unnecessary_comprehension_any_all(
checker: &mut Checker,
expr: &Expr,
func: &Expr,
args: &[Expr],
) {
let ExprKind::Name { id, .. } = &func.node else {
return;
};
if (matches!(id.as_str(), "all" | "any")) && args.len() == 1 {
let (ExprKind::ListComp { elt, .. } | ExprKind::SetComp { elt, .. }) = &args[0].node else {
return;
};
if is_async_generator(elt) {
return;
}
if !checker.ctx.is_builtin(id) {
return;
}
let mut diagnostic = Diagnostic::new(UnnecessaryComprehensionAnyAll, Range::from(&args[0]));
if checker.patch(diagnostic.kind.rule()) {
diagnostic.try_set_fix(|| {
fixes::fix_unnecessary_comprehension_any_all(checker.locator, checker.stylist, expr)
});
}
checker.diagnostics.push(diagnostic);
}
}
/// Return `true` if a key is a valid keyword argument name. /// Return `true` if a key is a valid keyword argument name.
fn is_valid_kwarg_name(key: &Expr) -> bool { fn is_valid_kwarg_name(key: &Expr) -> bool {
if let ExprKind::Constant { if let ExprKind::Constant {

View File

@ -1,95 +0,0 @@
---
source: crates/ruff/src/rules/flake8_pie/mod.rs
---
PIE802.py:2:5: PIE802 [*] Unnecessary list comprehension.
|
2 | # PIE802
3 | any([x.id for x in bar])
| ^^^^^^^^^^^^^^^^^^^ PIE802
4 | all([x.id for x in bar])
5 | any( # first comment
|
= help: Remove unnecessary list comprehension
Suggested fix
1 1 | # PIE802
2 |-any([x.id for x in bar])
2 |+any(x.id for x in bar)
3 3 | all([x.id for x in bar])
4 4 | any( # first comment
5 5 | [x.id for x in bar], # second comment
PIE802.py:3:5: PIE802 [*] Unnecessary list comprehension.
|
3 | # PIE802
4 | any([x.id for x in bar])
5 | all([x.id for x in bar])
| ^^^^^^^^^^^^^^^^^^^ PIE802
6 | any( # first comment
7 | [x.id for x in bar], # second comment
|
= help: Remove unnecessary list comprehension
Suggested fix
1 1 | # PIE802
2 2 | any([x.id for x in bar])
3 |-all([x.id for x in bar])
3 |+all(x.id for x in bar)
4 4 | any( # first comment
5 5 | [x.id for x in bar], # second comment
6 6 | ) # third comment
PIE802.py:5:5: PIE802 [*] Unnecessary list comprehension.
|
5 | all([x.id for x in bar])
6 | any( # first comment
7 | [x.id for x in bar], # second comment
| ^^^^^^^^^^^^^^^^^^^ PIE802
8 | ) # third comment
9 | all( # first comment
|
= help: Remove unnecessary list comprehension
Suggested fix
2 2 | any([x.id for x in bar])
3 3 | all([x.id for x in bar])
4 4 | any( # first comment
5 |- [x.id for x in bar], # second comment
5 |+ x.id for x in bar # second comment
6 6 | ) # third comment
7 7 | all( # first comment
8 8 | [x.id for x in bar], # second comment
PIE802.py:8:5: PIE802 [*] Unnecessary list comprehension.
|
8 | ) # third comment
9 | all( # first comment
10 | [x.id for x in bar], # second comment
| ^^^^^^^^^^^^^^^^^^^ PIE802
11 | ) # third comment
12 | any({x.id for x in bar})
|
= help: Remove unnecessary list comprehension
Suggested fix
5 5 | [x.id for x in bar], # second comment
6 6 | ) # third comment
7 7 | all( # first comment
8 |- [x.id for x in bar], # second comment
8 |+ x.id for x in bar # second comment
9 9 | ) # third comment
10 10 | any({x.id for x in bar})
11 11 |
PIE802.py:10:5: PIE802 [*] Unnecessary list comprehension.
|
10 | [x.id for x in bar], # second comment
11 | ) # third comment
12 | any({x.id for x in bar})
| ^^^^^^^^^^^^^^^^^^^ PIE802
13 |
14 | # OK
|
= help: Remove unnecessary list comprehension

2
ruff.schema.json generated
View File

@ -1605,6 +1605,7 @@
"C416", "C416",
"C417", "C417",
"C418", "C418",
"C419",
"C9", "C9",
"C90", "C90",
"C901", "C901",
@ -1920,7 +1921,6 @@
"PIE8", "PIE8",
"PIE80", "PIE80",
"PIE800", "PIE800",
"PIE802",
"PIE804", "PIE804",
"PIE807", "PIE807",
"PIE81", "PIE81",