diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B912.py b/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B912.py new file mode 100644 index 0000000000..32c7afb2f8 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B912.py @@ -0,0 +1,33 @@ +from itertools import count, cycle, repeat + +# Errors +map(lambda x: x, [1, 2, 3]) +map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6]) +map(lambda x, y, z: x + y + z, [1, 2, 3], [4, 5, 6], [7, 8, 9]) +map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9])) +map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]), strict=False) +map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9], strict=True)) + +# Errors (limited iterators). +map(lambda x, y: x + y, [1, 2, 3], repeat(1, 1)) +map(lambda x, y: x + y, [1, 2, 3], repeat(1, times=4)) + +import builtins +# Still an error even though it uses the qualified name +builtins.map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6]) + +# OK +map(lambda x: x, [1, 2, 3], strict=True) +map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], strict=True) +map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], strict=False) +map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]), strict=True) +map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9], strict=True), strict=True) + +# OK (single iterable - no strict required) +map(lambda x: x, [1, 2, 3]) + +# OK (infinite iterators) +map(lambda x, y: x + y, [1, 2, 3], cycle([1, 2, 3])) +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()) diff --git a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs index 97dc1052db..75bea0342c 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs @@ -741,6 +741,11 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) { flake8_bugbear::rules::zip_without_explicit_strict(checker, call); } } + if checker.is_rule_enabled(Rule::MapWithoutExplicitStrict) { + if checker.target_version() >= PythonVersion::PY314 { + flake8_bugbear::rules::map_without_explicit_strict(checker, call); + } + } if checker.is_rule_enabled(Rule::NoExplicitStacklevel) { flake8_bugbear::rules::no_explicit_stacklevel(checker, call); } diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index eba1404f2b..9a588f09cb 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -394,6 +394,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Flake8Bugbear, "905") => (RuleGroup::Stable, rules::flake8_bugbear::rules::ZipWithoutExplicitStrict), (Flake8Bugbear, "909") => (RuleGroup::Preview, rules::flake8_bugbear::rules::LoopIteratorMutation), (Flake8Bugbear, "911") => (RuleGroup::Stable, rules::flake8_bugbear::rules::BatchedWithoutExplicitStrict), + (Flake8Bugbear, "912") => (RuleGroup::Preview, rules::flake8_bugbear::rules::MapWithoutExplicitStrict), // flake8-blind-except (Flake8BlindExcept, "001") => (RuleGroup::Stable, rules::flake8_blind_except::rules::BlindExcept), diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/helpers.rs b/crates/ruff_linter/src/rules/flake8_bugbear/helpers.rs index 51015eb63a..090658de7d 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/helpers.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/helpers.rs @@ -4,6 +4,7 @@ use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer}; use ruff_text_size::{Ranged, TextRange}; use crate::Locator; +use ruff_python_ast::{self as ast, Arguments, Expr}; /// Return `true` if the statement containing the current expression is the last /// top-level expression in the cell. This assumes that the source is a Jupyter @@ -27,3 +28,54 @@ pub(super) fn at_last_top_level_expression_in_cell( .all(|token| token.kind() == SimpleTokenKind::Semi || token.kind().is_trivia()) }) } + +/// Return `true` if the [`Expr`] appears to be an infinite iterator (e.g., a call to +/// `itertools.cycle` or similar). +pub(crate) fn is_infinite_iterable(arg: &Expr, semantic: &SemanticModel) -> bool { + let Expr::Call(ast::ExprCall { + func, + arguments: Arguments { args, keywords, .. }, + .. + }) = &arg + else { + return false; + }; + + semantic + .resolve_qualified_name(func) + .is_some_and(|qualified_name| match qualified_name.segments() { + ["itertools", "cycle" | "count"] => true, + ["itertools", "repeat"] => { + // Ex) `itertools.repeat(1)` + if keywords.is_empty() && args.len() == 1 { + return true; + } + + // Ex) `itertools.repeat(1, None)` + if args.len() == 2 && args[1].is_none_literal_expr() { + return true; + } + + // Ex) `itertools.repeat(1, times=None)` + for keyword in keywords { + if keyword.arg.as_ref().is_some_and(|name| name == "times") + && keyword.value.is_none_literal_expr() + { + return true; + } + } + + false + } + _ => false, + }) +} + +/// Return `true` if any expression in the iterator appears to be an infinite iterator. +pub(crate) fn any_infinite_iterables<'a>( + iter: impl IntoIterator, + semantic: &SemanticModel, +) -> bool { + iter.into_iter() + .any(|arg| is_infinite_iterable(arg, semantic)) +} diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/mod.rs b/crates/ruff_linter/src/rules/flake8_bugbear/mod.rs index 2a8bd80a0f..da0aa312e2 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/mod.rs @@ -16,6 +16,7 @@ mod tests { use crate::settings::LinterSettings; use crate::test::test_path; + use crate::settings::types::PreviewMode; use ruff_python_ast::PythonVersion; #[test_case(Rule::AbstractBaseClassWithoutAbstractMethod, Path::new("B024.py"))] @@ -81,11 +82,35 @@ mod tests { Ok(()) } + #[test_case(Rule::MapWithoutExplicitStrict, Path::new("B912.py"))] + fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> { + let snapshot = format!( + "preview__{}_{}", + rule_code.noqa_code(), + path.to_string_lossy() + ); + let diagnostics = test_path( + Path::new("flake8_bugbear").join(path).as_path(), + &LinterSettings { + preview: PreviewMode::Enabled, + unresolved_target_version: PythonVersion::PY314.into(), + ..LinterSettings::for_rule(rule_code) + }, + )?; + assert_diagnostics!(snapshot, diagnostics); + Ok(()) + } + #[test_case( Rule::ClassAsDataStructure, Path::new("class_as_data_structure.py"), PythonVersion::PY39 )] + #[test_case( + Rule::MapWithoutExplicitStrict, + Path::new("B912.py"), + PythonVersion::PY313 + )] fn rules_with_target_version( rule_code: Rule, path: &Path, diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/batched_without_explicit_strict.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/batched_without_explicit_strict.rs index e7c467ca84..78e29661b3 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/batched_without_explicit_strict.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/batched_without_explicit_strict.rs @@ -3,7 +3,7 @@ use ruff_python_ast::ExprCall; use ruff_python_ast::PythonVersion; use crate::checkers::ast::Checker; -use crate::rules::flake8_bugbear::rules::is_infinite_iterable; +use crate::rules::flake8_bugbear::helpers::is_infinite_iterable; use crate::{FixAvailability, Violation}; /// ## What it does 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 new file mode 100644 index 0000000000..49d6049374 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/map_without_explicit_strict.rs @@ -0,0 +1,89 @@ +use ruff_macros::{ViolationMetadata, derive_message_formats}; + +use ruff_python_ast::{self as ast}; +use ruff_text_size::Ranged; + +use crate::checkers::ast::Checker; +use crate::fix::edits::add_argument; +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. +/// +/// 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). +/// +/// ## Why is this bad? +/// By default, if the iterables passed to `map` are of different lengths, the +/// resulting iterator will be silently truncated to the length of the shortest +/// iterable. This can lead to subtle bugs. +/// +/// Pass `strict=True` to raise a `ValueError` if the iterables are of +/// non-uniform length. Alternatively, if the iterables are deliberately of +/// different lengths, pass `strict=False` to make the intention explicit. +/// +/// ## Example +/// ```python +/// map(f, a, b) +/// ``` +/// +/// Use instead: +/// ```python +/// map(f, a, b, strict=True) +/// ``` +/// +/// ## Fix safety +/// This rule's fix is marked as unsafe for `map` calls that contain +/// `**kwargs`, as adding a `strict` keyword argument to such a call may lead +/// to a duplicate keyword argument error. +/// +/// ## References +/// - [Python documentation: `map`](https://docs.python.org/3/library/functions.html#map) +/// - [What’s New in Python 3.14](https://docs.python.org/dev/whatsnew/3.14.html) +#[derive(ViolationMetadata)] +pub(crate) struct MapWithoutExplicitStrict; + +impl AlwaysFixableViolation for MapWithoutExplicitStrict { + #[derive_message_formats] + fn message(&self) -> String { + "`map()` without an explicit `strict=` parameter".to_string() + } + + fn fix_title(&self) -> String { + "Add explicit value for parameter `strict=`".to_string() + } +} + +/// B912 +pub(crate) fn map_without_explicit_strict(checker: &Checker, call: &ast::ExprCall) { + let semantic = checker.semantic(); + + 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 + && !any_infinite_iterables(call.arguments.args.iter().skip(1), semantic) + { + checker + .report_diagnostic(MapWithoutExplicitStrict, call.range()) + .set_fix(Fix::applicable_edit( + add_argument( + "strict=False", + &call.arguments, + checker.comment_ranges(), + checker.locator().contents(), + ), + // If the function call contains `**kwargs`, mark the fix as unsafe. + if call + .arguments + .keywords + .iter() + .any(|keyword| keyword.arg.is_none()) + { + Applicability::Unsafe + } else { + Applicability::Safe + }, + )); + } +} diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/mod.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/mod.rs index dcf29d0fc1..e0beabd7cb 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/mod.rs @@ -16,6 +16,7 @@ pub(crate) use getattr_with_constant::*; pub(crate) use jump_statement_in_finally::*; pub(crate) use loop_iterator_mutation::*; pub(crate) use loop_variable_overrides_iterator::*; +pub(crate) use map_without_explicit_strict::*; pub(crate) use mutable_argument_default::*; pub(crate) use mutable_contextvar_default::*; pub(crate) use no_explicit_stacklevel::*; @@ -56,6 +57,7 @@ mod getattr_with_constant; mod jump_statement_in_finally; mod loop_iterator_mutation; mod loop_variable_overrides_iterator; +mod map_without_explicit_strict; mod mutable_argument_default; mod mutable_contextvar_default; mod no_explicit_stacklevel; 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 73134c65b6..9a4694491e 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 @@ -1,11 +1,11 @@ use ruff_macros::{ViolationMetadata, derive_message_formats}; -use ruff_python_ast::{self as ast, Arguments, Expr}; -use ruff_python_semantic::SemanticModel; +use ruff_python_ast::{self as ast}; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::fix::edits::add_argument; +use crate::rules::flake8_bugbear::helpers::any_infinite_iterables; use crate::{AlwaysFixableViolation, Applicability, Fix}; /// ## What it does @@ -57,11 +57,7 @@ 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() - && !call - .arguments - .args - .iter() - .any(|arg| is_infinite_iterable(arg, semantic)) + && !any_infinite_iterables(call.arguments.args.iter(), semantic) { checker .report_diagnostic(ZipWithoutExplicitStrict, call.range()) @@ -86,47 +82,3 @@ pub(crate) fn zip_without_explicit_strict(checker: &Checker, call: &ast::ExprCal )); } } - -/// Return `true` if the [`Expr`] appears to be an infinite iterator (e.g., a call to -/// `itertools.cycle` or similar). -pub(crate) fn is_infinite_iterable(arg: &Expr, semantic: &SemanticModel) -> bool { - let Expr::Call(ast::ExprCall { - func, - arguments: Arguments { args, keywords, .. }, - .. - }) = &arg - else { - return false; - }; - - semantic - .resolve_qualified_name(func) - .is_some_and(|qualified_name| { - match qualified_name.segments() { - ["itertools", "cycle" | "count"] => true, - ["itertools", "repeat"] => { - // Ex) `itertools.repeat(1)` - if keywords.is_empty() && args.len() == 1 { - return true; - } - - // Ex) `itertools.repeat(1, None)` - if args.len() == 2 && args[1].is_none_literal_expr() { - return true; - } - - // Ex) `iterools.repeat(1, times=None)` - for keyword in keywords { - if keyword.arg.as_ref().is_some_and(|name| name == "times") { - if keyword.value.is_none_literal_expr() { - return true; - } - } - } - - false - } - _ => false, - } - }) -} diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B912_py313_B912.py.snap b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B912_py313_B912.py.snap new file mode 100644 index 0000000000..967e60a4f9 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B912_py313_B912.py.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs +--- + 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 new file mode 100644 index 0000000000..102f1ce8ff --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__preview__B912_B912.py.snap @@ -0,0 +1,141 @@ +--- +source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs +--- +B912 [*] `map()` without an explicit `strict=` parameter + --> B912.py:5:1 + | +3 | # Errors +4 | map(lambda x: x, [1, 2, 3]) +5 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6]) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +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])) + | +help: Add explicit value for parameter `strict=` +2 | +3 | # Errors +4 | map(lambda x: x, [1, 2, 3]) + - map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6]) +5 + map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], strict=False) +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])) +8 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]), strict=False) + +B912 [*] `map()` without an explicit `strict=` parameter + --> B912.py:6:1 + | +4 | map(lambda x: x, [1, 2, 3]) +5 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6]) +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])) +8 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9]), strict=False) + | +help: Add explicit value for parameter `strict=` +3 | # Errors +4 | map(lambda x: x, [1, 2, 3]) +5 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 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], strict=False) +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) +9 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9], strict=True)) + +B912 [*] `map()` without an explicit `strict=` parameter + --> B912.py:7:1 + | +5 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6]) +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])) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +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)) + | +help: Add explicit value for parameter `strict=` +4 | map(lambda x: x, [1, 2, 3]) +5 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6]) +6 | map(lambda x, y, z: x + y + z, [1, 2, 3], [4, 5, 6], [7, 8, 9]) + - 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]), 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)) +10 | +11 | # Errors (limited iterators). + +B912 [*] `map()` without an explicit `strict=` parameter + --> B912.py:9:1 + | + 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) + 9 | map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], *map(lambda x: x, [7, 8, 9], strict=True)) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +10 | +11 | # Errors (limited iterators). + | +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]) +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) + - 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), strict=False) +10 | +11 | # Errors (limited iterators). +12 | map(lambda x, y: x + y, [1, 2, 3], repeat(1, 1)) + +B912 [*] `map()` without an explicit `strict=` parameter + --> B912.py:12:1 + | +11 | # Errors (limited iterators). +12 | map(lambda x, y: x + y, [1, 2, 3], repeat(1, 1)) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +13 | map(lambda x, y: x + y, [1, 2, 3], repeat(1, times=4)) + | +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)) +10 | +11 | # Errors (limited iterators). + - 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), strict=False) +13 | map(lambda x, y: x + y, [1, 2, 3], repeat(1, times=4)) +14 | +15 | import builtins + +B912 [*] `map()` without an explicit `strict=` parameter + --> B912.py:13:1 + | +11 | # Errors (limited iterators). +12 | map(lambda x, y: x + y, [1, 2, 3], repeat(1, 1)) +13 | map(lambda x, y: x + y, [1, 2, 3], repeat(1, times=4)) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +14 | +15 | import builtins + | +help: Add explicit value for parameter `strict=` +10 | +11 | # Errors (limited iterators). +12 | map(lambda x, y: x + y, [1, 2, 3], repeat(1, 1)) + - 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), strict=False) +14 | +15 | import builtins +16 | # Still an error even though it uses the qualified name + +B912 [*] `map()` without an explicit `strict=` parameter + --> B912.py:17:1 + | +15 | import builtins +16 | # Still an error even though it uses the qualified name +17 | builtins.map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6]) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +18 | +19 | # OK + | +help: Add explicit value for parameter `strict=` +14 | +15 | import builtins +16 | # Still an error even though it uses the qualified name + - builtins.map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6]) +17 + builtins.map(lambda x, y: x + y, [1, 2, 3], [4, 5, 6], strict=False) +18 | +19 | # OK +20 | map(lambda x: x, [1, 2, 3], strict=True) diff --git a/ruff.schema.json b/ruff.schema.json index 97fec6ae32..1edc77e55d 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -3073,6 +3073,7 @@ "B909", "B91", "B911", + "B912", "BLE", "BLE0", "BLE00",