[flake8-bugbear] Mark B905 and B912 fixes as unsafe (#20695)

Resolves https://github.com/astral-sh/ruff/issues/20694

This PR updates the `zip_without_explicit_strict` and
`map_without_explicit_strict` rules so their fixes are always marked
unsafe, following Brent's guidance that adding `strict=False` can
silently preserve buggy behaviour when inputs differ. The fix safety
docs now spell out that reasoning, the applicability drops to `Unsafe`,
and the snapshots were refreshed so Ruff clearly warns users before
applying the edit.
This commit is contained in:
liam 2025-10-07 16:55:56 -04:00 committed by GitHub
parent 7a347c4370
commit 2be73e9afb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 29 additions and 28 deletions

View file

@ -34,9 +34,10 @@ use crate::{AlwaysFixableViolation, Applicability, Fix};
/// ``` /// ```
/// ///
/// ## Fix safety /// ## Fix safety
/// This rule's fix is marked as unsafe for `map` calls that contain /// This rule's fix is marked as unsafe. While adding `strict=False` preserves
/// `**kwargs`, as adding a `strict` keyword argument to such a call may lead /// the runtime behavior, it can obscure situations where the iterables are of
/// to a duplicate keyword argument error. /// unequal length. Ruff prefers to alert users so they can choose the intended
/// behavior themselves.
/// ///
/// ## References /// ## References
/// - [Python documentation: `map`](https://docs.python.org/3/library/functions.html#map) /// - [Python documentation: `map`](https://docs.python.org/3/library/functions.html#map)
@ -73,17 +74,7 @@ pub(crate) fn map_without_explicit_strict(checker: &Checker, call: &ast::ExprCal
checker.comment_ranges(), checker.comment_ranges(),
checker.locator().contents(), checker.locator().contents(),
), ),
// If the function call contains `**kwargs`, mark the fix as unsafe. Applicability::Unsafe,
if call
.arguments
.keywords
.iter()
.any(|keyword| keyword.arg.is_none())
{
Applicability::Unsafe
} else {
Applicability::Safe
},
)); ));
} }
} }

View file

@ -31,9 +31,10 @@ use crate::{AlwaysFixableViolation, Applicability, Fix};
/// ``` /// ```
/// ///
/// ## Fix safety /// ## Fix safety
/// This rule's fix is marked as unsafe for `zip` calls that contain /// This rule's fix is marked as unsafe. While adding `strict=False` preserves
/// `**kwargs`, as adding a `strict` keyword argument to such a call may lead /// the runtime behavior, it can obscure situations where the iterables are of
/// to a duplicate keyword argument error. /// unequal length. Ruff prefers to alert users so they can choose the intended
/// behavior themselves.
/// ///
/// ## References /// ## References
/// - [Python documentation: `zip`](https://docs.python.org/3/library/functions.html#zip) /// - [Python documentation: `zip`](https://docs.python.org/3/library/functions.html#zip)
@ -68,17 +69,7 @@ pub(crate) fn zip_without_explicit_strict(checker: &Checker, call: &ast::ExprCal
checker.comment_ranges(), checker.comment_ranges(),
checker.locator().contents(), checker.locator().contents(),
), ),
// If the function call contains `**kwargs`, mark the fix as unsafe. Applicability::Unsafe,
if call
.arguments
.keywords
.iter()
.any(|keyword| keyword.arg.is_none())
{
Applicability::Unsafe
} else {
Applicability::Safe
},
)); ));
} }
} }

View file

@ -1,5 +1,6 @@
--- ---
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
assertion_line: 156
--- ---
B905 [*] `zip()` without an explicit `strict=` parameter B905 [*] `zip()` without an explicit `strict=` parameter
--> B905.py:4:1 --> B905.py:4:1
@ -19,6 +20,7 @@ help: Add explicit value for parameter `strict=`
5 | zip(range(3)) 5 | zip(range(3))
6 | zip("a", "b") 6 | zip("a", "b")
7 | zip("a", "b", *zip("c")) 7 | zip("a", "b", *zip("c"))
note: This is an unsafe fix and may change runtime behavior
B905 [*] `zip()` without an explicit `strict=` parameter B905 [*] `zip()` without an explicit `strict=` parameter
--> B905.py:5:1 --> B905.py:5:1
@ -39,6 +41,7 @@ help: Add explicit value for parameter `strict=`
6 | zip("a", "b") 6 | zip("a", "b")
7 | zip("a", "b", *zip("c")) 7 | zip("a", "b", *zip("c"))
8 | zip(zip("a"), strict=False) 8 | zip(zip("a"), strict=False)
note: This is an unsafe fix and may change runtime behavior
B905 [*] `zip()` without an explicit `strict=` parameter B905 [*] `zip()` without an explicit `strict=` parameter
--> B905.py:6:1 --> B905.py:6:1
@ -59,6 +62,7 @@ help: Add explicit value for parameter `strict=`
7 | zip("a", "b", *zip("c")) 7 | zip("a", "b", *zip("c"))
8 | zip(zip("a"), strict=False) 8 | zip(zip("a"), strict=False)
9 | zip(zip("a", strict=True)) 9 | zip(zip("a", strict=True))
note: This is an unsafe fix and may change runtime behavior
B905 [*] `zip()` without an explicit `strict=` parameter B905 [*] `zip()` without an explicit `strict=` parameter
--> B905.py:7:1 --> B905.py:7:1
@ -79,6 +83,7 @@ help: Add explicit value for parameter `strict=`
8 | zip(zip("a"), strict=False) 8 | zip(zip("a"), strict=False)
9 | zip(zip("a", strict=True)) 9 | zip(zip("a", strict=True))
10 | 10 |
note: This is an unsafe fix and may change runtime behavior
B905 [*] `zip()` without an explicit `strict=` parameter B905 [*] `zip()` without an explicit `strict=` parameter
--> B905.py:7:16 --> B905.py:7:16
@ -99,6 +104,7 @@ help: Add explicit value for parameter `strict=`
8 | zip(zip("a"), strict=False) 8 | zip(zip("a"), strict=False)
9 | zip(zip("a", strict=True)) 9 | zip(zip("a", strict=True))
10 | 10 |
note: This is an unsafe fix and may change runtime behavior
B905 [*] `zip()` without an explicit `strict=` parameter B905 [*] `zip()` without an explicit `strict=` parameter
--> B905.py:8:5 --> B905.py:8:5
@ -118,6 +124,7 @@ help: Add explicit value for parameter `strict=`
9 | zip(zip("a", strict=True)) 9 | zip(zip("a", strict=True))
10 | 10 |
11 | # OK 11 | # OK
note: This is an unsafe fix and may change runtime behavior
B905 [*] `zip()` without an explicit `strict=` parameter B905 [*] `zip()` without an explicit `strict=` parameter
--> B905.py:9:1 --> B905.py:9:1
@ -138,6 +145,7 @@ help: Add explicit value for parameter `strict=`
10 | 10 |
11 | # OK 11 | # OK
12 | zip(range(3), strict=True) 12 | zip(range(3), strict=True)
note: This is an unsafe fix and may change runtime behavior
B905 [*] `zip()` without an explicit `strict=` parameter B905 [*] `zip()` without an explicit `strict=` parameter
--> B905.py:24:1 --> B905.py:24:1
@ -156,6 +164,7 @@ help: Add explicit value for parameter `strict=`
25 | zip([1, 2, 3], repeat(1, times=4)) 25 | zip([1, 2, 3], repeat(1, times=4))
26 | 26 |
27 | import builtins 27 | import builtins
note: This is an unsafe fix and may change runtime behavior
B905 [*] `zip()` without an explicit `strict=` parameter B905 [*] `zip()` without an explicit `strict=` parameter
--> B905.py:25:1 --> B905.py:25:1
@ -176,6 +185,7 @@ help: Add explicit value for parameter `strict=`
26 | 26 |
27 | import builtins 27 | import builtins
28 | # Still an error even though it uses the qualified name 28 | # 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 [*] `zip()` without an explicit `strict=` parameter
--> B905.py:29:1 --> B905.py:29:1
@ -191,3 +201,4 @@ help: Add explicit value for parameter `strict=`
28 | # Still an error even though it uses the qualified name 28 | # Still an error even though it uses the qualified name
- builtins.zip([1, 2, 3]) - builtins.zip([1, 2, 3])
29 + builtins.zip([1, 2, 3], strict=False) 29 + builtins.zip([1, 2, 3], strict=False)
note: This is an unsafe fix and may change runtime behavior

View file

@ -1,5 +1,6 @@
--- ---
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
assertion_line: 112
--- ---
B912 [*] `map()` without an explicit `strict=` parameter B912 [*] `map()` without an explicit `strict=` parameter
--> B912.py:5:1 --> B912.py:5:1
@ -20,6 +21,7 @@ help: Add explicit value for parameter `strict=`
6 | map(lambda x, y, z: x + y + z, [1, 2, 3], [4, 5, 6], [7, 8, 9]) 6 | map(lambda x, y, z: x + y + z, [1, 2, 3], [4, 5, 6], [7, 8, 9])
7 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9])) 7 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]))
8 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]), strict=False) 8 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]), strict=False)
note: This is an unsafe fix and may change runtime behavior
B912 [*] `map()` without an explicit `strict=` parameter B912 [*] `map()` without an explicit `strict=` parameter
--> B912.py:6:1 --> B912.py:6:1
@ -40,6 +42,7 @@ help: Add explicit value for parameter `strict=`
7 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9])) 7 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]))
8 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]), strict=False) 8 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]), strict=False)
9 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9], strict=True)) 9 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9], strict=True))
note: This is an unsafe fix and may change runtime behavior
B912 [*] `map()` without an explicit `strict=` parameter B912 [*] `map()` without an explicit `strict=` parameter
--> B912.py:7:1 --> B912.py:7:1
@ -61,6 +64,7 @@ help: Add explicit value for parameter `strict=`
9 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9], strict=True)) 9 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9], strict=True))
10 | 10 |
11 | # Errors (limited iterators). 11 | # Errors (limited iterators).
note: This is an unsafe fix and may change runtime behavior
B912 [*] `map()` without an explicit `strict=` parameter B912 [*] `map()` without an explicit `strict=` parameter
--> B912.py:9:1 --> B912.py:9:1
@ -81,6 +85,7 @@ help: Add explicit value for parameter `strict=`
10 | 10 |
11 | # Errors (limited iterators). 11 | # Errors (limited iterators).
12 | map(lambda x, y: x + y, [1, 2, 3], repeat(1, 1)) 12 | map(lambda x, y: x + y, [1, 2, 3], repeat(1, 1))
note: This is an unsafe fix and may change runtime behavior
B912 [*] `map()` without an explicit `strict=` parameter B912 [*] `map()` without an explicit `strict=` parameter
--> B912.py:12:1 --> B912.py:12:1
@ -99,6 +104,7 @@ help: Add explicit value for parameter `strict=`
13 | map(lambda x, y: x + y, [1, 2, 3], repeat(1, times=4)) 13 | map(lambda x, y: x + y, [1, 2, 3], repeat(1, times=4))
14 | 14 |
15 | import builtins 15 | import builtins
note: This is an unsafe fix and may change runtime behavior
B912 [*] `map()` without an explicit `strict=` parameter B912 [*] `map()` without an explicit `strict=` parameter
--> B912.py:13:1 --> B912.py:13:1
@ -119,6 +125,7 @@ help: Add explicit value for parameter `strict=`
14 | 14 |
15 | import builtins 15 | import builtins
16 | # Still an error even though it uses the qualified name 16 | # Still an error even though it uses the qualified name
note: This is an unsafe fix and may change runtime behavior
B912 [*] `map()` without an explicit `strict=` parameter B912 [*] `map()` without an explicit `strict=` parameter
--> B912.py:17:1 --> B912.py:17:1
@ -139,3 +146,4 @@ help: Add explicit value for parameter `strict=`
18 | 18 |
19 | # OK 19 | # OK
20 | map(lambda x: x, [1, 2, 3], strict=True) 20 | map(lambda x: x, [1, 2, 3], strict=True)
note: This is an unsafe fix and may change runtime behavior