Add Pylint rule `C0208` (`use-sequence-for-iteration`) as `PLC0208` (`iteration-over-set`) (#4706)

This commit is contained in:
Tom Kuson 2023-06-01 22:26:23 +01:00 committed by GitHub
parent ab26f2dc9d
commit bdff4a66ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 197 additions and 0 deletions

View File

@ -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

View File

@ -1554,6 +1554,9 @@ where
if self.enabled(Rule::RedefinedLoopName) {
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 self.enabled(Rule::ReimplementedBuiltin) {
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 {
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 {
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 {
op,

View File

@ -150,6 +150,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pylint, "C0414") => (RuleGroup::Unspecified, Rule::UselessImportAlias),
(Pylint, "C1901") => (RuleGroup::Unspecified, Rule::CompareToEmptyString),
(Pylint, "C3002") => (RuleGroup::Unspecified, Rule::UnnecessaryDirectLambdaCall),
(Pylint, "C0208") => (RuleGroup::Unspecified, Rule::IterationOverSet),
(Pylint, "E0100") => (RuleGroup::Unspecified, Rule::YieldInInit),
(Pylint, "E0101") => (RuleGroup::Unspecified, Rule::ReturnInInit),
(Pylint, "E0116") => (RuleGroup::Unspecified, Rule::ContinueInFinally),

View File

@ -163,6 +163,7 @@ ruff_macros::register_rules!(
rules::pylint::rules::DuplicateValue,
rules::pylint::rules::DuplicateBases,
rules::pylint::rules::NamedExprWithoutContext,
rules::pylint::rules::IterationOverSet,
// flake8-async
rules::flake8_async::rules::BlockingHttpCallInAsyncFunction,
rules::flake8_async::rules::OpenSleepOrSubprocessInAsyncFunction,

View File

@ -64,6 +64,7 @@ mod tests {
)]
#[test_case(Rule::InvalidEnvvarDefault, Path::new("invalid_envvar_default.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::LoggingTooManyArgs, Path::new("logging_too_many_args.py"))]
#[test_case(Rule::MagicValueComparison, Path::new("magic_value_comparison.py"))]

View File

@ -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()));
}
}

View File

@ -21,6 +21,7 @@ pub(crate) use invalid_string_characters::{
invalid_string_characters, InvalidCharacterBackspace, InvalidCharacterEsc, InvalidCharacterNul,
InvalidCharacterSub, InvalidCharacterZeroWidthSpace,
};
pub(crate) use iteration_over_set::{iteration_over_set, IterationOverSet};
pub(crate) use load_before_global_declaration::{
load_before_global_declaration, LoadBeforeGlobalDeclaration,
};
@ -73,6 +74,7 @@ mod invalid_all_object;
mod invalid_envvar_default;
mod invalid_envvar_value;
mod invalid_string_characters;
mod iteration_over_set;
mod load_before_global_declaration;
mod logging;
mod magic_value_comparison;

View File

@ -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
|

3
ruff.schema.json generated
View File

@ -2006,6 +2006,9 @@
"PL",
"PLC",
"PLC0",
"PLC02",
"PLC020",
"PLC0208",
"PLC04",
"PLC041",
"PLC0414",