mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-02 14:52:01 +00:00
[flake8-bugbear
] Implement loop-iterator-mutation
(B909
) (#9578)
## Summary This PR adds the implementation for the current [flake8-bugbear](https://github.com/PyCQA/flake8-bugbear)'s B038 rule. The B038 rule checks for mutation of loop iterators in the body of a for loop and alerts when found. Rational: Editing the loop iterator can lead to undesired behavior and is probably a bug in most cases. Closes #9511. Note there will be a second iteration of B038 implemented in `flake8-bugbear` soon, and this PR currently only implements the weakest form of the rule. I'd be happy to also implement the further improvements to B038 here in ruff 🙂 See https://github.com/PyCQA/flake8-bugbear/issues/454 for more information on the planned improvements. ## Test Plan Re-using the same test file that I've used for `flake8-bugbear`, which is included in this PR (look for the `B038.py` file). Note: this is my first time using `rust` (beside `rustlings`) - I'd be very happy about thorough feedback on what I could've done better 🙂 - Bring it on 😀
This commit is contained in:
parent
25f5a8b201
commit
03899dcba3
9 changed files with 805 additions and 0 deletions
160
crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B909.py
vendored
Normal file
160
crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B909.py
vendored
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
"""
|
||||||
|
Should emit:
|
||||||
|
B909 - on lines 11, 25, 26, 40, 46
|
||||||
|
"""
|
||||||
|
|
||||||
|
# lists
|
||||||
|
|
||||||
|
some_list = [1, 2, 3]
|
||||||
|
some_other_list = [1, 2, 3]
|
||||||
|
for elem in some_list:
|
||||||
|
# errors
|
||||||
|
some_list.remove(0)
|
||||||
|
del some_list[2]
|
||||||
|
some_list.append(elem)
|
||||||
|
some_list.sort()
|
||||||
|
some_list.reverse()
|
||||||
|
some_list.clear()
|
||||||
|
some_list.extend([1, 2])
|
||||||
|
some_list.insert(1, 1)
|
||||||
|
some_list.pop(1)
|
||||||
|
some_list.pop()
|
||||||
|
|
||||||
|
# conditional break should error
|
||||||
|
if elem == 2:
|
||||||
|
some_list.remove(0)
|
||||||
|
if elem == 3:
|
||||||
|
break
|
||||||
|
|
||||||
|
# non-errors
|
||||||
|
some_other_list.remove(elem)
|
||||||
|
del some_list
|
||||||
|
del some_other_list
|
||||||
|
found_idx = some_list.index(elem)
|
||||||
|
some_list = 3
|
||||||
|
|
||||||
|
# unconditional break should not error
|
||||||
|
if elem == 2:
|
||||||
|
some_list.remove(elem)
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
# dicts
|
||||||
|
mydicts = {"a": {"foo": 1, "bar": 2}}
|
||||||
|
|
||||||
|
for elem in mydicts:
|
||||||
|
# errors
|
||||||
|
mydicts.popitem()
|
||||||
|
mydicts.setdefault("foo", 1)
|
||||||
|
mydicts.update({"foo": "bar"})
|
||||||
|
|
||||||
|
# no errors
|
||||||
|
elem.popitem()
|
||||||
|
elem.setdefault("foo", 1)
|
||||||
|
elem.update({"foo": "bar"})
|
||||||
|
|
||||||
|
# sets
|
||||||
|
|
||||||
|
myset = {1, 2, 3}
|
||||||
|
|
||||||
|
for _ in myset:
|
||||||
|
# errors
|
||||||
|
myset.update({4, 5})
|
||||||
|
myset.intersection_update({4, 5})
|
||||||
|
myset.difference_update({4, 5})
|
||||||
|
myset.symmetric_difference_update({4, 5})
|
||||||
|
myset.add(4)
|
||||||
|
myset.discard(3)
|
||||||
|
|
||||||
|
# no errors
|
||||||
|
del myset
|
||||||
|
|
||||||
|
|
||||||
|
# members
|
||||||
|
class A:
|
||||||
|
some_list: list
|
||||||
|
|
||||||
|
def __init__(self, ls):
|
||||||
|
self.some_list = list(ls)
|
||||||
|
|
||||||
|
|
||||||
|
a = A((1, 2, 3))
|
||||||
|
# ensure member accesses are handled as errors
|
||||||
|
for elem in a.some_list:
|
||||||
|
a.some_list.remove(0)
|
||||||
|
del a.some_list[2]
|
||||||
|
|
||||||
|
|
||||||
|
# Augassign should error
|
||||||
|
|
||||||
|
foo = [1, 2, 3]
|
||||||
|
bar = [4, 5, 6]
|
||||||
|
for _ in foo:
|
||||||
|
foo *= 2
|
||||||
|
foo += bar
|
||||||
|
foo[1] = 9
|
||||||
|
foo[1:2] = bar
|
||||||
|
foo[1:2:3] = bar
|
||||||
|
|
||||||
|
foo = {1, 2, 3}
|
||||||
|
bar = {4, 5, 6}
|
||||||
|
for _ in foo: # should error
|
||||||
|
foo |= bar
|
||||||
|
foo &= bar
|
||||||
|
foo -= bar
|
||||||
|
foo ^= bar
|
||||||
|
|
||||||
|
|
||||||
|
# more tests for unconditional breaks - should not error
|
||||||
|
for _ in foo:
|
||||||
|
foo.remove(1)
|
||||||
|
for _ in bar:
|
||||||
|
bar.remove(1)
|
||||||
|
break
|
||||||
|
break
|
||||||
|
|
||||||
|
# should not error
|
||||||
|
for _ in foo:
|
||||||
|
foo.remove(1)
|
||||||
|
for _ in bar:
|
||||||
|
...
|
||||||
|
break
|
||||||
|
|
||||||
|
# should error (?)
|
||||||
|
for _ in foo:
|
||||||
|
foo.remove(1)
|
||||||
|
if bar:
|
||||||
|
bar.remove(1)
|
||||||
|
break
|
||||||
|
break
|
||||||
|
|
||||||
|
# should error
|
||||||
|
for _ in foo:
|
||||||
|
if bar:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
foo.remove(1)
|
||||||
|
|
||||||
|
# should error
|
||||||
|
for elem in some_list:
|
||||||
|
if some_list.pop() == 2:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# should not error
|
||||||
|
for elem in some_list:
|
||||||
|
if some_list.pop() == 2:
|
||||||
|
break
|
||||||
|
|
||||||
|
# should error
|
||||||
|
for elem in some_list:
|
||||||
|
if some_list.pop() == 2:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
# should not error
|
||||||
|
for elem in some_list:
|
||||||
|
del some_list[elem]
|
||||||
|
some_list[elem] = 1
|
||||||
|
some_list.remove(elem)
|
||||||
|
some_list.discard(elem)
|
|
@ -30,6 +30,9 @@ pub(crate) fn deferred_for_loops(checker: &mut Checker) {
|
||||||
if checker.enabled(Rule::EnumerateForLoop) {
|
if checker.enabled(Rule::EnumerateForLoop) {
|
||||||
flake8_simplify::rules::enumerate_for_loop(checker, stmt_for);
|
flake8_simplify::rules::enumerate_for_loop(checker, stmt_for);
|
||||||
}
|
}
|
||||||
|
if checker.enabled(Rule::LoopIteratorMutation) {
|
||||||
|
flake8_bugbear::rules::loop_iterator_mutation(checker, stmt_for);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1268,6 +1268,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||||
if checker.any_enabled(&[
|
if checker.any_enabled(&[
|
||||||
Rule::EnumerateForLoop,
|
Rule::EnumerateForLoop,
|
||||||
Rule::IncorrectDictIterator,
|
Rule::IncorrectDictIterator,
|
||||||
|
Rule::LoopIteratorMutation,
|
||||||
Rule::UnnecessaryEnumerate,
|
Rule::UnnecessaryEnumerate,
|
||||||
Rule::UnusedLoopControlVariable,
|
Rule::UnusedLoopControlVariable,
|
||||||
Rule::YieldInForLoop,
|
Rule::YieldInForLoop,
|
||||||
|
|
|
@ -378,6 +378,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||||
(Flake8Bugbear, "035") => (RuleGroup::Stable, rules::flake8_bugbear::rules::StaticKeyDictComprehension),
|
(Flake8Bugbear, "035") => (RuleGroup::Stable, rules::flake8_bugbear::rules::StaticKeyDictComprehension),
|
||||||
(Flake8Bugbear, "904") => (RuleGroup::Stable, rules::flake8_bugbear::rules::RaiseWithoutFromInsideExcept),
|
(Flake8Bugbear, "904") => (RuleGroup::Stable, rules::flake8_bugbear::rules::RaiseWithoutFromInsideExcept),
|
||||||
(Flake8Bugbear, "905") => (RuleGroup::Stable, rules::flake8_bugbear::rules::ZipWithoutExplicitStrict),
|
(Flake8Bugbear, "905") => (RuleGroup::Stable, rules::flake8_bugbear::rules::ZipWithoutExplicitStrict),
|
||||||
|
(Flake8Bugbear, "909") => (RuleGroup::Preview, rules::flake8_bugbear::rules::LoopIteratorMutation),
|
||||||
|
|
||||||
// flake8-blind-except
|
// flake8-blind-except
|
||||||
(Flake8BlindExcept, "001") => (RuleGroup::Stable, rules::flake8_blind_except::rules::BlindExcept),
|
(Flake8BlindExcept, "001") => (RuleGroup::Stable, rules::flake8_blind_except::rules::BlindExcept),
|
||||||
|
|
|
@ -61,6 +61,7 @@ mod tests {
|
||||||
#[test_case(Rule::UselessContextlibSuppress, Path::new("B022.py"))]
|
#[test_case(Rule::UselessContextlibSuppress, Path::new("B022.py"))]
|
||||||
#[test_case(Rule::UselessExpression, Path::new("B018.ipynb"))]
|
#[test_case(Rule::UselessExpression, Path::new("B018.ipynb"))]
|
||||||
#[test_case(Rule::UselessExpression, Path::new("B018.py"))]
|
#[test_case(Rule::UselessExpression, Path::new("B018.py"))]
|
||||||
|
#[test_case(Rule::LoopIteratorMutation, Path::new("B909.py"))]
|
||||||
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(
|
||||||
|
|
|
@ -0,0 +1,295 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use ruff_diagnostics::Diagnostic;
|
||||||
|
use ruff_diagnostics::Violation;
|
||||||
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
use ruff_python_ast::comparable::ComparableExpr;
|
||||||
|
use ruff_python_ast::name::UnqualifiedName;
|
||||||
|
use ruff_python_ast::{
|
||||||
|
visitor::{self, Visitor},
|
||||||
|
Arguments, Expr, ExprAttribute, ExprCall, ExprSubscript, Stmt, StmtAssign, StmtAugAssign,
|
||||||
|
StmtBreak, StmtDelete, StmtFor, StmtIf,
|
||||||
|
};
|
||||||
|
use ruff_text_size::TextRange;
|
||||||
|
|
||||||
|
use crate::checkers::ast::Checker;
|
||||||
|
use crate::fix::snippet::SourceCodeSnippet;
|
||||||
|
|
||||||
|
/// ## What it does
|
||||||
|
/// Checks for mutations to an iterable during a loop iteration.
|
||||||
|
///
|
||||||
|
/// ## Why is this bad?
|
||||||
|
/// When iterating over an iterable, mutating the iterable can lead to unexpected
|
||||||
|
/// behavior, like skipping elements or infinite loops.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```python
|
||||||
|
/// items = [1, 2, 3]
|
||||||
|
///
|
||||||
|
/// for item in items:
|
||||||
|
/// print(item)
|
||||||
|
///
|
||||||
|
/// # Create an infinite loop by appending to the list.
|
||||||
|
/// items.append(item)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## References
|
||||||
|
/// - [Python documentation: Mutable Sequence Types](https://docs.python.org/3/library/stdtypes.html#typesseq-mutable)
|
||||||
|
#[violation]
|
||||||
|
pub struct LoopIteratorMutation {
|
||||||
|
name: Option<SourceCodeSnippet>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Violation for LoopIteratorMutation {
|
||||||
|
#[derive_message_formats]
|
||||||
|
fn message(&self) -> String {
|
||||||
|
let LoopIteratorMutation { name } = self;
|
||||||
|
|
||||||
|
if let Some(name) = name.as_ref().and_then(SourceCodeSnippet::full_display) {
|
||||||
|
format!("Mutation to loop iterable `{name}` during iteration")
|
||||||
|
} else {
|
||||||
|
format!("Mutation to loop iterable during iteration")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// B909
|
||||||
|
pub(crate) fn loop_iterator_mutation(checker: &mut Checker, stmt_for: &StmtFor) {
|
||||||
|
let StmtFor {
|
||||||
|
target,
|
||||||
|
iter,
|
||||||
|
body,
|
||||||
|
orelse: _,
|
||||||
|
is_async: _,
|
||||||
|
range: _,
|
||||||
|
} = stmt_for;
|
||||||
|
|
||||||
|
if !matches!(iter.as_ref(), Expr::Name(_) | Expr::Attribute(_)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect mutations to the iterable.
|
||||||
|
let mutations = {
|
||||||
|
let mut visitor = LoopMutationsVisitor::new(iter, target);
|
||||||
|
visitor.visit_body(body);
|
||||||
|
visitor.mutations
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a diagnostic for each mutation.
|
||||||
|
for mutation in mutations.values().flatten() {
|
||||||
|
let name = UnqualifiedName::from_expr(iter)
|
||||||
|
.map(|name| name.to_string())
|
||||||
|
.map(SourceCodeSnippet::new);
|
||||||
|
checker
|
||||||
|
.diagnostics
|
||||||
|
.push(Diagnostic::new(LoopIteratorMutation { name }, *mutation));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the method mutates when called on an iterator.
|
||||||
|
fn is_mutating_function(function_name: &str) -> bool {
|
||||||
|
matches!(
|
||||||
|
function_name,
|
||||||
|
"append"
|
||||||
|
| "sort"
|
||||||
|
| "reverse"
|
||||||
|
| "remove"
|
||||||
|
| "clear"
|
||||||
|
| "extend"
|
||||||
|
| "insert"
|
||||||
|
| "pop"
|
||||||
|
| "popitem"
|
||||||
|
| "setdefault"
|
||||||
|
| "update"
|
||||||
|
| "intersection_update"
|
||||||
|
| "difference_update"
|
||||||
|
| "symmetric_difference_update"
|
||||||
|
| "add"
|
||||||
|
| "discard"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A visitor to collect mutations to a variable in a loop.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct LoopMutationsVisitor<'a> {
|
||||||
|
iter: &'a Expr,
|
||||||
|
target: &'a Expr,
|
||||||
|
mutations: HashMap<u8, Vec<TextRange>>,
|
||||||
|
branches: Vec<u8>,
|
||||||
|
branch: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> LoopMutationsVisitor<'a> {
|
||||||
|
/// Initialize the visitor.
|
||||||
|
fn new(iter: &'a Expr, target: &'a Expr) -> Self {
|
||||||
|
Self {
|
||||||
|
iter,
|
||||||
|
target,
|
||||||
|
mutations: HashMap::new(),
|
||||||
|
branches: vec![0],
|
||||||
|
branch: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register a mutation.
|
||||||
|
fn add_mutation(&mut self, range: TextRange) {
|
||||||
|
self.mutations.entry(self.branch).or_default().push(range);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle, e.g., `del items[0]`.
|
||||||
|
fn handle_delete(&mut self, range: TextRange, targets: &[Expr]) {
|
||||||
|
for target in targets {
|
||||||
|
if let Expr::Subscript(ExprSubscript {
|
||||||
|
range: _,
|
||||||
|
value,
|
||||||
|
slice,
|
||||||
|
ctx: _,
|
||||||
|
}) = target
|
||||||
|
{
|
||||||
|
// Find, e.g., `del items[0]`.
|
||||||
|
if ComparableExpr::from(self.iter) == ComparableExpr::from(value) {
|
||||||
|
// But allow, e.g., `for item in items: del items[item]`.
|
||||||
|
if ComparableExpr::from(self.target) != ComparableExpr::from(slice) {
|
||||||
|
self.add_mutation(range);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle, e.g., `items[0] = 1`.
|
||||||
|
fn handle_assign(&mut self, range: TextRange, targets: &[Expr]) {
|
||||||
|
for target in targets {
|
||||||
|
if let Expr::Subscript(ExprSubscript {
|
||||||
|
range: _,
|
||||||
|
value,
|
||||||
|
slice,
|
||||||
|
ctx: _,
|
||||||
|
}) = target
|
||||||
|
{
|
||||||
|
// Find, e.g., `items[0] = 1`.
|
||||||
|
if ComparableExpr::from(self.iter) == ComparableExpr::from(value) {
|
||||||
|
// But allow, e.g., `for item in items: items[item] = 1`.
|
||||||
|
if ComparableExpr::from(self.target) != ComparableExpr::from(slice) {
|
||||||
|
self.add_mutation(range);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle, e.g., `items += [1]`.
|
||||||
|
fn handle_aug_assign(&mut self, range: TextRange, target: &Expr) {
|
||||||
|
if ComparableExpr::from(self.iter) == ComparableExpr::from(target) {
|
||||||
|
self.add_mutation(range);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle, e.g., `items.append(1)`.
|
||||||
|
fn handle_call(&mut self, func: &Expr, arguments: &Arguments) {
|
||||||
|
if let Expr::Attribute(ExprAttribute {
|
||||||
|
range,
|
||||||
|
value,
|
||||||
|
attr,
|
||||||
|
ctx: _,
|
||||||
|
}) = func
|
||||||
|
{
|
||||||
|
if is_mutating_function(attr.as_str()) {
|
||||||
|
// Find, e.g., `items.remove(1)`.
|
||||||
|
if ComparableExpr::from(self.iter) == ComparableExpr::from(value) {
|
||||||
|
// But allow, e.g., `for item in items: items.remove(item)`.
|
||||||
|
if matches!(attr.as_str(), "remove" | "discard" | "pop") {
|
||||||
|
if arguments.len() == 1 {
|
||||||
|
if let [arg] = &*arguments.args {
|
||||||
|
if ComparableExpr::from(self.target) == ComparableExpr::from(arg) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.add_mutation(*range);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Visitor` to collect all used identifiers in a statement.
|
||||||
|
impl<'a> Visitor<'a> for LoopMutationsVisitor<'a> {
|
||||||
|
fn visit_stmt(&mut self, stmt: &'a Stmt) {
|
||||||
|
match stmt {
|
||||||
|
// Ex) `del items[0]`
|
||||||
|
Stmt::Delete(StmtDelete { range, targets }) => {
|
||||||
|
self.handle_delete(*range, targets);
|
||||||
|
visitor::walk_stmt(self, stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ex) `items[0] = 1`
|
||||||
|
Stmt::Assign(StmtAssign { range, targets, .. }) => {
|
||||||
|
self.handle_assign(*range, targets);
|
||||||
|
visitor::walk_stmt(self, stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ex) `items += [1]`
|
||||||
|
Stmt::AugAssign(StmtAugAssign { range, target, .. }) => {
|
||||||
|
self.handle_aug_assign(*range, target);
|
||||||
|
visitor::walk_stmt(self, stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ex) `if True: items.append(1)`
|
||||||
|
Stmt::If(StmtIf {
|
||||||
|
test,
|
||||||
|
body,
|
||||||
|
elif_else_clauses,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
// Handle the `if` branch.
|
||||||
|
self.branch += 1;
|
||||||
|
self.branches.push(self.branch);
|
||||||
|
self.visit_expr(test);
|
||||||
|
self.visit_body(body);
|
||||||
|
self.branches.pop();
|
||||||
|
|
||||||
|
// Handle the `elif` and `else` branches.
|
||||||
|
for clause in elif_else_clauses {
|
||||||
|
self.branch += 1;
|
||||||
|
self.branches.push(self.branch);
|
||||||
|
if let Some(test) = &clause.test {
|
||||||
|
self.visit_expr(test);
|
||||||
|
}
|
||||||
|
self.visit_body(&clause.body);
|
||||||
|
self.branches.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// On break, clear the mutations for the current branch.
|
||||||
|
Stmt::Break(StmtBreak { range: _ }) => {
|
||||||
|
if let Some(mutations) = self.mutations.get_mut(&self.branch) {
|
||||||
|
mutations.clear();
|
||||||
|
}
|
||||||
|
visitor::walk_stmt(self, stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid recursion for class and function definitions.
|
||||||
|
Stmt::ClassDef(_) | Stmt::FunctionDef(_) => {}
|
||||||
|
|
||||||
|
// Default case.
|
||||||
|
_ => {
|
||||||
|
visitor::walk_stmt(self, stmt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_expr(&mut self, expr: &'a Expr) {
|
||||||
|
// Ex) `items.append(1)`
|
||||||
|
if let Expr::Call(ExprCall {
|
||||||
|
func, arguments, ..
|
||||||
|
}) = expr
|
||||||
|
{
|
||||||
|
self.handle_call(func, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
visitor::walk_expr(self, expr);
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ pub(crate) use function_call_in_argument_default::*;
|
||||||
pub(crate) use function_uses_loop_variable::*;
|
pub(crate) use function_uses_loop_variable::*;
|
||||||
pub(crate) use getattr_with_constant::*;
|
pub(crate) use getattr_with_constant::*;
|
||||||
pub(crate) use jump_statement_in_finally::*;
|
pub(crate) use jump_statement_in_finally::*;
|
||||||
|
pub(crate) use loop_iterator_mutation::*;
|
||||||
pub(crate) use loop_variable_overrides_iterator::*;
|
pub(crate) use loop_variable_overrides_iterator::*;
|
||||||
pub(crate) use mutable_argument_default::*;
|
pub(crate) use mutable_argument_default::*;
|
||||||
pub(crate) use no_explicit_stacklevel::*;
|
pub(crate) use no_explicit_stacklevel::*;
|
||||||
|
@ -47,6 +48,7 @@ mod function_call_in_argument_default;
|
||||||
mod function_uses_loop_variable;
|
mod function_uses_loop_variable;
|
||||||
mod getattr_with_constant;
|
mod getattr_with_constant;
|
||||||
mod jump_statement_in_finally;
|
mod jump_statement_in_finally;
|
||||||
|
mod loop_iterator_mutation;
|
||||||
mod loop_variable_overrides_iterator;
|
mod loop_variable_overrides_iterator;
|
||||||
mod mutable_argument_default;
|
mod mutable_argument_default;
|
||||||
mod no_explicit_stacklevel;
|
mod no_explicit_stacklevel;
|
||||||
|
|
|
@ -0,0 +1,341 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
|
||||||
|
---
|
||||||
|
B909.py:12:5: B909 Mutation to loop iterable `some_list` during iteration
|
||||||
|
|
|
||||||
|
10 | for elem in some_list:
|
||||||
|
11 | # errors
|
||||||
|
12 | some_list.remove(0)
|
||||||
|
| ^^^^^^^^^^^^^^^^ B909
|
||||||
|
13 | del some_list[2]
|
||||||
|
14 | some_list.append(elem)
|
||||||
|
|
|
||||||
|
|
||||||
|
B909.py:13:5: B909 Mutation to loop iterable `some_list` during iteration
|
||||||
|
|
|
||||||
|
11 | # errors
|
||||||
|
12 | some_list.remove(0)
|
||||||
|
13 | del some_list[2]
|
||||||
|
| ^^^^^^^^^^^^^^^^ B909
|
||||||
|
14 | some_list.append(elem)
|
||||||
|
15 | some_list.sort()
|
||||||
|
|
|
||||||
|
|
||||||
|
B909.py:14:5: B909 Mutation to loop iterable `some_list` during iteration
|
||||||
|
|
|
||||||
|
12 | some_list.remove(0)
|
||||||
|
13 | del some_list[2]
|
||||||
|
14 | some_list.append(elem)
|
||||||
|
| ^^^^^^^^^^^^^^^^ B909
|
||||||
|
15 | some_list.sort()
|
||||||
|
16 | some_list.reverse()
|
||||||
|
|
|
||||||
|
|
||||||
|
B909.py:15:5: B909 Mutation to loop iterable `some_list` during iteration
|
||||||
|
|
|
||||||
|
13 | del some_list[2]
|
||||||
|
14 | some_list.append(elem)
|
||||||
|
15 | some_list.sort()
|
||||||
|
| ^^^^^^^^^^^^^^ B909
|
||||||
|
16 | some_list.reverse()
|
||||||
|
17 | some_list.clear()
|
||||||
|
|
|
||||||
|
|
||||||
|
B909.py:16:5: B909 Mutation to loop iterable `some_list` during iteration
|
||||||
|
|
|
||||||
|
14 | some_list.append(elem)
|
||||||
|
15 | some_list.sort()
|
||||||
|
16 | some_list.reverse()
|
||||||
|
| ^^^^^^^^^^^^^^^^^ B909
|
||||||
|
17 | some_list.clear()
|
||||||
|
18 | some_list.extend([1, 2])
|
||||||
|
|
|
||||||
|
|
||||||
|
B909.py:17:5: B909 Mutation to loop iterable `some_list` during iteration
|
||||||
|
|
|
||||||
|
15 | some_list.sort()
|
||||||
|
16 | some_list.reverse()
|
||||||
|
17 | some_list.clear()
|
||||||
|
| ^^^^^^^^^^^^^^^ B909
|
||||||
|
18 | some_list.extend([1, 2])
|
||||||
|
19 | some_list.insert(1, 1)
|
||||||
|
|
|
||||||
|
|
||||||
|
B909.py:18:5: B909 Mutation to loop iterable `some_list` during iteration
|
||||||
|
|
|
||||||
|
16 | some_list.reverse()
|
||||||
|
17 | some_list.clear()
|
||||||
|
18 | some_list.extend([1, 2])
|
||||||
|
| ^^^^^^^^^^^^^^^^ B909
|
||||||
|
19 | some_list.insert(1, 1)
|
||||||
|
20 | some_list.pop(1)
|
||||||
|
|
|
||||||
|
|
||||||
|
B909.py:19:5: B909 Mutation to loop iterable `some_list` during iteration
|
||||||
|
|
|
||||||
|
17 | some_list.clear()
|
||||||
|
18 | some_list.extend([1, 2])
|
||||||
|
19 | some_list.insert(1, 1)
|
||||||
|
| ^^^^^^^^^^^^^^^^ B909
|
||||||
|
20 | some_list.pop(1)
|
||||||
|
21 | some_list.pop()
|
||||||
|
|
|
||||||
|
|
||||||
|
B909.py:20:5: B909 Mutation to loop iterable `some_list` during iteration
|
||||||
|
|
|
||||||
|
18 | some_list.extend([1, 2])
|
||||||
|
19 | some_list.insert(1, 1)
|
||||||
|
20 | some_list.pop(1)
|
||||||
|
| ^^^^^^^^^^^^^ B909
|
||||||
|
21 | some_list.pop()
|
||||||
|
|
|
||||||
|
|
||||||
|
B909.py:21:5: B909 Mutation to loop iterable `some_list` during iteration
|
||||||
|
|
|
||||||
|
19 | some_list.insert(1, 1)
|
||||||
|
20 | some_list.pop(1)
|
||||||
|
21 | some_list.pop()
|
||||||
|
| ^^^^^^^^^^^^^ B909
|
||||||
|
22 |
|
||||||
|
23 | # conditional break should error
|
||||||
|
|
|
||||||
|
|
||||||
|
B909.py:25:9: B909 Mutation to loop iterable `some_list` during iteration
|
||||||
|
|
|
||||||
|
23 | # conditional break should error
|
||||||
|
24 | if elem == 2:
|
||||||
|
25 | some_list.remove(0)
|
||||||
|
| ^^^^^^^^^^^^^^^^ B909
|
||||||
|
26 | if elem == 3:
|
||||||
|
27 | break
|
||||||
|
|
|
||||||
|
|
||||||
|
B909.py:47:5: B909 Mutation to loop iterable `mydicts` during iteration
|
||||||
|
|
|
||||||
|
45 | for elem in mydicts:
|
||||||
|
46 | # errors
|
||||||
|
47 | mydicts.popitem()
|
||||||
|
| ^^^^^^^^^^^^^^^ B909
|
||||||
|
48 | mydicts.setdefault("foo", 1)
|
||||||
|
49 | mydicts.update({"foo": "bar"})
|
||||||
|
|
|
||||||
|
|
||||||
|
B909.py:48:5: B909 Mutation to loop iterable `mydicts` during iteration
|
||||||
|
|
|
||||||
|
46 | # errors
|
||||||
|
47 | mydicts.popitem()
|
||||||
|
48 | mydicts.setdefault("foo", 1)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^ B909
|
||||||
|
49 | mydicts.update({"foo": "bar"})
|
||||||
|
|
|
||||||
|
|
||||||
|
B909.py:49:5: B909 Mutation to loop iterable `mydicts` during iteration
|
||||||
|
|
|
||||||
|
47 | mydicts.popitem()
|
||||||
|
48 | mydicts.setdefault("foo", 1)
|
||||||
|
49 | mydicts.update({"foo": "bar"})
|
||||||
|
| ^^^^^^^^^^^^^^ B909
|
||||||
|
50 |
|
||||||
|
51 | # no errors
|
||||||
|
|
|
||||||
|
|
||||||
|
B909.py:62:5: B909 Mutation to loop iterable `myset` during iteration
|
||||||
|
|
|
||||||
|
60 | for _ in myset:
|
||||||
|
61 | # errors
|
||||||
|
62 | myset.update({4, 5})
|
||||||
|
| ^^^^^^^^^^^^ B909
|
||||||
|
63 | myset.intersection_update({4, 5})
|
||||||
|
64 | myset.difference_update({4, 5})
|
||||||
|
|
|
||||||
|
|
||||||
|
B909.py:63:5: B909 Mutation to loop iterable `myset` during iteration
|
||||||
|
|
|
||||||
|
61 | # errors
|
||||||
|
62 | myset.update({4, 5})
|
||||||
|
63 | myset.intersection_update({4, 5})
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^ B909
|
||||||
|
64 | myset.difference_update({4, 5})
|
||||||
|
65 | myset.symmetric_difference_update({4, 5})
|
||||||
|
|
|
||||||
|
|
||||||
|
B909.py:64:5: B909 Mutation to loop iterable `myset` during iteration
|
||||||
|
|
|
||||||
|
62 | myset.update({4, 5})
|
||||||
|
63 | myset.intersection_update({4, 5})
|
||||||
|
64 | myset.difference_update({4, 5})
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^ B909
|
||||||
|
65 | myset.symmetric_difference_update({4, 5})
|
||||||
|
66 | myset.add(4)
|
||||||
|
|
|
||||||
|
|
||||||
|
B909.py:65:5: B909 Mutation to loop iterable `myset` during iteration
|
||||||
|
|
|
||||||
|
63 | myset.intersection_update({4, 5})
|
||||||
|
64 | myset.difference_update({4, 5})
|
||||||
|
65 | myset.symmetric_difference_update({4, 5})
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B909
|
||||||
|
66 | myset.add(4)
|
||||||
|
67 | myset.discard(3)
|
||||||
|
|
|
||||||
|
|
||||||
|
B909.py:66:5: B909 Mutation to loop iterable `myset` during iteration
|
||||||
|
|
|
||||||
|
64 | myset.difference_update({4, 5})
|
||||||
|
65 | myset.symmetric_difference_update({4, 5})
|
||||||
|
66 | myset.add(4)
|
||||||
|
| ^^^^^^^^^ B909
|
||||||
|
67 | myset.discard(3)
|
||||||
|
|
|
||||||
|
|
||||||
|
B909.py:67:5: B909 Mutation to loop iterable `myset` during iteration
|
||||||
|
|
|
||||||
|
65 | myset.symmetric_difference_update({4, 5})
|
||||||
|
66 | myset.add(4)
|
||||||
|
67 | myset.discard(3)
|
||||||
|
| ^^^^^^^^^^^^^ B909
|
||||||
|
68 |
|
||||||
|
69 | # no errors
|
||||||
|
|
|
||||||
|
|
||||||
|
B909.py:84:5: B909 Mutation to loop iterable `a.some_list` during iteration
|
||||||
|
|
|
||||||
|
82 | # ensure member accesses are handled as errors
|
||||||
|
83 | for elem in a.some_list:
|
||||||
|
84 | a.some_list.remove(0)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^ B909
|
||||||
|
85 | del a.some_list[2]
|
||||||
|
|
|
||||||
|
|
||||||
|
B909.py:85:5: B909 Mutation to loop iterable `a.some_list` during iteration
|
||||||
|
|
|
||||||
|
83 | for elem in a.some_list:
|
||||||
|
84 | a.some_list.remove(0)
|
||||||
|
85 | del a.some_list[2]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^ B909
|
||||||
|
|
|
||||||
|
|
||||||
|
B909.py:93:5: B909 Mutation to loop iterable `foo` during iteration
|
||||||
|
|
|
||||||
|
91 | bar = [4, 5, 6]
|
||||||
|
92 | for _ in foo:
|
||||||
|
93 | foo *= 2
|
||||||
|
| ^^^^^^^^ B909
|
||||||
|
94 | foo += bar
|
||||||
|
95 | foo[1] = 9
|
||||||
|
|
|
||||||
|
|
||||||
|
B909.py:94:5: B909 Mutation to loop iterable `foo` during iteration
|
||||||
|
|
|
||||||
|
92 | for _ in foo:
|
||||||
|
93 | foo *= 2
|
||||||
|
94 | foo += bar
|
||||||
|
| ^^^^^^^^^^ B909
|
||||||
|
95 | foo[1] = 9
|
||||||
|
96 | foo[1:2] = bar
|
||||||
|
|
|
||||||
|
|
||||||
|
B909.py:95:5: B909 Mutation to loop iterable `foo` during iteration
|
||||||
|
|
|
||||||
|
93 | foo *= 2
|
||||||
|
94 | foo += bar
|
||||||
|
95 | foo[1] = 9
|
||||||
|
| ^^^^^^^^^^ B909
|
||||||
|
96 | foo[1:2] = bar
|
||||||
|
97 | foo[1:2:3] = bar
|
||||||
|
|
|
||||||
|
|
||||||
|
B909.py:96:5: B909 Mutation to loop iterable `foo` during iteration
|
||||||
|
|
|
||||||
|
94 | foo += bar
|
||||||
|
95 | foo[1] = 9
|
||||||
|
96 | foo[1:2] = bar
|
||||||
|
| ^^^^^^^^^^^^^^ B909
|
||||||
|
97 | foo[1:2:3] = bar
|
||||||
|
|
|
||||||
|
|
||||||
|
B909.py:97:5: B909 Mutation to loop iterable `foo` during iteration
|
||||||
|
|
|
||||||
|
95 | foo[1] = 9
|
||||||
|
96 | foo[1:2] = bar
|
||||||
|
97 | foo[1:2:3] = bar
|
||||||
|
| ^^^^^^^^^^^^^^^^ B909
|
||||||
|
98 |
|
||||||
|
99 | foo = {1, 2, 3}
|
||||||
|
|
|
||||||
|
|
||||||
|
B909.py:102:5: B909 Mutation to loop iterable `foo` during iteration
|
||||||
|
|
|
||||||
|
100 | bar = {4, 5, 6}
|
||||||
|
101 | for _ in foo: # should error
|
||||||
|
102 | foo |= bar
|
||||||
|
| ^^^^^^^^^^ B909
|
||||||
|
103 | foo &= bar
|
||||||
|
104 | foo -= bar
|
||||||
|
|
|
||||||
|
|
||||||
|
B909.py:103:5: B909 Mutation to loop iterable `foo` during iteration
|
||||||
|
|
|
||||||
|
101 | for _ in foo: # should error
|
||||||
|
102 | foo |= bar
|
||||||
|
103 | foo &= bar
|
||||||
|
| ^^^^^^^^^^ B909
|
||||||
|
104 | foo -= bar
|
||||||
|
105 | foo ^= bar
|
||||||
|
|
|
||||||
|
|
||||||
|
B909.py:104:5: B909 Mutation to loop iterable `foo` during iteration
|
||||||
|
|
|
||||||
|
102 | foo |= bar
|
||||||
|
103 | foo &= bar
|
||||||
|
104 | foo -= bar
|
||||||
|
| ^^^^^^^^^^ B909
|
||||||
|
105 | foo ^= bar
|
||||||
|
|
|
||||||
|
|
||||||
|
B909.py:105:5: B909 Mutation to loop iterable `foo` during iteration
|
||||||
|
|
|
||||||
|
103 | foo &= bar
|
||||||
|
104 | foo -= bar
|
||||||
|
105 | foo ^= bar
|
||||||
|
| ^^^^^^^^^^ B909
|
||||||
|
|
|
||||||
|
|
||||||
|
B909.py:125:5: B909 Mutation to loop iterable `foo` during iteration
|
||||||
|
|
|
||||||
|
123 | # should error (?)
|
||||||
|
124 | for _ in foo:
|
||||||
|
125 | foo.remove(1)
|
||||||
|
| ^^^^^^^^^^ B909
|
||||||
|
126 | if bar:
|
||||||
|
127 | bar.remove(1)
|
||||||
|
|
|
||||||
|
|
||||||
|
B909.py:136:9: B909 Mutation to loop iterable `foo` during iteration
|
||||||
|
|
|
||||||
|
134 | pass
|
||||||
|
135 | else:
|
||||||
|
136 | foo.remove(1)
|
||||||
|
| ^^^^^^^^^^ B909
|
||||||
|
137 |
|
||||||
|
138 | # should error
|
||||||
|
|
|
||||||
|
|
||||||
|
B909.py:140:8: B909 Mutation to loop iterable `some_list` during iteration
|
||||||
|
|
|
||||||
|
138 | # should error
|
||||||
|
139 | for elem in some_list:
|
||||||
|
140 | if some_list.pop() == 2:
|
||||||
|
| ^^^^^^^^^^^^^ B909
|
||||||
|
141 | pass
|
||||||
|
|
|
||||||
|
|
||||||
|
B909.py:150:8: B909 Mutation to loop iterable `some_list` during iteration
|
||||||
|
|
|
||||||
|
148 | # should error
|
||||||
|
149 | for elem in some_list:
|
||||||
|
150 | if some_list.pop() == 2:
|
||||||
|
| ^^^^^^^^^^^^^ B909
|
||||||
|
151 | pass
|
||||||
|
152 | else:
|
||||||
|
|
|
1
ruff.schema.json
generated
1
ruff.schema.json
generated
|
@ -2727,6 +2727,7 @@
|
||||||
"B90",
|
"B90",
|
||||||
"B904",
|
"B904",
|
||||||
"B905",
|
"B905",
|
||||||
|
"B909",
|
||||||
"BLE",
|
"BLE",
|
||||||
"BLE0",
|
"BLE0",
|
||||||
"BLE00",
|
"BLE00",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue