mirror of https://github.com/astral-sh/ruff
Add Pylint rule `C0208` (`use-sequence-for-iteration`) as `PLC0208` (`iteration-over-set`) (#4706)
This commit is contained in:
parent
ab26f2dc9d
commit
bdff4a66ac
|
|
@ -0,0 +1,38 @@
|
||||||
|
# Errors
|
||||||
|
|
||||||
|
for item in {"apples", "lemons", "water"}: # flags in-line set literals
|
||||||
|
print(f"I like {item}.")
|
||||||
|
|
||||||
|
for item in set(("apples", "lemons", "water")): # flags set() calls
|
||||||
|
print(f"I like {item}.")
|
||||||
|
|
||||||
|
for number in {i for i in range(10)}: # flags set comprehensions
|
||||||
|
print(number)
|
||||||
|
|
||||||
|
numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
|
||||||
|
|
||||||
|
numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
||||||
|
|
||||||
|
numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
|
||||||
|
|
||||||
|
numbers_gen = (i for i in {1, 2, 3}) # flags sets in generator expressions
|
||||||
|
|
||||||
|
# Non-errors
|
||||||
|
|
||||||
|
items = {"apples", "lemons", "water"}
|
||||||
|
for item in items: # only complains about in-line sets (as per Pylint)
|
||||||
|
print(f"I like {item}.")
|
||||||
|
|
||||||
|
for item in ["apples", "lemons", "water"]: # lists are fine
|
||||||
|
print(f"I like {item}.")
|
||||||
|
|
||||||
|
for item in ("apples", "lemons", "water"): # tuples are fine
|
||||||
|
print(f"I like {item}.")
|
||||||
|
|
||||||
|
numbers_list = [i for i in [1, 2, 3]] # lists in comprehensions are fine
|
||||||
|
|
||||||
|
numbers_set = {i for i in (1, 2, 3)} # tuples in comprehensions are fine
|
||||||
|
|
||||||
|
numbers_dict = {str(i): i for i in [1, 2, 3]} # lists in dict comprehensions are fine
|
||||||
|
|
||||||
|
numbers_gen = (i for i in (1, 2, 3)) # tuples in generator expressions are fine
|
||||||
|
|
@ -1554,6 +1554,9 @@ where
|
||||||
if self.enabled(Rule::RedefinedLoopName) {
|
if self.enabled(Rule::RedefinedLoopName) {
|
||||||
pylint::rules::redefined_loop_name(self, &Node::Stmt(stmt));
|
pylint::rules::redefined_loop_name(self, &Node::Stmt(stmt));
|
||||||
}
|
}
|
||||||
|
if self.enabled(Rule::IterationOverSet) {
|
||||||
|
pylint::rules::iteration_over_set(self, iter);
|
||||||
|
}
|
||||||
if matches!(stmt, Stmt::For(_)) {
|
if matches!(stmt, Stmt::For(_)) {
|
||||||
if self.enabled(Rule::ReimplementedBuiltin) {
|
if self.enabled(Rule::ReimplementedBuiltin) {
|
||||||
flake8_simplify::rules::convert_for_loop_to_any_all(
|
flake8_simplify::rules::convert_for_loop_to_any_all(
|
||||||
|
|
@ -3502,6 +3505,11 @@ where
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if self.enabled(Rule::IterationOverSet) {
|
||||||
|
for generator in generators {
|
||||||
|
pylint::rules::iteration_over_set(self, &generator.iter);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Expr::DictComp(ast::ExprDictComp {
|
Expr::DictComp(ast::ExprDictComp {
|
||||||
key,
|
key,
|
||||||
|
|
@ -3526,6 +3534,11 @@ where
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if self.enabled(Rule::IterationOverSet) {
|
||||||
|
for generator in generators {
|
||||||
|
pylint::rules::iteration_over_set(self, &generator.iter);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Expr::GeneratorExp(ast::ExprGeneratorExp {
|
Expr::GeneratorExp(ast::ExprGeneratorExp {
|
||||||
generators,
|
generators,
|
||||||
|
|
@ -3544,6 +3557,11 @@ where
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if self.enabled(Rule::IterationOverSet) {
|
||||||
|
for generator in generators {
|
||||||
|
pylint::rules::iteration_over_set(self, &generator.iter);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Expr::BoolOp(ast::ExprBoolOp {
|
Expr::BoolOp(ast::ExprBoolOp {
|
||||||
op,
|
op,
|
||||||
|
|
|
||||||
|
|
@ -150,6 +150,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||||
(Pylint, "C0414") => (RuleGroup::Unspecified, Rule::UselessImportAlias),
|
(Pylint, "C0414") => (RuleGroup::Unspecified, Rule::UselessImportAlias),
|
||||||
(Pylint, "C1901") => (RuleGroup::Unspecified, Rule::CompareToEmptyString),
|
(Pylint, "C1901") => (RuleGroup::Unspecified, Rule::CompareToEmptyString),
|
||||||
(Pylint, "C3002") => (RuleGroup::Unspecified, Rule::UnnecessaryDirectLambdaCall),
|
(Pylint, "C3002") => (RuleGroup::Unspecified, Rule::UnnecessaryDirectLambdaCall),
|
||||||
|
(Pylint, "C0208") => (RuleGroup::Unspecified, Rule::IterationOverSet),
|
||||||
(Pylint, "E0100") => (RuleGroup::Unspecified, Rule::YieldInInit),
|
(Pylint, "E0100") => (RuleGroup::Unspecified, Rule::YieldInInit),
|
||||||
(Pylint, "E0101") => (RuleGroup::Unspecified, Rule::ReturnInInit),
|
(Pylint, "E0101") => (RuleGroup::Unspecified, Rule::ReturnInInit),
|
||||||
(Pylint, "E0116") => (RuleGroup::Unspecified, Rule::ContinueInFinally),
|
(Pylint, "E0116") => (RuleGroup::Unspecified, Rule::ContinueInFinally),
|
||||||
|
|
|
||||||
|
|
@ -163,6 +163,7 @@ ruff_macros::register_rules!(
|
||||||
rules::pylint::rules::DuplicateValue,
|
rules::pylint::rules::DuplicateValue,
|
||||||
rules::pylint::rules::DuplicateBases,
|
rules::pylint::rules::DuplicateBases,
|
||||||
rules::pylint::rules::NamedExprWithoutContext,
|
rules::pylint::rules::NamedExprWithoutContext,
|
||||||
|
rules::pylint::rules::IterationOverSet,
|
||||||
// flake8-async
|
// flake8-async
|
||||||
rules::flake8_async::rules::BlockingHttpCallInAsyncFunction,
|
rules::flake8_async::rules::BlockingHttpCallInAsyncFunction,
|
||||||
rules::flake8_async::rules::OpenSleepOrSubprocessInAsyncFunction,
|
rules::flake8_async::rules::OpenSleepOrSubprocessInAsyncFunction,
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,7 @@ mod tests {
|
||||||
)]
|
)]
|
||||||
#[test_case(Rule::InvalidEnvvarDefault, Path::new("invalid_envvar_default.py"))]
|
#[test_case(Rule::InvalidEnvvarDefault, Path::new("invalid_envvar_default.py"))]
|
||||||
#[test_case(Rule::InvalidEnvvarValue, Path::new("invalid_envvar_value.py"))]
|
#[test_case(Rule::InvalidEnvvarValue, Path::new("invalid_envvar_value.py"))]
|
||||||
|
#[test_case(Rule::IterationOverSet, Path::new("iteration_over_set.py"))]
|
||||||
#[test_case(Rule::LoggingTooFewArgs, Path::new("logging_too_few_args.py"))]
|
#[test_case(Rule::LoggingTooFewArgs, Path::new("logging_too_few_args.py"))]
|
||||||
#[test_case(Rule::LoggingTooManyArgs, Path::new("logging_too_many_args.py"))]
|
#[test_case(Rule::LoggingTooManyArgs, Path::new("logging_too_many_args.py"))]
|
||||||
#[test_case(Rule::MagicValueComparison, Path::new("magic_value_comparison.py"))]
|
#[test_case(Rule::MagicValueComparison, Path::new("magic_value_comparison.py"))]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
use rustpython_parser::ast::{Expr, ExprName, Ranged};
|
||||||
|
|
||||||
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
|
||||||
|
use crate::checkers::ast::Checker;
|
||||||
|
|
||||||
|
/// ## What it does
|
||||||
|
/// Checks for iterations over `set` literals and comprehensions.
|
||||||
|
///
|
||||||
|
/// ## Why is this bad?
|
||||||
|
/// Iterating over a `set` is less efficient than iterating over a sequence
|
||||||
|
/// type, like `list` or `tuple`.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```python
|
||||||
|
/// for number in {1, 2, 3}:
|
||||||
|
/// ...
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Use instead:
|
||||||
|
/// ```python
|
||||||
|
/// for number in (1, 2, 3):
|
||||||
|
/// ...
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## References
|
||||||
|
/// - [Python documentation: `set`](https://docs.python.org/3/library/stdtypes.html#set)
|
||||||
|
#[violation]
|
||||||
|
pub struct IterationOverSet;
|
||||||
|
|
||||||
|
impl Violation for IterationOverSet {
|
||||||
|
#[derive_message_formats]
|
||||||
|
fn message(&self) -> String {
|
||||||
|
format!("Use a sequence type instead of a `set` when iterating over values")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// PLC0208
|
||||||
|
pub(crate) fn iteration_over_set(checker: &mut Checker, expr: &Expr) {
|
||||||
|
let is_set = match expr {
|
||||||
|
// Ex) `for i in {1, 2, 3}`
|
||||||
|
Expr::Set(_) => true,
|
||||||
|
// Ex)` for i in {n for n in range(1, 4)}`
|
||||||
|
Expr::SetComp(_) => true,
|
||||||
|
// Ex) `for i in set(1, 2, 3)`
|
||||||
|
Expr::Call(call) => {
|
||||||
|
if let Expr::Name(ExprName { id, .. }) = call.func.as_ref() {
|
||||||
|
id.as_str() == "set" && checker.semantic_model().is_builtin("set")
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if is_set {
|
||||||
|
checker
|
||||||
|
.diagnostics
|
||||||
|
.push(Diagnostic::new(IterationOverSet, expr.range()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,6 +21,7 @@ pub(crate) use invalid_string_characters::{
|
||||||
invalid_string_characters, InvalidCharacterBackspace, InvalidCharacterEsc, InvalidCharacterNul,
|
invalid_string_characters, InvalidCharacterBackspace, InvalidCharacterEsc, InvalidCharacterNul,
|
||||||
InvalidCharacterSub, InvalidCharacterZeroWidthSpace,
|
InvalidCharacterSub, InvalidCharacterZeroWidthSpace,
|
||||||
};
|
};
|
||||||
|
pub(crate) use iteration_over_set::{iteration_over_set, IterationOverSet};
|
||||||
pub(crate) use load_before_global_declaration::{
|
pub(crate) use load_before_global_declaration::{
|
||||||
load_before_global_declaration, LoadBeforeGlobalDeclaration,
|
load_before_global_declaration, LoadBeforeGlobalDeclaration,
|
||||||
};
|
};
|
||||||
|
|
@ -73,6 +74,7 @@ mod invalid_all_object;
|
||||||
mod invalid_envvar_default;
|
mod invalid_envvar_default;
|
||||||
mod invalid_envvar_value;
|
mod invalid_envvar_value;
|
||||||
mod invalid_string_characters;
|
mod invalid_string_characters;
|
||||||
|
mod iteration_over_set;
|
||||||
mod load_before_global_declaration;
|
mod load_before_global_declaration;
|
||||||
mod logging;
|
mod logging;
|
||||||
mod magic_value_comparison;
|
mod magic_value_comparison;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff/src/rules/pylint/mod.rs
|
||||||
|
---
|
||||||
|
iteration_over_set.py:3:13: PLC0208 Use a sequence type instead of a `set` when iterating over values
|
||||||
|
|
|
||||||
|
3 | # Errors
|
||||||
|
4 |
|
||||||
|
5 | for item in {"apples", "lemons", "water"}: # flags in-line set literals
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0208
|
||||||
|
6 | print(f"I like {item}.")
|
||||||
|
|
|
||||||
|
|
||||||
|
iteration_over_set.py:6:13: PLC0208 Use a sequence type instead of a `set` when iterating over values
|
||||||
|
|
|
||||||
|
6 | print(f"I like {item}.")
|
||||||
|
7 |
|
||||||
|
8 | for item in set(("apples", "lemons", "water")): # flags set() calls
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0208
|
||||||
|
9 | print(f"I like {item}.")
|
||||||
|
|
|
||||||
|
|
||||||
|
iteration_over_set.py:9:15: PLC0208 Use a sequence type instead of a `set` when iterating over values
|
||||||
|
|
|
||||||
|
9 | print(f"I like {item}.")
|
||||||
|
10 |
|
||||||
|
11 | for number in {i for i in range(10)}: # flags set comprehensions
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^ PLC0208
|
||||||
|
12 | print(number)
|
||||||
|
|
|
||||||
|
|
||||||
|
iteration_over_set.py:12:28: PLC0208 Use a sequence type instead of a `set` when iterating over values
|
||||||
|
|
|
||||||
|
12 | print(number)
|
||||||
|
13 |
|
||||||
|
14 | numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
|
||||||
|
| ^^^^^^^^^ PLC0208
|
||||||
|
15 |
|
||||||
|
16 | numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
||||||
|
|
|
||||||
|
|
||||||
|
iteration_over_set.py:14:27: PLC0208 Use a sequence type instead of a `set` when iterating over values
|
||||||
|
|
|
||||||
|
14 | numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
|
||||||
|
15 |
|
||||||
|
16 | numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
||||||
|
| ^^^^^^^^^ PLC0208
|
||||||
|
17 |
|
||||||
|
18 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
|
||||||
|
|
|
||||||
|
|
||||||
|
iteration_over_set.py:16:36: PLC0208 Use a sequence type instead of a `set` when iterating over values
|
||||||
|
|
|
||||||
|
16 | numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
||||||
|
17 |
|
||||||
|
18 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
|
||||||
|
| ^^^^^^^^^ PLC0208
|
||||||
|
19 |
|
||||||
|
20 | numbers_gen = (i for i in {1, 2, 3}) # flags sets in generator expressions
|
||||||
|
|
|
||||||
|
|
||||||
|
iteration_over_set.py:18:27: PLC0208 Use a sequence type instead of a `set` when iterating over values
|
||||||
|
|
|
||||||
|
18 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
|
||||||
|
19 |
|
||||||
|
20 | numbers_gen = (i for i in {1, 2, 3}) # flags sets in generator expressions
|
||||||
|
| ^^^^^^^^^ PLC0208
|
||||||
|
21 |
|
||||||
|
22 | # Non-errors
|
||||||
|
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2006,6 +2006,9 @@
|
||||||
"PL",
|
"PL",
|
||||||
"PLC",
|
"PLC",
|
||||||
"PLC0",
|
"PLC0",
|
||||||
|
"PLC02",
|
||||||
|
"PLC020",
|
||||||
|
"PLC0208",
|
||||||
"PLC04",
|
"PLC04",
|
||||||
"PLC041",
|
"PLC041",
|
||||||
"PLC0414",
|
"PLC0414",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue