Skip to content

Commit 01d3d4b

Browse files
authored
ignore if using infinite iterators in B905 (#4914)
1 parent ac4a4da commit 01d3d4b

File tree

4 files changed

+129
-51
lines changed

4 files changed

+129
-51
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,25 @@
1+
from itertools import count, cycle, repeat
2+
3+
# Errors
14
zip()
25
zip(range(3))
36
zip("a", "b")
47
zip("a", "b", *zip("c"))
58
zip(zip("a"), strict=False)
69
zip(zip("a", strict=True))
710

11+
# OK
812
zip(range(3), strict=True)
913
zip("a", "b", strict=False)
1014
zip("a", "b", "c", strict=True)
15+
16+
# OK (infinite iterators).
17+
zip([1, 2, 3], cycle("ABCDEF"))
18+
zip([1, 2, 3], count())
19+
zip([1, 2, 3], repeat(1))
20+
zip([1, 2, 3], repeat(1, None))
21+
zip([1, 2, 3], repeat(1, times=None))
22+
23+
# Errors (limited iterators).
24+
zip([1, 2, 3], repeat(1, 1))
25+
zip([1, 2, 3], repeat(1, times=4))

crates/ruff/src/checkers/ast/mod.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -2633,7 +2633,9 @@ where
26332633
if self.enabled(Rule::ZipWithoutExplicitStrict)
26342634
&& self.settings.target_version >= PythonVersion::Py310
26352635
{
2636-
flake8_bugbear::rules::zip_without_explicit_strict(self, expr, func, keywords);
2636+
flake8_bugbear::rules::zip_without_explicit_strict(
2637+
self, expr, func, args, keywords,
2638+
);
26372639
}
26382640
if self.enabled(Rule::NoExplicitStacklevel) {
26392641
flake8_bugbear::rules::no_explicit_stacklevel(self, func, args, keywords);

crates/ruff/src/rules/flake8_bugbear/rules/zip_without_explicit_strict.rs

+43
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ use rustpython_parser::ast::{self, Expr, Keyword, Ranged};
22

33
use ruff_diagnostics::{Diagnostic, Violation};
44
use ruff_macros::{derive_message_formats, violation};
5+
use ruff_python_ast::helpers::is_const_none;
6+
use ruff_python_semantic::model::SemanticModel;
57

68
use crate::checkers::ast::Checker;
79

@@ -20,6 +22,7 @@ pub(crate) fn zip_without_explicit_strict(
2022
checker: &mut Checker,
2123
expr: &Expr,
2224
func: &Expr,
25+
args: &[Expr],
2326
kwargs: &[Keyword],
2427
) {
2528
if let Expr::Name(ast::ExprName { id, .. }) = func {
@@ -28,10 +31,50 @@ pub(crate) fn zip_without_explicit_strict(
2831
&& !kwargs
2932
.iter()
3033
.any(|keyword| keyword.arg.as_ref().map_or(false, |name| name == "strict"))
34+
&& !args
35+
.iter()
36+
.any(|arg| is_infinite_iterator(arg, checker.semantic_model()))
3137
{
3238
checker
3339
.diagnostics
3440
.push(Diagnostic::new(ZipWithoutExplicitStrict, expr.range()));
3541
}
3642
}
3743
}
44+
45+
/// Return `true` if the [`Expr`] appears to be an infinite iterator (e.g., a call to
46+
/// `itertools.cycle` or similar).
47+
fn is_infinite_iterator(arg: &Expr, model: &SemanticModel) -> bool {
48+
let Expr::Call(ast::ExprCall { func, args, keywords, .. }) = &arg else {
49+
return false;
50+
};
51+
52+
return model
53+
.resolve_call_path(func)
54+
.map_or(false, |call_path| match call_path.as_slice() {
55+
["itertools", "cycle" | "count"] => true,
56+
["itertools", "repeat"] => {
57+
// Ex) `itertools.repeat(1)`
58+
if keywords.is_empty() && args.len() == 1 {
59+
return true;
60+
}
61+
62+
// Ex) `itertools.repeat(1, None)`
63+
if args.len() == 2 && is_const_none(&args[1]) {
64+
return true;
65+
}
66+
67+
// Ex) `iterools.repeat(1, times=None)`
68+
for keyword in keywords {
69+
if keyword.arg.as_ref().map_or(false, |name| name == "times") {
70+
if is_const_none(&keyword.value) {
71+
return true;
72+
}
73+
}
74+
}
75+
76+
false
77+
}
78+
_ => false,
79+
});
80+
}
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,88 @@
11
---
22
source: crates/ruff/src/rules/flake8_bugbear/mod.rs
33
---
4-
B905.py:1:1: B905 `zip()` without an explicit `strict=` parameter
4+
B905.py:4:1: B905 `zip()` without an explicit `strict=` parameter
55
|
6-
1 | zip()
6+
4 | # Errors
7+
5 | zip()
78
| ^^^^^ B905
8-
2 | zip(range(3))
9-
3 | zip("a", "b")
9+
6 | zip(range(3))
10+
7 | zip("a", "b")
1011
|
1112

12-
B905.py:2:1: B905 `zip()` without an explicit `strict=` parameter
13+
B905.py:5:1: B905 `zip()` without an explicit `strict=` parameter
1314
|
14-
2 | zip()
15-
3 | zip(range(3))
15+
5 | # Errors
16+
6 | zip()
17+
7 | zip(range(3))
1618
| ^^^^^^^^^^^^^ B905
17-
4 | zip("a", "b")
18-
5 | zip("a", "b", *zip("c"))
19+
8 | zip("a", "b")
20+
9 | zip("a", "b", *zip("c"))
1921
|
2022

21-
B905.py:3:1: B905 `zip()` without an explicit `strict=` parameter
22-
|
23-
3 | zip()
24-
4 | zip(range(3))
25-
5 | zip("a", "b")
26-
| ^^^^^^^^^^^^^ B905
27-
6 | zip("a", "b", *zip("c"))
28-
7 | zip(zip("a"), strict=False)
29-
|
23+
B905.py:6:1: B905 `zip()` without an explicit `strict=` parameter
24+
|
25+
6 | zip()
26+
7 | zip(range(3))
27+
8 | zip("a", "b")
28+
| ^^^^^^^^^^^^^ B905
29+
9 | zip("a", "b", *zip("c"))
30+
10 | zip(zip("a"), strict=False)
31+
|
3032

31-
B905.py:4:1: B905 `zip()` without an explicit `strict=` parameter
32-
|
33-
4 | zip(range(3))
34-
5 | zip("a", "b")
35-
6 | zip("a", "b", *zip("c"))
36-
| ^^^^^^^^^^^^^^^^^^^^^^^^ B905
37-
7 | zip(zip("a"), strict=False)
38-
8 | zip(zip("a", strict=True))
39-
|
33+
B905.py:7:1: B905 `zip()` without an explicit `strict=` parameter
34+
|
35+
7 | zip(range(3))
36+
8 | zip("a", "b")
37+
9 | zip("a", "b", *zip("c"))
38+
| ^^^^^^^^^^^^^^^^^^^^^^^^ B905
39+
10 | zip(zip("a"), strict=False)
40+
11 | zip(zip("a", strict=True))
41+
|
4042

41-
B905.py:4:16: B905 `zip()` without an explicit `strict=` parameter
42-
|
43-
4 | zip(range(3))
44-
5 | zip("a", "b")
45-
6 | zip("a", "b", *zip("c"))
46-
| ^^^^^^^^ B905
47-
7 | zip(zip("a"), strict=False)
48-
8 | zip(zip("a", strict=True))
49-
|
43+
B905.py:7:16: B905 `zip()` without an explicit `strict=` parameter
44+
|
45+
7 | zip(range(3))
46+
8 | zip("a", "b")
47+
9 | zip("a", "b", *zip("c"))
48+
| ^^^^^^^^ B905
49+
10 | zip(zip("a"), strict=False)
50+
11 | zip(zip("a", strict=True))
51+
|
5052

51-
B905.py:5:5: B905 `zip()` without an explicit `strict=` parameter
52-
|
53-
5 | zip("a", "b")
54-
6 | zip("a", "b", *zip("c"))
55-
7 | zip(zip("a"), strict=False)
56-
| ^^^^^^^^ B905
57-
8 | zip(zip("a", strict=True))
58-
|
53+
B905.py:8:5: B905 `zip()` without an explicit `strict=` parameter
54+
|
55+
8 | zip("a", "b")
56+
9 | zip("a", "b", *zip("c"))
57+
10 | zip(zip("a"), strict=False)
58+
| ^^^^^^^^ B905
59+
11 | zip(zip("a", strict=True))
60+
|
5961

60-
B905.py:6:1: B905 `zip()` without an explicit `strict=` parameter
62+
B905.py:9:1: B905 `zip()` without an explicit `strict=` parameter
6163
|
62-
6 | zip("a", "b", *zip("c"))
63-
7 | zip(zip("a"), strict=False)
64-
8 | zip(zip("a", strict=True))
64+
9 | zip("a", "b", *zip("c"))
65+
10 | zip(zip("a"), strict=False)
66+
11 | zip(zip("a", strict=True))
6567
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ B905
66-
9 |
67-
10 | zip(range(3), strict=True)
68+
12 |
69+
13 | # OK
70+
|
71+
72+
B905.py:24:1: B905 `zip()` without an explicit `strict=` parameter
73+
|
74+
24 | # Errors (limited iterators).
75+
25 | zip([1, 2, 3], repeat(1, 1))
76+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B905
77+
26 | zip([1, 2, 3], repeat(1, times=4))
78+
|
79+
80+
B905.py:25:1: B905 `zip()` without an explicit `strict=` parameter
81+
|
82+
25 | # Errors (limited iterators).
83+
26 | zip([1, 2, 3], repeat(1, 1))
84+
27 | zip([1, 2, 3], repeat(1, times=4))
85+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B905
6886
|
6987

7088

0 commit comments

Comments
 (0)