Skip to content

Commit bf40a37

Browse files
committed
Tweak some docs
1 parent f1d0d9a commit bf40a37

File tree

2 files changed

+41
-40
lines changed

2 files changed

+41
-40
lines changed

crates/ruff_linter/src/rules/pylint/rules/modified_iterating_set.rs

+31-30
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,20 @@ use ruff_text_size::Ranged;
77
use crate::checkers::ast::Checker;
88

99
/// ## What it does
10-
/// Checks for `set` being modified during the iteration on the set.
10+
/// Checks for loops in which a `set` is modified during iteration.
1111
///
1212
/// ## Why is this bad?
13-
/// If `set` is modified during the iteration, it will cause `RuntimeError`.
14-
/// This could be fixed by using temporal copy of the set to iterate.
13+
/// If a `set` is modified during iteration, it will cause a `RuntimeError`.
14+
///
15+
/// If you need to modify a `set` within a loop, consider iterating over a copy
16+
/// of the `set` instead.
17+
///
18+
/// ## Known problems
19+
/// This rule favors false negatives over false positives. Specifically, it
20+
/// will only detect variables that can be inferred to be a `set` type based on
21+
/// local type inference, and will only detect modifications that are made
22+
/// directly on the variable itself (e.g., `set.add()`), as opposed to
23+
/// modifications within other function calls (e.g., `some_function(set)`).
1524
///
1625
/// ## Example
1726
/// ```python
@@ -37,26 +46,17 @@ pub struct ModifiedIteratingSet {
3746
impl AlwaysFixableViolation for ModifiedIteratingSet {
3847
#[derive_message_formats]
3948
fn message(&self) -> String {
40-
format!(
41-
"Iterated set `{}` is being modified inside for loop body.",
42-
self.name
43-
)
49+
let ModifiedIteratingSet { name } = self;
50+
format!("Iterated set `{name}` is modified within the `for` loop",)
4451
}
4552

4653
fn fix_title(&self) -> String {
47-
format!("Consider iterating through a copy of it instead.")
54+
let ModifiedIteratingSet { name } = self;
55+
format!("Iterate over a copy of `{name}`")
4856
}
4957
}
5058

51-
fn is_method_modifying(identifier: &str) -> bool {
52-
(identifier == "add")
53-
|| (identifier == "clear")
54-
|| (identifier == "discard")
55-
|| (identifier == "pop")
56-
|| (identifier == "remove")
57-
}
58-
59-
// PLE4703
59+
/// PLE4703
6060
pub(crate) fn modified_iterating_set(checker: &mut Checker, for_stmt: &StmtFor) {
6161
let Some(name) = for_stmt.iter.as_name_expr() else {
6262
return;
@@ -69,8 +69,7 @@ pub(crate) fn modified_iterating_set(checker: &mut Checker, for_stmt: &StmtFor)
6969
return;
7070
}
7171

72-
let mut is_modified = false;
73-
for stmt in &for_stmt.body {
72+
if for_stmt.body.iter().any(|stmt| {
7473
// name_of_set.modify_method()
7574
// ^---------^ ^-----------^
7675
// value attr
@@ -79,40 +78,42 @@ pub(crate) fn modified_iterating_set(checker: &mut Checker, for_stmt: &StmtFor)
7978
// ^-------------------------^
8079
// expr, stmt
8180
let Stmt::Expr(ast::StmtExpr { value: expr, .. }) = stmt else {
82-
continue;
81+
return false;
8382
};
8483

8584
let Some(func) = expr.as_call_expr().map(|exprcall| &exprcall.func) else {
86-
continue;
85+
return false;
8786
};
8887

8988
let Expr::Attribute(ast::ExprAttribute { value, attr, .. }) = func.as_ref() else {
90-
continue;
89+
return false;
9190
};
9291

9392
let Some(value) = value.as_name_expr() else {
94-
continue;
93+
return false;
9594
};
9695

9796
let Some(binding_id_value) = checker.semantic().only_binding(value) else {
98-
continue;
97+
return false;
9998
};
100-
if binding_id == binding_id_value && is_method_modifying(attr.as_str()) {
101-
is_modified = true;
102-
}
103-
}
10499

105-
if is_modified {
100+
binding_id == binding_id_value && modifies_set(attr.as_str())
101+
}) {
106102
let mut diagnostic = Diagnostic::new(
107103
ModifiedIteratingSet {
108104
name: name.id.clone(),
109105
},
110106
for_stmt.range(),
111107
);
112108
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
113-
format!("{}.copy()", name.id),
109+
format!("{}.copy()", checker.locator().slice(name)),
114110
name.range(),
115111
)));
116112
checker.diagnostics.push(diagnostic);
117113
}
118114
}
115+
116+
/// Returns `true` if the method modifies the set.
117+
fn modifies_set(identifier: &str) -> bool {
118+
matches!(identifier, "add" | "clear" | "discard" | "pop" | "remove")
119+
}

crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE4703_modified_iterating_set.py.snap

+10-10
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
source: crates/ruff_linter/src/rules/pylint/mod.rs
33
---
4-
modified_iterating_set.py:4:1: PLE4703 [*] Iterated set `nums` is being modified inside for loop body.
4+
modified_iterating_set.py:4:1: PLE4703 [*] Iterated set `nums` is modified within the `for` loop
55
|
66
3 | nums = {1, 2, 3}
77
4 | / for num in nums:
@@ -10,7 +10,7 @@ modified_iterating_set.py:4:1: PLE4703 [*] Iterated set `nums` is being modified
1010
6 |
1111
7 | animals = {"dog", "cat", "cow"}
1212
|
13-
= help: Consider iterating through a copy of it instead.
13+
= help: Iterate over a copy of `nums`
1414

1515
Unsafe fix
1616
1 1 | # Errors
@@ -22,7 +22,7 @@ modified_iterating_set.py:4:1: PLE4703 [*] Iterated set `nums` is being modified
2222
6 6 |
2323
7 7 | animals = {"dog", "cat", "cow"}
2424

25-
modified_iterating_set.py:8:1: PLE4703 [*] Iterated set `animals` is being modified inside for loop body.
25+
modified_iterating_set.py:8:1: PLE4703 [*] Iterated set `animals` is modified within the `for` loop
2626
|
2727
7 | animals = {"dog", "cat", "cow"}
2828
8 | / for animal in animals:
@@ -31,7 +31,7 @@ modified_iterating_set.py:8:1: PLE4703 [*] Iterated set `animals` is being modif
3131
10 |
3232
11 | fruits = {"apple", "orange", "grape"}
3333
|
34-
= help: Consider iterating through a copy of it instead.
34+
= help: Iterate over a copy of `animals`
3535

3636
Unsafe fix
3737
5 5 | nums.add(num + 1)
@@ -43,7 +43,7 @@ modified_iterating_set.py:8:1: PLE4703 [*] Iterated set `animals` is being modif
4343
10 10 |
4444
11 11 | fruits = {"apple", "orange", "grape"}
4545

46-
modified_iterating_set.py:12:1: PLE4703 [*] Iterated set `fruits` is being modified inside for loop body.
46+
modified_iterating_set.py:12:1: PLE4703 [*] Iterated set `fruits` is modified within the `for` loop
4747
|
4848
11 | fruits = {"apple", "orange", "grape"}
4949
12 | / for fruit in fruits:
@@ -52,7 +52,7 @@ modified_iterating_set.py:12:1: PLE4703 [*] Iterated set `fruits` is being modif
5252
14 |
5353
15 | planets = {"mercury", "venus", "earth"}
5454
|
55-
= help: Consider iterating through a copy of it instead.
55+
= help: Iterate over a copy of `fruits`
5656

5757
Unsafe fix
5858
9 9 | animals.pop("cow")
@@ -64,7 +64,7 @@ modified_iterating_set.py:12:1: PLE4703 [*] Iterated set `fruits` is being modif
6464
14 14 |
6565
15 15 | planets = {"mercury", "venus", "earth"}
6666

67-
modified_iterating_set.py:16:1: PLE4703 [*] Iterated set `planets` is being modified inside for loop body.
67+
modified_iterating_set.py:16:1: PLE4703 [*] Iterated set `planets` is modified within the `for` loop
6868
|
6969
15 | planets = {"mercury", "venus", "earth"}
7070
16 | / for planet in planets:
@@ -73,7 +73,7 @@ modified_iterating_set.py:16:1: PLE4703 [*] Iterated set `planets` is being modi
7373
18 |
7474
19 | colors = {"red", "green", "blue"}
7575
|
76-
= help: Consider iterating through a copy of it instead.
76+
= help: Iterate over a copy of `planets`
7777

7878
Unsafe fix
7979
13 13 | fruits.clear()
@@ -85,7 +85,7 @@ modified_iterating_set.py:16:1: PLE4703 [*] Iterated set `planets` is being modi
8585
18 18 |
8686
19 19 | colors = {"red", "green", "blue"}
8787

88-
modified_iterating_set.py:20:1: PLE4703 [*] Iterated set `colors` is being modified inside for loop body.
88+
modified_iterating_set.py:20:1: PLE4703 [*] Iterated set `colors` is modified within the `for` loop
8989
|
9090
19 | colors = {"red", "green", "blue"}
9191
20 | / for color in colors:
@@ -94,7 +94,7 @@ modified_iterating_set.py:20:1: PLE4703 [*] Iterated set `colors` is being modif
9494
22 |
9595
23 | # OK
9696
|
97-
= help: Consider iterating through a copy of it instead.
97+
= help: Iterate over a copy of `colors`
9898

9999
Unsafe fix
100100
17 17 | planets.discard("mercury")

0 commit comments

Comments
 (0)