diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B905.py b/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B905.py index 031773b92edf32..02ed07daf59ce6 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B905.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B905.py @@ -1,12 +1,10 @@ from itertools import count, cycle, repeat # Errors -zip() -zip(range(3)) zip("a", "b") zip("a", "b", *zip("c")) -zip(zip("a"), strict=False) -zip(zip("a", strict=True)) +zip(zip("a", "b"), strict=False) +zip(zip("a", strict=True),"b") # OK zip(range(3), strict=True) @@ -27,3 +25,10 @@ import builtins # Still an error even though it uses the qualified name builtins.zip([1, 2, 3]) + +# Regression https://github.com/astral-sh/ruff/issues/20997 +# Ok +zip() +zip(range(3)) +# Error +zip(*lot_of_iterators) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B912.py b/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B912.py index 32c7afb2f89733..72bbf67cb8a386 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B912.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B912.py @@ -31,3 +31,6 @@ map(lambda x, y: x + y, [1, 2, 3], repeat(1)) map(lambda x, y: x + y, [1, 2, 3], repeat(1, times=None)) map(lambda x, y: x + y, [1, 2, 3], count()) + +# Regression https://github.com/astral-sh/ruff/issues/20997 +map(f, *lots_of_iterators) diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/map_without_explicit_strict.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/map_without_explicit_strict.rs index 13c6806433e7dd..0eba117469e9a2 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/map_without_explicit_strict.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/map_without_explicit_strict.rs @@ -9,7 +9,7 @@ use crate::rules::flake8_bugbear::helpers::any_infinite_iterables; use crate::{AlwaysFixableViolation, Applicability, Fix}; /// ## What it does -/// Checks for `map` calls without an explicit `strict` parameter when called with two or more iterables. +/// Checks for `map` calls without an explicit `strict` parameter when called with two or more iterables, or any starred argument. /// /// This rule applies to Python 3.14 and later, where `map` accepts a `strict` keyword /// argument. For details, see: [What’s New in Python 3.14](https://docs.python.org/dev/whatsnew/3.14.html). @@ -62,7 +62,12 @@ pub(crate) fn map_without_explicit_strict(checker: &Checker, call: &ast::ExprCal if semantic.match_builtin_expr(&call.func, "map") && call.arguments.find_keyword("strict").is_none() - && call.arguments.args.len() >= 3 // function + at least 2 iterables + && ( + // at least 2 iterables (+ 1 function) + call.arguments.args.len() >= 3 + // or a starred argument + || call.arguments.args.iter().any(ast::Expr::is_starred_expr) + ) && !any_infinite_iterables(call.arguments.args.iter().skip(1), semantic) { checker diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/zip_without_explicit_strict.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/zip_without_explicit_strict.rs index a432bbdce97e95..102d6b0a57400d 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/zip_without_explicit_strict.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/zip_without_explicit_strict.rs @@ -9,7 +9,7 @@ use crate::rules::flake8_bugbear::helpers::any_infinite_iterables; use crate::{AlwaysFixableViolation, Applicability, Fix}; /// ## What it does -/// Checks for `zip` calls without an explicit `strict` parameter. +/// Checks for `zip` calls without an explicit `strict` parameter when called with two or more iterables, or any starred argument. /// /// ## Why is this bad? /// By default, if the iterables passed to `zip` are of different lengths, the @@ -58,6 +58,12 @@ pub(crate) fn zip_without_explicit_strict(checker: &Checker, call: &ast::ExprCal if semantic.match_builtin_expr(&call.func, "zip") && call.arguments.find_keyword("strict").is_none() + && ( + // at least 2 iterables + call.arguments.args.len() >= 2 + // or a starred argument + || call.arguments.args.iter().any(ast::Expr::is_starred_expr) + ) && !any_infinite_iterables(call.arguments.args.iter(), semantic) { checker diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B905.py.snap b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B905.py.snap index 8ea572c657dd8f..b6b4640583fd5f 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B905.py.snap +++ b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B905.py.snap @@ -1,204 +1,140 @@ --- source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs -assertion_line: 156 --- B905 [*] `zip()` without an explicit `strict=` parameter --> B905.py:4:1 | 3 | # Errors -4 | zip() - | ^^^^^ -5 | zip(range(3)) -6 | zip("a", "b") +4 | zip("a", "b") + | ^^^^^^^^^^^^^ +5 | zip("a", "b", *zip("c")) +6 | zip(zip("a", "b"), strict=False) | help: Add explicit value for parameter `strict=` 1 | from itertools import count, cycle, repeat 2 | 3 | # Errors - - zip() -4 + zip(strict=False) -5 | zip(range(3)) -6 | zip("a", "b") -7 | zip("a", "b", *zip("c")) + - zip("a", "b") +4 + zip("a", "b", strict=False) +5 | zip("a", "b", *zip("c")) +6 | zip(zip("a", "b"), strict=False) +7 | zip(zip("a", strict=True),"b") note: This is an unsafe fix and may change runtime behavior B905 [*] `zip()` without an explicit `strict=` parameter --> B905.py:5:1 | 3 | # Errors -4 | zip() -5 | zip(range(3)) - | ^^^^^^^^^^^^^ -6 | zip("a", "b") -7 | zip("a", "b", *zip("c")) +4 | zip("a", "b") +5 | zip("a", "b", *zip("c")) + | ^^^^^^^^^^^^^^^^^^^^^^^^ +6 | zip(zip("a", "b"), strict=False) +7 | zip(zip("a", strict=True),"b") | help: Add explicit value for parameter `strict=` 2 | 3 | # Errors -4 | zip() - - zip(range(3)) -5 + zip(range(3), strict=False) -6 | zip("a", "b") -7 | zip("a", "b", *zip("c")) -8 | zip(zip("a"), strict=False) +4 | zip("a", "b") + - zip("a", "b", *zip("c")) +5 + zip("a", "b", *zip("c"), strict=False) +6 | zip(zip("a", "b"), strict=False) +7 | zip(zip("a", strict=True),"b") +8 | note: This is an unsafe fix and may change runtime behavior B905 [*] `zip()` without an explicit `strict=` parameter - --> B905.py:6:1 + --> B905.py:6:5 | -4 | zip() -5 | zip(range(3)) -6 | zip("a", "b") - | ^^^^^^^^^^^^^ -7 | zip("a", "b", *zip("c")) -8 | zip(zip("a"), strict=False) +4 | zip("a", "b") +5 | zip("a", "b", *zip("c")) +6 | zip(zip("a", "b"), strict=False) + | ^^^^^^^^^^^^^ +7 | zip(zip("a", strict=True),"b") | help: Add explicit value for parameter `strict=` 3 | # Errors -4 | zip() -5 | zip(range(3)) - - zip("a", "b") -6 + zip("a", "b", strict=False) -7 | zip("a", "b", *zip("c")) -8 | zip(zip("a"), strict=False) -9 | zip(zip("a", strict=True)) +4 | zip("a", "b") +5 | zip("a", "b", *zip("c")) + - zip(zip("a", "b"), strict=False) +6 + zip(zip("a", "b", strict=False), strict=False) +7 | zip(zip("a", strict=True),"b") +8 | +9 | # OK note: This is an unsafe fix and may change runtime behavior B905 [*] `zip()` without an explicit `strict=` parameter --> B905.py:7:1 | -5 | zip(range(3)) -6 | zip("a", "b") -7 | zip("a", "b", *zip("c")) - | ^^^^^^^^^^^^^^^^^^^^^^^^ -8 | zip(zip("a"), strict=False) -9 | zip(zip("a", strict=True)) - | -help: Add explicit value for parameter `strict=` -4 | zip() -5 | zip(range(3)) -6 | zip("a", "b") - - zip("a", "b", *zip("c")) -7 + zip("a", "b", *zip("c"), strict=False) -8 | zip(zip("a"), strict=False) -9 | zip(zip("a", strict=True)) -10 | -note: This is an unsafe fix and may change runtime behavior - -B905 [*] `zip()` without an explicit `strict=` parameter - --> B905.py:7:16 - | -5 | zip(range(3)) -6 | zip("a", "b") -7 | zip("a", "b", *zip("c")) - | ^^^^^^^^ -8 | zip(zip("a"), strict=False) -9 | zip(zip("a", strict=True)) +5 | zip("a", "b", *zip("c")) +6 | zip(zip("a", "b"), strict=False) +7 | zip(zip("a", strict=True),"b") + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +8 | +9 | # OK | help: Add explicit value for parameter `strict=` -4 | zip() -5 | zip(range(3)) -6 | zip("a", "b") - - zip("a", "b", *zip("c")) -7 + zip("a", "b", *zip("c", strict=False)) -8 | zip(zip("a"), strict=False) -9 | zip(zip("a", strict=True)) -10 | -note: This is an unsafe fix and may change runtime behavior - -B905 [*] `zip()` without an explicit `strict=` parameter - --> B905.py:8:5 - | -6 | zip("a", "b") -7 | zip("a", "b", *zip("c")) -8 | zip(zip("a"), strict=False) - | ^^^^^^^^ -9 | zip(zip("a", strict=True)) - | -help: Add explicit value for parameter `strict=` -5 | zip(range(3)) -6 | zip("a", "b") -7 | zip("a", "b", *zip("c")) - - zip(zip("a"), strict=False) -8 + zip(zip("a", strict=False), strict=False) -9 | zip(zip("a", strict=True)) -10 | -11 | # OK -note: This is an unsafe fix and may change runtime behavior - -B905 [*] `zip()` without an explicit `strict=` parameter - --> B905.py:9:1 - | - 7 | zip("a", "b", *zip("c")) - 8 | zip(zip("a"), strict=False) - 9 | zip(zip("a", strict=True)) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ -10 | -11 | # OK - | -help: Add explicit value for parameter `strict=` -6 | zip("a", "b") -7 | zip("a", "b", *zip("c")) -8 | zip(zip("a"), strict=False) - - zip(zip("a", strict=True)) -9 + zip(zip("a", strict=True), strict=False) -10 | -11 | # OK -12 | zip(range(3), strict=True) +4 | zip("a", "b") +5 | zip("a", "b", *zip("c")) +6 | zip(zip("a", "b"), strict=False) + - zip(zip("a", strict=True),"b") +7 + zip(zip("a", strict=True),"b", strict=False) +8 | +9 | # OK +10 | zip(range(3), strict=True) note: This is an unsafe fix and may change runtime behavior B905 [*] `zip()` without an explicit `strict=` parameter - --> B905.py:24:1 + --> B905.py:22:1 | -23 | # Errors (limited iterators). -24 | zip([1, 2, 3], repeat(1, 1)) +21 | # Errors (limited iterators). +22 | zip([1, 2, 3], repeat(1, 1)) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -25 | zip([1, 2, 3], repeat(1, times=4)) +23 | zip([1, 2, 3], repeat(1, times=4)) | help: Add explicit value for parameter `strict=` -21 | zip([1, 2, 3], repeat(1, times=None)) -22 | -23 | # Errors (limited iterators). +19 | zip([1, 2, 3], repeat(1, times=None)) +20 | +21 | # Errors (limited iterators). - zip([1, 2, 3], repeat(1, 1)) -24 + zip([1, 2, 3], repeat(1, 1), strict=False) -25 | zip([1, 2, 3], repeat(1, times=4)) -26 | -27 | import builtins +22 + zip([1, 2, 3], repeat(1, 1), strict=False) +23 | zip([1, 2, 3], repeat(1, times=4)) +24 | +25 | import builtins note: This is an unsafe fix and may change runtime behavior B905 [*] `zip()` without an explicit `strict=` parameter - --> B905.py:25:1 + --> B905.py:23:1 | -23 | # Errors (limited iterators). -24 | zip([1, 2, 3], repeat(1, 1)) -25 | zip([1, 2, 3], repeat(1, times=4)) +21 | # Errors (limited iterators). +22 | zip([1, 2, 3], repeat(1, 1)) +23 | zip([1, 2, 3], repeat(1, times=4)) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -26 | -27 | import builtins +24 | +25 | import builtins | help: Add explicit value for parameter `strict=` -22 | -23 | # Errors (limited iterators). -24 | zip([1, 2, 3], repeat(1, 1)) +20 | +21 | # Errors (limited iterators). +22 | zip([1, 2, 3], repeat(1, 1)) - zip([1, 2, 3], repeat(1, times=4)) -25 + zip([1, 2, 3], repeat(1, times=4), strict=False) -26 | -27 | import builtins -28 | # Still an error even though it uses the qualified name +23 + zip([1, 2, 3], repeat(1, times=4), strict=False) +24 | +25 | import builtins +26 | # Still an error even though it uses the qualified name note: This is an unsafe fix and may change runtime behavior B905 [*] `zip()` without an explicit `strict=` parameter - --> B905.py:29:1 + --> B905.py:34:1 | -27 | import builtins -28 | # Still an error even though it uses the qualified name -29 | builtins.zip([1, 2, 3]) - | ^^^^^^^^^^^^^^^^^^^^^^^ +32 | zip(range(3)) +33 | # Error +34 | zip(*lot_of_iterators) + | ^^^^^^^^^^^^^^^^^^^^^^ | help: Add explicit value for parameter `strict=` -26 | -27 | import builtins -28 | # Still an error even though it uses the qualified name - - builtins.zip([1, 2, 3]) -29 + builtins.zip([1, 2, 3], strict=False) +31 | zip() +32 | zip(range(3)) +33 | # Error + - zip(*lot_of_iterators) +34 + zip(*lot_of_iterators, strict=False) note: This is an unsafe fix and may change runtime behavior diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__preview__B912_B912.py.snap b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__preview__B912_B912.py.snap index 98a2147bbaf0f7..32047fa1010ed8 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__preview__B912_B912.py.snap +++ b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__preview__B912_B912.py.snap @@ -1,6 +1,5 @@ --- source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs -assertion_line: 112 --- B912 [*] `map()` without an explicit `strict=` parameter --> B912.py:5:1 @@ -147,3 +146,18 @@ help: Add explicit value for parameter `strict=` 19 | # OK 20 | map(lambda x: x, [1, 2, 3], strict=True) note: This is an unsafe fix and may change runtime behavior + +B912 [*] `map()` without an explicit `strict=` parameter + --> B912.py:36:1 + | +35 | # Regression https://github.com/astral-sh/ruff/issues/20997 +36 | map(f, *lots_of_iterators) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: Add explicit value for parameter `strict=` +33 | map(lambda x, y: x + y, [1, 2, 3], count()) +34 | +35 | # Regression https://github.com/astral-sh/ruff/issues/20997 + - map(f, *lots_of_iterators) +36 + map(f, *lots_of_iterators, strict=False) +note: This is an unsafe fix and may change runtime behavior