mirror of https://github.com/astral-sh/ruff
fix-21142
This commit is contained in:
parent
17c7b3cde1
commit
174c619a98
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Module-level mutable "constant"
|
||||||
|
MY_SET = {"ABC", "DEF"} # plain set
|
||||||
|
MY_LIST = [1, 2, 3] # plain list
|
||||||
|
MY_DICT = {"key": "value"} # plain dict
|
||||||
|
|
||||||
|
# NOT triggering B006 (should trigger)
|
||||||
|
def func_A(s: set[str] = MY_SET):
|
||||||
|
return s
|
||||||
|
|
||||||
|
# Triggering B006 (correct)
|
||||||
|
def func_B(s: set[str] = {"ABC", "DEF"}):
|
||||||
|
return s
|
||||||
|
|
||||||
|
# Should trigger B006
|
||||||
|
def func_C(items: list[int] = MY_LIST):
|
||||||
|
return items
|
||||||
|
|
||||||
|
# Should trigger B006
|
||||||
|
def func_D(data: dict[str, str] = MY_DICT):
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
@ -48,6 +48,7 @@ mod tests {
|
||||||
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_7.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_8.py"))]
|
||||||
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_9.py"))]
|
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_9.py"))]
|
||||||
|
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_10.py"))]
|
||||||
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_B008.py"))]
|
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_B008.py"))]
|
||||||
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_1.pyi"))]
|
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_1.pyi"))]
|
||||||
#[test_case(Rule::NoExplicitStacklevel, Path::new("B028.py"))]
|
#[test_case(Rule::NoExplicitStacklevel, Path::new("B028.py"))]
|
||||||
|
|
@ -93,6 +94,7 @@ mod tests {
|
||||||
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_7.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_8.py"))]
|
||||||
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_9.py"))]
|
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_9.py"))]
|
||||||
|
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_10.py"))]
|
||||||
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_B008.py"))]
|
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_B008.py"))]
|
||||||
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_1.pyi"))]
|
#[test_case(Rule::MutableArgumentDefault, Path::new("B006_1.pyi"))]
|
||||||
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
|
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,9 @@ use ruff_python_ast::parenthesize::parenthesized_range;
|
||||||
use ruff_python_ast::{self as ast, Expr, ParameterWithDefault};
|
use ruff_python_ast::{self as ast, Expr, ParameterWithDefault};
|
||||||
use ruff_python_semantic::SemanticModel;
|
use ruff_python_semantic::SemanticModel;
|
||||||
use ruff_python_semantic::analyze::function_type::is_stub;
|
use ruff_python_semantic::analyze::function_type::is_stub;
|
||||||
use ruff_python_semantic::analyze::typing::{is_immutable_annotation, is_mutable_expr};
|
use ruff_python_semantic::analyze::typing::{
|
||||||
|
find_binding_value, is_immutable_annotation, is_mutable_expr,
|
||||||
|
};
|
||||||
use ruff_python_trivia::{indentation_at_offset, textwrap};
|
use ruff_python_trivia::{indentation_at_offset, textwrap};
|
||||||
use ruff_source_file::LineRanges;
|
use ruff_source_file::LineRanges;
|
||||||
use ruff_text_size::Ranged;
|
use ruff_text_size::Ranged;
|
||||||
|
|
@ -142,6 +144,23 @@ fn is_guaranteed_mutable_expr(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||||
elts.iter().any(|e| is_guaranteed_mutable_expr(e, semantic))
|
elts.iter().any(|e| is_guaranteed_mutable_expr(e, semantic))
|
||||||
}
|
}
|
||||||
Expr::Named(ast::ExprNamed { value, .. }) => is_guaranteed_mutable_expr(value, semantic),
|
Expr::Named(ast::ExprNamed { value, .. }) => is_guaranteed_mutable_expr(value, semantic),
|
||||||
|
Expr::Name(name) => {
|
||||||
|
// Resolve module-level constants that are bound to mutable objects
|
||||||
|
let Some(binding_id) = semantic.only_binding(name) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let binding = semantic.binding(binding_id);
|
||||||
|
// Only check assignments (not imports, function parameters, etc.)
|
||||||
|
if !binding.kind.is_assignment() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Get the assigned value and check if it's mutable
|
||||||
|
if let Some(value) = find_binding_value(binding, semantic) {
|
||||||
|
is_guaranteed_mutable_expr(value, semantic)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => is_mutable_expr(expr, semantic),
|
_ => is_mutable_expr(expr, semantic),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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_10.py:11:26
|
||||||
|
|
|
||||||
|
10 | # Triggering B006 (correct)
|
||||||
|
11 | def func_B(s: set[str] = {"ABC", "DEF"}):
|
||||||
|
| ^^^^^^^^^^^^^^
|
||||||
|
12 | return s
|
||||||
|
|
|
||||||
|
help: Replace with `None`; initialize within function
|
||||||
|
8 | return s
|
||||||
|
9 |
|
||||||
|
10 | # Triggering B006 (correct)
|
||||||
|
- def func_B(s: set[str] = {"ABC", "DEF"}):
|
||||||
|
11 + def func_B(s: set[str] = None):
|
||||||
|
12 + if s is None:
|
||||||
|
13 + s = {"ABC", "DEF"}
|
||||||
|
14 | return s
|
||||||
|
15 |
|
||||||
|
16 | # Should trigger B006
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
|
||||||
|
---
|
||||||
|
B006 [*] Do not use mutable data structures for argument defaults
|
||||||
|
--> B006_10.py:7:26
|
||||||
|
|
|
||||||
|
6 | # NOT triggering B006 (should trigger)
|
||||||
|
7 | def func_A(s: set[str] = MY_SET):
|
||||||
|
| ^^^^^^
|
||||||
|
8 | return s
|
||||||
|
|
|
||||||
|
help: Replace with `None`; initialize within function
|
||||||
|
4 | MY_DICT = {"key": "value"} # plain dict
|
||||||
|
5 |
|
||||||
|
6 | # NOT triggering B006 (should trigger)
|
||||||
|
- def func_A(s: set[str] = MY_SET):
|
||||||
|
7 + def func_A(s: set[str] = None):
|
||||||
|
8 + if s is None:
|
||||||
|
9 + s = MY_SET
|
||||||
|
10 | return s
|
||||||
|
11 |
|
||||||
|
12 | # Triggering B006 (correct)
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
|
B006 [*] Do not use mutable data structures for argument defaults
|
||||||
|
--> B006_10.py:11:26
|
||||||
|
|
|
||||||
|
10 | # Triggering B006 (correct)
|
||||||
|
11 | def func_B(s: set[str] = {"ABC", "DEF"}):
|
||||||
|
| ^^^^^^^^^^^^^^
|
||||||
|
12 | return s
|
||||||
|
|
|
||||||
|
help: Replace with `None`; initialize within function
|
||||||
|
8 | return s
|
||||||
|
9 |
|
||||||
|
10 | # Triggering B006 (correct)
|
||||||
|
- def func_B(s: set[str] = {"ABC", "DEF"}):
|
||||||
|
11 + def func_B(s: set[str] = None):
|
||||||
|
12 + if s is None:
|
||||||
|
13 + s = {"ABC", "DEF"}
|
||||||
|
14 | return s
|
||||||
|
15 |
|
||||||
|
16 | # Should trigger B006
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
|
B006 [*] Do not use mutable data structures for argument defaults
|
||||||
|
--> B006_10.py:15:31
|
||||||
|
|
|
||||||
|
14 | # Should trigger B006
|
||||||
|
15 | def func_C(items: list[int] = MY_LIST):
|
||||||
|
| ^^^^^^^
|
||||||
|
16 | return items
|
||||||
|
|
|
||||||
|
help: Replace with `None`; initialize within function
|
||||||
|
12 | return s
|
||||||
|
13 |
|
||||||
|
14 | # Should trigger B006
|
||||||
|
- def func_C(items: list[int] = MY_LIST):
|
||||||
|
15 + def func_C(items: list[int] = None):
|
||||||
|
16 + if items is None:
|
||||||
|
17 + items = MY_LIST
|
||||||
|
18 | return items
|
||||||
|
19 |
|
||||||
|
20 | # Should trigger B006
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
|
|
||||||
|
B006 [*] Do not use mutable data structures for argument defaults
|
||||||
|
--> B006_10.py:19:35
|
||||||
|
|
|
||||||
|
18 | # Should trigger B006
|
||||||
|
19 | def func_D(data: dict[str, str] = MY_DICT):
|
||||||
|
| ^^^^^^^
|
||||||
|
20 | return data
|
||||||
|
|
|
||||||
|
help: Replace with `None`; initialize within function
|
||||||
|
16 | return items
|
||||||
|
17 |
|
||||||
|
18 | # Should trigger B006
|
||||||
|
- def func_D(data: dict[str, str] = MY_DICT):
|
||||||
|
19 + def func_D(data: dict[str, str] = None):
|
||||||
|
20 + if data is None:
|
||||||
|
21 + data = MY_DICT
|
||||||
|
22 | return data
|
||||||
|
23 |
|
||||||
|
note: This is an unsafe fix and may change runtime behavior
|
||||||
Loading…
Reference in New Issue