diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B006_9.py b/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B006_9.py new file mode 100644 index 0000000000..c0dcd4008c --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B006_9.py @@ -0,0 +1,26 @@ +def f1(x=([],)): + print(x) + + +def f2(x=(x for x in "x")): + print(x) + + +def f3(x=((x for x in "x"),)): + print(x) + + +def f4(x=(z := [1, ])): + print(x) + + +def f5(x=([1, ])): + print(x) + + +def w1(x=(1,)): + print(x) + + +def w2(x=(z := 3)): + print(x) diff --git a/crates/ruff_linter/src/preview.rs b/crates/ruff_linter/src/preview.rs index a803e0ac9c..8266a5b4a8 100644 --- a/crates/ruff_linter/src/preview.rs +++ b/crates/ruff_linter/src/preview.rs @@ -245,3 +245,17 @@ pub(crate) const fn is_a003_class_scope_shadowing_expansion_enabled( pub(crate) const fn is_refined_submodule_import_match_enabled(settings: &LinterSettings) -> bool { settings.preview.is_enabled() } + +// github.com/astral-sh/ruff/issues/20004 +pub(crate) const fn is_b006_check_guaranteed_mutable_expr_enabled( + settings: &LinterSettings, +) -> bool { + settings.preview.is_enabled() +} + +// github.com/astral-sh/ruff/issues/20004 +pub(crate) const fn is_b006_unsafe_fix_preserve_assignment_expr_enabled( + settings: &LinterSettings, +) -> bool { + settings.preview.is_enabled() +} diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/mod.rs b/crates/ruff_linter/src/rules/flake8_bugbear/mod.rs index da0aa312e2..163d175872 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/mod.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/mod.rs @@ -47,6 +47,7 @@ mod tests { #[test_case(Rule::MutableArgumentDefault, Path::new("B006_6.py"))] #[test_case(Rule::MutableArgumentDefault, Path::new("B006_7.py"))] #[test_case(Rule::MutableArgumentDefault, Path::new("B006_8.py"))] + #[test_case(Rule::MutableArgumentDefault, Path::new("B006_9.py"))] #[test_case(Rule::MutableArgumentDefault, Path::new("B006_B008.py"))] #[test_case(Rule::MutableArgumentDefault, Path::new("B006_1.pyi"))] #[test_case(Rule::NoExplicitStacklevel, Path::new("B028.py"))] @@ -83,6 +84,17 @@ mod tests { } #[test_case(Rule::MapWithoutExplicitStrict, Path::new("B912.py"))] + #[test_case(Rule::MutableArgumentDefault, Path::new("B006_1.py"))] + #[test_case(Rule::MutableArgumentDefault, Path::new("B006_2.py"))] + #[test_case(Rule::MutableArgumentDefault, Path::new("B006_3.py"))] + #[test_case(Rule::MutableArgumentDefault, Path::new("B006_4.py"))] + #[test_case(Rule::MutableArgumentDefault, Path::new("B006_5.py"))] + #[test_case(Rule::MutableArgumentDefault, Path::new("B006_6.py"))] + #[test_case(Rule::MutableArgumentDefault, Path::new("B006_7.py"))] + #[test_case(Rule::MutableArgumentDefault, Path::new("B006_8.py"))] + #[test_case(Rule::MutableArgumentDefault, Path::new("B006_9.py"))] + #[test_case(Rule::MutableArgumentDefault, Path::new("B006_B008.py"))] + #[test_case(Rule::MutableArgumentDefault, Path::new("B006_1.pyi"))] fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!( "preview__{}_{}", diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_argument_default.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_argument_default.rs index 960ddbfdfb..7e13ba5aee 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_argument_default.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_argument_default.rs @@ -3,9 +3,8 @@ use std::fmt::Write; use ruff_macros::{ViolationMetadata, derive_message_formats}; use ruff_python_ast::helpers::is_docstring_stmt; use ruff_python_ast::name::QualifiedName; -use ruff_python_ast::{self as ast, Expr, Parameter}; -use ruff_python_codegen::{Generator, Stylist}; -use ruff_python_index::Indexer; +use ruff_python_ast::parenthesize::parenthesized_range; +use ruff_python_ast::{self as ast, Expr, ParameterWithDefault}; use ruff_python_semantic::SemanticModel; use ruff_python_semantic::analyze::function_type::is_stub; use ruff_python_semantic::analyze::typing::{is_immutable_annotation, is_mutable_expr}; @@ -13,8 +12,11 @@ use ruff_python_trivia::{indentation_at_offset, textwrap}; use ruff_source_file::LineRanges; use ruff_text_size::Ranged; -use crate::Locator; use crate::checkers::ast::Checker; +use crate::preview::{ + is_b006_check_guaranteed_mutable_expr_enabled, + is_b006_unsafe_fix_preserve_assignment_expr_enabled, +}; use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does @@ -111,8 +113,12 @@ pub(crate) fn mutable_argument_default(checker: &Checker, function_def: &ast::St .iter() .map(|target| QualifiedName::from_dotted_name(target)) .collect(); - - if is_mutable_expr(default, checker.semantic()) + let is_mut_expr = if is_b006_check_guaranteed_mutable_expr_enabled(checker.settings()) { + is_guaranteed_mutable_expr(default, checker.semantic()) + } else { + is_mutable_expr(default, checker.semantic()) + }; + if is_mut_expr && !parameter.annotation().is_some_and(|expr| { is_immutable_annotation(expr, checker.semantic(), extend_immutable_calls.as_slice()) }) @@ -120,35 +126,37 @@ pub(crate) fn mutable_argument_default(checker: &Checker, function_def: &ast::St let mut diagnostic = checker.report_diagnostic(MutableArgumentDefault, default.range()); // If the function body is on the same line as the function def, do not fix - if let Some(fix) = move_initialization( - function_def, - ¶meter.parameter, - default, - checker.semantic(), - checker.locator(), - checker.stylist(), - checker.indexer(), - checker.generator(), - ) { + if let Some(fix) = move_initialization(function_def, parameter, default, checker) { diagnostic.set_fix(fix); } } } } +/// Returns `true` if the expression is guaranteed to create a mutable object. +fn is_guaranteed_mutable_expr(expr: &Expr, semantic: &SemanticModel) -> bool { + match expr { + Expr::Generator(_) => true, + Expr::Tuple(ast::ExprTuple { elts, .. }) => { + elts.iter().any(|e| is_guaranteed_mutable_expr(e, semantic)) + } + Expr::Named(ast::ExprNamed { value, .. }) => is_guaranteed_mutable_expr(value, semantic), + _ => is_mutable_expr(expr, semantic), + } +} + /// Generate a [`Fix`] to move a mutable argument default initialization /// into the function body. -#[expect(clippy::too_many_arguments)] fn move_initialization( function_def: &ast::StmtFunctionDef, - parameter: &Parameter, + parameter: &ParameterWithDefault, default: &Expr, - semantic: &SemanticModel, - locator: &Locator, - stylist: &Stylist, - indexer: &Indexer, - generator: Generator, + checker: &Checker, ) -> Option { + let indexer = checker.indexer(); + let locator = checker.locator(); + let stylist = checker.stylist(); + let mut body = function_def.body.iter().peekable(); // Avoid attempting to fix single-line functions. @@ -157,25 +165,58 @@ fn move_initialization( return None; } + let range = match parenthesized_range( + default.into(), + parameter.into(), + checker.comment_ranges(), + checker.source(), + ) { + Some(range) => range, + None => default.range(), + }; // Set the default argument value to `None`. - let default_edit = Edit::range_replacement("None".to_string(), default.range()); + let default_edit = Edit::range_replacement("None".to_string(), range); // If the function is a stub, this is the only necessary edit. - if is_stub(function_def, semantic) { + if is_stub(function_def, checker.semantic()) { return Some(Fix::unsafe_edit(default_edit)); } // Add an `if`, to set the argument to its original value if still `None`. let mut content = String::new(); - let _ = write!(&mut content, "if {} is None:", parameter.name()); + let _ = write!(&mut content, "if {} is None:", parameter.parameter.name()); content.push_str(stylist.line_ending().as_str()); content.push_str(stylist.indentation()); - let _ = write!( - &mut content, - "{} = {}", - parameter.name(), - generator.expr(default) - ); + if is_b006_unsafe_fix_preserve_assignment_expr_enabled(checker.settings()) { + let annotation = if let Some(ann) = parameter.annotation() { + format!(": {}", locator.slice(ann)) + } else { + String::new() + }; + let _ = write!( + &mut content, + "{}{} = {}", + parameter.parameter.name(), + annotation, + locator.slice( + parenthesized_range( + default.into(), + parameter.into(), + checker.comment_ranges(), + checker.source() + ) + .unwrap_or(default.range()) + ) + ); + } else { + let _ = write!( + &mut content, + "{} = {}", + parameter.name(), + checker.generator().expr(default) + ); + } + content.push_str(stylist.line_ending().as_str()); // Determine the indentation depth of the function body. diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B006_B006_9.py.snap b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B006_B006_9.py.snap new file mode 100644 index 0000000000..259771063d --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B006_B006_9.py.snap @@ -0,0 +1,22 @@ +--- +source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs +--- +B006 [*] Do not use mutable data structures for argument defaults + --> B006_9.py:17:11 + | +17 | def f5(x=([1, ])): + | ^^^^^ +18 | print(x) + | +help: Replace with `None`; initialize within function +14 | print(x) +15 | +16 | + - def f5(x=([1, ])): +17 + def f5(x=None): +18 + if x is None: +19 + x = [1] +20 | print(x) +21 | +22 | +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__B006_B006_1.py.snap b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__preview__B006_B006_1.py.snap new file mode 100644 index 0000000000..af07dc5476 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__preview__B006_B006_1.py.snap @@ -0,0 +1,21 @@ +--- +source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs +--- +B006 [*] Do not use mutable data structures for argument defaults + --> B006_1.py:3:22 + | +1 | # Docstring followed by a newline +2 | +3 | def foobar(foor, bar={}): + | ^^ +4 | """ +5 | """ + | +help: Replace with `None`; initialize within function +1 | # Docstring followed by a newline +2 | + - def foobar(foor, bar={}): +3 + def foobar(foor, bar=None): +4 | """ +5 | """ +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__B006_B006_1.pyi.snap b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__preview__B006_B006_1.pyi.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__preview__B006_B006_1.pyi.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__B006_B006_2.py.snap b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__preview__B006_B006_2.py.snap new file mode 100644 index 0000000000..9c1184adae --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__preview__B006_B006_2.py.snap @@ -0,0 +1,22 @@ +--- +source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs +--- +B006 [*] Do not use mutable data structures for argument defaults + --> B006_2.py:4:22 + | +2 | # Regression test for https://github.com/astral-sh/ruff/issues/7155 +3 | +4 | def foobar(foor, bar={}): + | ^^ +5 | """ +6 | """ + | +help: Replace with `None`; initialize within function +1 | # Docstring followed by whitespace with no newline +2 | # Regression test for https://github.com/astral-sh/ruff/issues/7155 +3 | + - def foobar(foor, bar={}): +4 + def foobar(foor, bar=None): +5 | """ +6 | """ +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__B006_B006_3.py.snap b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__preview__B006_B006_3.py.snap new file mode 100644 index 0000000000..acf948afd7 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__preview__B006_B006_3.py.snap @@ -0,0 +1,20 @@ +--- +source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs +--- +B006 [*] Do not use mutable data structures for argument defaults + --> B006_3.py:4:22 + | +4 | def foobar(foor, bar={}): + | ^^ +5 | """ +6 | """ + | +help: Replace with `None`; initialize within function +1 | # Docstring with no newline +2 | +3 | + - def foobar(foor, bar={}): +4 + def foobar(foor, bar=None): +5 | """ +6 | """ +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__B006_B006_4.py.snap b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__preview__B006_B006_4.py.snap new file mode 100644 index 0000000000..98d4e56b34 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__preview__B006_B006_4.py.snap @@ -0,0 +1,22 @@ +--- +source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs +--- +B006 [*] Do not use mutable data structures for argument defaults + --> B006_4.py:7:26 + | +6 | class FormFeedIndent: +7 | def __init__(self, a=[]): + | ^^ +8 | print(a) + | +help: Replace with `None`; initialize within function +4 | +5 | +6 | class FormFeedIndent: + - def __init__(self, a=[]): +7 + def __init__(self, a=None): +8 + if a is None: +9 + a = [] +10 | print(a) +11 | +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__B006_B006_5.py.snap b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__preview__B006_B006_5.py.snap new file mode 100644 index 0000000000..de544081f7 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__preview__B006_B006_5.py.snap @@ -0,0 +1,314 @@ +--- +source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs +--- +B006 [*] Do not use mutable data structures for argument defaults + --> B006_5.py:5:49 + | +5 | def import_module_wrong(value: dict[str, str] = {}): + | ^^ +6 | import os + | +help: Replace with `None`; initialize within function +2 | # https://github.com/astral-sh/ruff/issues/7616 +3 | +4 | + - def import_module_wrong(value: dict[str, str] = {}): +5 + def import_module_wrong(value: dict[str, str] = None): +6 | import os +7 + if value is None: +8 + value: dict[str, str] = {} +9 | +10 | +11 | def import_module_with_values_wrong(value: dict[str, str] = {}): +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_5.py:9:61 + | + 9 | def import_module_with_values_wrong(value: dict[str, str] = {}): + | ^^ +10 | import os + | +help: Replace with `None`; initialize within function +6 | import os +7 | +8 | + - def import_module_with_values_wrong(value: dict[str, str] = {}): +9 + def import_module_with_values_wrong(value: dict[str, str] = None): +10 | import os +11 | +12 + if value is None: +13 + value: dict[str, str] = {} +14 | return 2 +15 | +16 | +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_5.py:15:50 + | +15 | def import_modules_wrong(value: dict[str, str] = {}): + | ^^ +16 | import os +17 | import sys + | +help: Replace with `None`; initialize within function +12 | return 2 +13 | +14 | + - def import_modules_wrong(value: dict[str, str] = {}): +15 + def import_modules_wrong(value: dict[str, str] = None): +16 | import os +17 | import sys +18 | import itertools +19 + if value is None: +20 + value: dict[str, str] = {} +21 | +22 | +23 | def from_import_module_wrong(value: dict[str, str] = {}): +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_5.py:21:54 + | +21 | def from_import_module_wrong(value: dict[str, str] = {}): + | ^^ +22 | from os import path + | +help: Replace with `None`; initialize within function +18 | import itertools +19 | +20 | + - def from_import_module_wrong(value: dict[str, str] = {}): +21 + def from_import_module_wrong(value: dict[str, str] = None): +22 | from os import path +23 + if value is None: +24 + value: dict[str, str] = {} +25 | +26 | +27 | def from_imports_module_wrong(value: dict[str, str] = {}): +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_5.py:25:55 + | +25 | def from_imports_module_wrong(value: dict[str, str] = {}): + | ^^ +26 | from os import path +27 | from sys import version_info + | +help: Replace with `None`; initialize within function +22 | from os import path +23 | +24 | + - def from_imports_module_wrong(value: dict[str, str] = {}): +25 + def from_imports_module_wrong(value: dict[str, str] = None): +26 | from os import path +27 | from sys import version_info +28 + if value is None: +29 + value: dict[str, str] = {} +30 | +31 | +32 | def import_and_from_imports_module_wrong(value: dict[str, str] = {}): +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_5.py:30:66 + | +30 | def import_and_from_imports_module_wrong(value: dict[str, str] = {}): + | ^^ +31 | import os +32 | from sys import version_info + | +help: Replace with `None`; initialize within function +27 | from sys import version_info +28 | +29 | + - def import_and_from_imports_module_wrong(value: dict[str, str] = {}): +30 + def import_and_from_imports_module_wrong(value: dict[str, str] = None): +31 | import os +32 | from sys import version_info +33 + if value is None: +34 + value: dict[str, str] = {} +35 | +36 | +37 | def import_docstring_module_wrong(value: dict[str, str] = {}): +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_5.py:35:59 + | +35 | def import_docstring_module_wrong(value: dict[str, str] = {}): + | ^^ +36 | """Docstring""" +37 | import os + | +help: Replace with `None`; initialize within function +32 | from sys import version_info +33 | +34 | + - def import_docstring_module_wrong(value: dict[str, str] = {}): +35 + def import_docstring_module_wrong(value: dict[str, str] = None): +36 | """Docstring""" +37 | import os +38 + if value is None: +39 + value: dict[str, str] = {} +40 | +41 | +42 | def import_module_wrong(value: dict[str, str] = {}): +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_5.py:40:49 + | +40 | def import_module_wrong(value: dict[str, str] = {}): + | ^^ +41 | """Docstring""" +42 | import os; import sys + | +help: Replace with `None`; initialize within function +37 | import os +38 | +39 | + - def import_module_wrong(value: dict[str, str] = {}): +40 + def import_module_wrong(value: dict[str, str] = None): +41 | """Docstring""" +42 | import os; import sys +43 + if value is None: +44 + value: dict[str, str] = {} +45 | +46 | +47 | def import_module_wrong(value: dict[str, str] = {}): +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_5.py:45:49 + | +45 | def import_module_wrong(value: dict[str, str] = {}): + | ^^ +46 | """Docstring""" +47 | import os; import sys; x = 1 + | +help: Replace with `None`; initialize within function +42 | import os; import sys +43 | +44 | + - def import_module_wrong(value: dict[str, str] = {}): +45 + def import_module_wrong(value: dict[str, str] = None): +46 | """Docstring""" +47 + if value is None: +48 + value: dict[str, str] = {} +49 | import os; import sys; x = 1 +50 | +51 | +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_5.py:50:49 + | +50 | def import_module_wrong(value: dict[str, str] = {}): + | ^^ +51 | """Docstring""" +52 | import os; import sys + | +help: Replace with `None`; initialize within function +47 | import os; import sys; x = 1 +48 | +49 | + - def import_module_wrong(value: dict[str, str] = {}): +50 + def import_module_wrong(value: dict[str, str] = None): +51 | """Docstring""" +52 | import os; import sys +53 + if value is None: +54 + value: dict[str, str] = {} +55 | +56 | +57 | def import_module_wrong(value: dict[str, str] = {}): +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_5.py:55:49 + | +55 | def import_module_wrong(value: dict[str, str] = {}): + | ^^ +56 | import os; import sys + | +help: Replace with `None`; initialize within function +52 | import os; import sys +53 | +54 | + - def import_module_wrong(value: dict[str, str] = {}): +55 + def import_module_wrong(value: dict[str, str] = None): +56 | import os; import sys +57 + if value is None: +58 + value: dict[str, str] = {} +59 | +60 | +61 | def import_module_wrong(value: dict[str, str] = {}): +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_5.py:59:49 + | +59 | def import_module_wrong(value: dict[str, str] = {}): + | ^^ +60 | import os; import sys; x = 1 + | +help: Replace with `None`; initialize within function +56 | import os; import sys +57 | +58 | + - def import_module_wrong(value: dict[str, str] = {}): +59 + def import_module_wrong(value: dict[str, str] = None): +60 + if value is None: +61 + value: dict[str, str] = {} +62 | import os; import sys; x = 1 +63 | +64 | +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_5.py:63:49 + | +63 | def import_module_wrong(value: dict[str, str] = {}): + | ^^ +64 | import os; import sys + | +help: Replace with `None`; initialize within function +60 | import os; import sys; x = 1 +61 | +62 | + - def import_module_wrong(value: dict[str, str] = {}): +63 + def import_module_wrong(value: dict[str, str] = None): +64 | import os; import sys +65 + if value is None: +66 + value: dict[str, str] = {} +67 | +68 | +69 | def import_module_wrong(value: dict[str, str] = {}): import os +note: This is an unsafe fix and may change runtime behavior + +B006 Do not use mutable data structures for argument defaults + --> B006_5.py:67:49 + | +67 | def import_module_wrong(value: dict[str, str] = {}): import os + | ^^ + | +help: Replace with `None`; initialize within function + +B006 Do not use mutable data structures for argument defaults + --> B006_5.py:70:49 + | +70 | def import_module_wrong(value: dict[str, str] = {}): import os; import sys + | ^^ + | +help: Replace with `None`; initialize within function + +B006 Do not use mutable data structures for argument defaults + --> B006_5.py:73:49 + | +73 | def import_module_wrong(value: dict[str, str] = {}): \ + | ^^ +74 | import os + | +help: Replace with `None`; initialize within function diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__preview__B006_B006_6.py.snap b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__preview__B006_B006_6.py.snap new file mode 100644 index 0000000000..40e631db6c --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__preview__B006_B006_6.py.snap @@ -0,0 +1,23 @@ +--- +source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs +--- +B006 [*] Do not use mutable data structures for argument defaults + --> B006_6.py:4:22 + | +2 | # Same as B006_2.py, but import instead of docstring +3 | +4 | def foobar(foor, bar={}): + | ^^ +5 | import os + | +help: Replace with `None`; initialize within function +1 | # Import followed by whitespace with no newline +2 | # Same as B006_2.py, but import instead of docstring +3 | + - def foobar(foor, bar={}): + - import os +4 + def foobar(foor, bar=None): +5 + import os +6 + if bar is None: +7 + bar = {} +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__B006_B006_7.py.snap b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__preview__B006_B006_7.py.snap new file mode 100644 index 0000000000..b5dc7a5d76 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__preview__B006_B006_7.py.snap @@ -0,0 +1,23 @@ +--- +source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs +--- +B006 [*] Do not use mutable data structures for argument defaults + --> B006_7.py:4:22 + | +2 | # Same as B006_3.py, but import instead of docstring +3 | +4 | def foobar(foor, bar={}): + | ^^ +5 | import os + | +help: Replace with `None`; initialize within function +1 | # Import with no newline +2 | # Same as B006_3.py, but import instead of docstring +3 | + - def foobar(foor, bar={}): + - import os +4 + def foobar(foor, bar=None): +5 + import os +6 + if bar is None: +7 + bar = {} +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__B006_B006_8.py.snap b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__preview__B006_B006_8.py.snap new file mode 100644 index 0000000000..a8a6f4fdea --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__preview__B006_B006_8.py.snap @@ -0,0 +1,92 @@ +--- +source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs +--- +B006 [*] Do not use mutable data structures for argument defaults + --> B006_8.py:1:19 + | +1 | def foo(a: list = []): + | ^^ +2 | raise NotImplementedError("") + | +help: Replace with `None`; initialize within function + - def foo(a: list = []): +1 + def foo(a: list = None): +2 | raise NotImplementedError("") +3 | +4 | +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_8.py:5:19 + | +5 | def bar(a: dict = {}): + | ^^ +6 | """ This one also has a docstring""" +7 | raise NotImplementedError("and has some text in here") + | +help: Replace with `None`; initialize within function +2 | raise NotImplementedError("") +3 | +4 | + - def bar(a: dict = {}): +5 + def bar(a: dict = None): +6 | """ This one also has a docstring""" +7 | raise NotImplementedError("and has some text in here") +8 | +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_8.py:10:19 + | +10 | def baz(a: list = []): + | ^^ +11 | """This one raises a different exception""" +12 | raise IndexError() + | +help: Replace with `None`; initialize within function +7 | raise NotImplementedError("and has some text in here") +8 | +9 | + - def baz(a: list = []): +10 + def baz(a: list = None): +11 | """This one raises a different exception""" +12 + if a is None: +13 + a: list = [] +14 | raise IndexError() +15 | +16 | +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_8.py:15:19 + | +15 | def qux(a: list = []): + | ^^ +16 | raise NotImplementedError + | +help: Replace with `None`; initialize within function +12 | raise IndexError() +13 | +14 | + - def qux(a: list = []): +15 + def qux(a: list = None): +16 | raise NotImplementedError +17 | +18 | +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_8.py:19:20 + | +19 | def quux(a: list = []): + | ^^ +20 | raise NotImplemented + | +help: Replace with `None`; initialize within function +16 | raise NotImplementedError +17 | +18 | + - def quux(a: list = []): +19 + def quux(a: list = None): +20 | raise NotImplemented +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__B006_B006_9.py.snap b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__preview__B006_B006_9.py.snap new file mode 100644 index 0000000000..a94f89a9c5 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__preview__B006_B006_9.py.snap @@ -0,0 +1,99 @@ +--- +source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs +--- +B006 [*] Do not use mutable data structures for argument defaults + --> B006_9.py:1:10 + | +1 | def f1(x=([],)): + | ^^^^^ +2 | print(x) + | +help: Replace with `None`; initialize within function + - def f1(x=([],)): +1 + def f1(x=None): +2 + if x is None: +3 + x = ([],) +4 | print(x) +5 | +6 | +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_9.py:5:10 + | +5 | def f2(x=(x for x in "x")): + | ^^^^^^^^^^^^^^^^ +6 | print(x) + | +help: Replace with `None`; initialize within function +2 | print(x) +3 | +4 | + - def f2(x=(x for x in "x")): +5 + def f2(x=None): +6 + if x is None: +7 + x = (x for x in "x") +8 | print(x) +9 | +10 | +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_9.py:9:10 + | + 9 | def f3(x=((x for x in "x"),)): + | ^^^^^^^^^^^^^^^^^^^ +10 | print(x) + | +help: Replace with `None`; initialize within function +6 | print(x) +7 | +8 | + - def f3(x=((x for x in "x"),)): +9 + def f3(x=None): +10 + if x is None: +11 + x = ((x for x in "x"),) +12 | print(x) +13 | +14 | +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_9.py:13:11 + | +13 | def f4(x=(z := [1, ])): + | ^^^^^^^^^^ +14 | print(x) + | +help: Replace with `None`; initialize within function +10 | print(x) +11 | +12 | + - def f4(x=(z := [1, ])): +13 + def f4(x=None): +14 + if x is None: +15 + x = (z := [1, ]) +16 | print(x) +17 | +18 | +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_9.py:17:11 + | +17 | def f5(x=([1, ])): + | ^^^^^ +18 | print(x) + | +help: Replace with `None`; initialize within function +14 | print(x) +15 | +16 | + - def f5(x=([1, ])): +17 + def f5(x=None): +18 + if x is None: +19 + x = ([1, ]) +20 | print(x) +21 | +22 | +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__B006_B006_B008.py.snap b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__preview__B006_B006_B008.py.snap new file mode 100644 index 0000000000..e3f42e2da7 --- /dev/null +++ b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__preview__B006_B006_B008.py.snap @@ -0,0 +1,462 @@ +--- +source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs +--- +B006 [*] Do not use mutable data structures for argument defaults + --> B006_B008.py:63:25 + | +63 | def this_is_wrong(value=[1, 2, 3]): + | ^^^^^^^^^ +64 | ... + | +help: Replace with `None`; initialize within function +60 | # Flag mutable literals/comprehensions +61 | +62 | + - def this_is_wrong(value=[1, 2, 3]): +63 + def this_is_wrong(value=None): +64 | ... +65 | +66 | +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_B008.py:67:30 + | +67 | def this_is_also_wrong(value={}): + | ^^ +68 | ... + | +help: Replace with `None`; initialize within function +64 | ... +65 | +66 | + - def this_is_also_wrong(value={}): +67 + def this_is_also_wrong(value=None): +68 | ... +69 | +70 | +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_B008.py:73:52 + | +71 | class Foo: +72 | @staticmethod +73 | def this_is_also_wrong_and_more_indented(value={}): + | ^^ +74 | pass + | +help: Replace with `None`; initialize within function +70 | +71 | class Foo: +72 | @staticmethod + - def this_is_also_wrong_and_more_indented(value={}): +73 + def this_is_also_wrong_and_more_indented(value=None): +74 | pass +75 | +76 | +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_B008.py:77:31 + | +77 | def multiline_arg_wrong(value={ + | _______________________________^ +78 | | +79 | | }): + | |_^ +80 | ... + | +help: Replace with `None`; initialize within function +74 | pass +75 | +76 | + - def multiline_arg_wrong(value={ + - + - }): +77 + def multiline_arg_wrong(value=None): +78 | ... +79 | +80 | def single_line_func_wrong(value = {}): ... +note: This is an unsafe fix and may change runtime behavior + +B006 Do not use mutable data structures for argument defaults + --> B006_B008.py:82:36 + | +80 | ... +81 | +82 | def single_line_func_wrong(value = {}): ... + | ^^ + | +help: Replace with `None`; initialize within function + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_B008.py:85:20 + | +85 | def and_this(value=set()): + | ^^^^^ +86 | ... + | +help: Replace with `None`; initialize within function +82 | def single_line_func_wrong(value = {}): ... +83 | +84 | + - def and_this(value=set()): +85 + def and_this(value=None): +86 | ... +87 | +88 | +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_B008.py:89:20 + | +89 | def this_too(value=collections.OrderedDict()): + | ^^^^^^^^^^^^^^^^^^^^^^^^^ +90 | ... + | +help: Replace with `None`; initialize within function +86 | ... +87 | +88 | + - def this_too(value=collections.OrderedDict()): +89 + def this_too(value=None): +90 | ... +91 | +92 | +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_B008.py:93:32 + | +93 | async def async_this_too(value=collections.defaultdict()): + | ^^^^^^^^^^^^^^^^^^^^^^^^^ +94 | ... + | +help: Replace with `None`; initialize within function +90 | ... +91 | +92 | + - async def async_this_too(value=collections.defaultdict()): +93 + async def async_this_too(value=None): +94 | ... +95 | +96 | +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_B008.py:97:26 + | +97 | def dont_forget_me(value=collections.deque()): + | ^^^^^^^^^^^^^^^^^^^ +98 | ... + | +help: Replace with `None`; initialize within function +94 | ... +95 | +96 | + - def dont_forget_me(value=collections.deque()): +97 + def dont_forget_me(value=None): +98 | ... +99 | +100 | +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_B008.py:102:46 + | +101 | # N.B. we're also flagging the function call in the comprehension +102 | def list_comprehension_also_not_okay(default=[i**2 for i in range(3)]): + | ^^^^^^^^^^^^^^^^^^^^^^^^ +103 | pass + | +help: Replace with `None`; initialize within function +99 | +100 | +101 | # N.B. we're also flagging the function call in the comprehension + - def list_comprehension_also_not_okay(default=[i**2 for i in range(3)]): +102 + def list_comprehension_also_not_okay(default=None): +103 | pass +104 | +105 | +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_B008.py:106:46 + | +106 | def dict_comprehension_also_not_okay(default={i: i**2 for i in range(3)}): + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +107 | pass + | +help: Replace with `None`; initialize within function +103 | pass +104 | +105 | + - def dict_comprehension_also_not_okay(default={i: i**2 for i in range(3)}): +106 + def dict_comprehension_also_not_okay(default=None): +107 | pass +108 | +109 | +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_B008.py:110:45 + | +110 | def set_comprehension_also_not_okay(default={i**2 for i in range(3)}): + | ^^^^^^^^^^^^^^^^^^^^^^^^ +111 | pass + | +help: Replace with `None`; initialize within function +107 | pass +108 | +109 | + - def set_comprehension_also_not_okay(default={i**2 for i in range(3)}): +110 + def set_comprehension_also_not_okay(default=None): +111 | pass +112 | +113 | +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_B008.py:114:33 + | +114 | def kwonlyargs_mutable(*, value=[]): + | ^^ +115 | ... + | +help: Replace with `None`; initialize within function +111 | pass +112 | +113 | + - def kwonlyargs_mutable(*, value=[]): +114 + def kwonlyargs_mutable(*, value=None): +115 | ... +116 | +117 | +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_B008.py:239:20 + | +237 | # B006 and B008 +238 | # We should handle arbitrary nesting of these B008. +239 | def nested_combo(a=[float(3), dt.datetime.now()]): + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +240 | pass + | +help: Replace with `None`; initialize within function +236 | +237 | # B006 and B008 +238 | # We should handle arbitrary nesting of these B008. + - def nested_combo(a=[float(3), dt.datetime.now()]): +239 + def nested_combo(a=None): +240 | pass +241 | +242 | +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_B008.py:276:27 + | +275 | def mutable_annotations( +276 | a: list[int] | None = [], + | ^^ +277 | b: Optional[Dict[int, int]] = {}, +278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(), + | +help: Replace with `None`; initialize within function +273 | +274 | +275 | def mutable_annotations( + - a: list[int] | None = [], +276 + a: list[int] | None = None, +277 | b: Optional[Dict[int, int]] = {}, +278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(), +279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(), +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_B008.py:277:35 + | +275 | def mutable_annotations( +276 | a: list[int] | None = [], +277 | b: Optional[Dict[int, int]] = {}, + | ^^ +278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(), +279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(), + | +help: Replace with `None`; initialize within function +274 | +275 | def mutable_annotations( +276 | a: list[int] | None = [], + - b: Optional[Dict[int, int]] = {}, +277 + b: Optional[Dict[int, int]] = None, +278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(), +279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(), +280 | ): +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_B008.py:278:62 + | +276 | a: list[int] | None = [], +277 | b: Optional[Dict[int, int]] = {}, +278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(), + | ^^^^^ +279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(), +280 | ): + | +help: Replace with `None`; initialize within function +275 | def mutable_annotations( +276 | a: list[int] | None = [], +277 | b: Optional[Dict[int, int]] = {}, + - c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(), +278 + c: Annotated[Union[Set[str], abc.Sized], "annotation"] = None, +279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(), +280 | ): +281 | pass +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_B008.py:279:80 + | +277 | b: Optional[Dict[int, int]] = {}, +278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(), +279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(), + | ^^^^^ +280 | ): +281 | pass + | +help: Replace with `None`; initialize within function +276 | a: list[int] | None = [], +277 | b: Optional[Dict[int, int]] = {}, +278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(), + - d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(), +279 + d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = None, +280 | ): +281 | pass +282 | +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_B008.py:284:52 + | +284 | def single_line_func_wrong(value: dict[str, str] = {}): + | ^^ +285 | """Docstring""" + | +help: Replace with `None`; initialize within function +281 | pass +282 | +283 | + - def single_line_func_wrong(value: dict[str, str] = {}): +284 + def single_line_func_wrong(value: dict[str, str] = None): +285 | """Docstring""" +286 | +287 | +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_B008.py:288:52 + | +288 | def single_line_func_wrong(value: dict[str, str] = {}): + | ^^ +289 | """Docstring""" +290 | ... + | +help: Replace with `None`; initialize within function +285 | """Docstring""" +286 | +287 | + - def single_line_func_wrong(value: dict[str, str] = {}): +288 + def single_line_func_wrong(value: dict[str, str] = None): +289 | """Docstring""" +290 | ... +291 | +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_B008.py:293:52 + | +293 | def single_line_func_wrong(value: dict[str, str] = {}): + | ^^ +294 | """Docstring"""; ... + | +help: Replace with `None`; initialize within function +290 | ... +291 | +292 | + - def single_line_func_wrong(value: dict[str, str] = {}): +293 + def single_line_func_wrong(value: dict[str, str] = None): +294 | """Docstring"""; ... +295 | +296 | +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_B008.py:297:52 + | +297 | def single_line_func_wrong(value: dict[str, str] = {}): + | ^^ +298 | """Docstring"""; \ +299 | ... + | +help: Replace with `None`; initialize within function +294 | """Docstring"""; ... +295 | +296 | + - def single_line_func_wrong(value: dict[str, str] = {}): +297 + def single_line_func_wrong(value: dict[str, str] = None): +298 | """Docstring"""; \ +299 | ... +300 | +note: This is an unsafe fix and may change runtime behavior + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_B008.py:302:52 + | +302 | def single_line_func_wrong(value: dict[str, str] = { + | ____________________________________________________^ +303 | | # This is a comment +304 | | }): + | |_^ +305 | """Docstring""" + | +help: Replace with `None`; initialize within function +299 | ... +300 | +301 | + - def single_line_func_wrong(value: dict[str, str] = { + - # This is a comment + - }): +302 + def single_line_func_wrong(value: dict[str, str] = None): +303 | """Docstring""" +304 | +305 | +note: This is an unsafe fix and may change runtime behavior + +B006 Do not use mutable data structures for argument defaults + --> B006_B008.py:308:52 + | +308 | def single_line_func_wrong(value: dict[str, str] = {}) \ + | ^^ +309 | : \ +310 | """Docstring""" + | +help: Replace with `None`; initialize within function + +B006 [*] Do not use mutable data structures for argument defaults + --> B006_B008.py:313:52 + | +313 | def single_line_func_wrong(value: dict[str, str] = {}): + | ^^ +314 | """Docstring without newline""" + | +help: Replace with `None`; initialize within function +310 | """Docstring""" +311 | +312 | + - def single_line_func_wrong(value: dict[str, str] = {}): +313 + def single_line_func_wrong(value: dict[str, str] = None): +314 | """Docstring without newline""" +note: This is an unsafe fix and may change runtime behavior