[flake8-bugbear] Fix mutable-contextvar-default (B039) to resolve annotated function calls properly (#14532)

## Summary

<!-- What's the purpose of the change? What does it do, and why? -->

Fix #14525

## Test Plan

<!-- How was it tested? -->

New test cases

---------

Signed-off-by: harupy <hkawamura0130@gmail.com>
This commit is contained in:
Harutaka Kawamura 2024-11-24 11:29:25 +09:00 committed by GitHub
parent 1f303a5eb6
commit e3d792605f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 104 additions and 77 deletions

View file

@ -14,6 +14,8 @@ ContextVar("cv", default=frozenset())
ContextVar("cv", default=MappingProxyType({})) ContextVar("cv", default=MappingProxyType({}))
ContextVar("cv", default=re.compile("foo")) ContextVar("cv", default=re.compile("foo"))
ContextVar("cv", default=float(1)) ContextVar("cv", default=float(1))
ContextVar("cv", default=frozenset[str]())
ContextVar[frozenset[str]]("cv", default=frozenset[str]())
# Bad # Bad
ContextVar("cv", default=[]) ContextVar("cv", default=[])
@ -25,6 +27,8 @@ ContextVar("cv", default=[char for char in "foo"])
ContextVar("cv", default={char for char in "foo"}) ContextVar("cv", default={char for char in "foo"})
ContextVar("cv", default={char: idx for idx, char in enumerate("foo")}) ContextVar("cv", default={char: idx for idx, char in enumerate("foo")})
ContextVar("cv", default=collections.deque()) ContextVar("cv", default=collections.deque())
ContextVar("cv", default=set[str]())
ContextVar[set[str]]("cv", default=set[str]())
def bar() -> list[int]: def bar() -> list[int]:
return [1, 2, 3] return [1, 2, 3]

View file

@ -1,5 +1,6 @@
use ruff_diagnostics::{Diagnostic, Violation}; use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::map_subscript;
use ruff_python_ast::name::QualifiedName; use ruff_python_ast::name::QualifiedName;
use ruff_python_ast::{self as ast, Expr}; use ruff_python_ast::{self as ast, Expr};
use ruff_python_semantic::analyze::typing::{is_immutable_func, is_mutable_expr, is_mutable_func}; use ruff_python_semantic::analyze::typing::{is_immutable_func, is_mutable_expr, is_mutable_func};
@ -96,7 +97,7 @@ pub(crate) fn mutable_contextvar_default(checker: &mut Checker, call: &ast::Expr
&& !is_immutable_func(func, checker.semantic(), &extend_immutable_calls))) && !is_immutable_func(func, checker.semantic(), &extend_immutable_calls)))
&& checker && checker
.semantic() .semantic()
.resolve_qualified_name(&call.func) .resolve_qualified_name(map_subscript(&call.func))
.is_some_and(|qualified_name| { .is_some_and(|qualified_name| {
matches!(qualified_name.segments(), ["contextvars", "ContextVar"]) matches!(qualified_name.segments(), ["contextvars", "ContextVar"])
}) })

View file

@ -2,127 +2,149 @@
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
snapshot_kind: text snapshot_kind: text
--- ---
B039.py:19:26: B039 Do not use mutable data structures for `ContextVar` defaults
|
18 | # Bad
19 | ContextVar("cv", default=[])
| ^^ B039
20 | ContextVar("cv", default={})
21 | ContextVar("cv", default=list())
|
= help: Replace with `None`; initialize with `.set()``
B039.py:20:26: B039 Do not use mutable data structures for `ContextVar` defaults
|
18 | # Bad
19 | ContextVar("cv", default=[])
20 | ContextVar("cv", default={})
| ^^ B039
21 | ContextVar("cv", default=list())
22 | ContextVar("cv", default=set())
|
= help: Replace with `None`; initialize with `.set()``
B039.py:21:26: B039 Do not use mutable data structures for `ContextVar` defaults B039.py:21:26: B039 Do not use mutable data structures for `ContextVar` defaults
| |
19 | ContextVar("cv", default=[]) 20 | # Bad
20 | ContextVar("cv", default={}) 21 | ContextVar("cv", default=[])
21 | ContextVar("cv", default=list()) | ^^ B039
| ^^^^^^ B039 22 | ContextVar("cv", default={})
22 | ContextVar("cv", default=set()) 23 | ContextVar("cv", default=list())
23 | ContextVar("cv", default=dict())
| |
= help: Replace with `None`; initialize with `.set()`` = help: Replace with `None`; initialize with `.set()``
B039.py:22:26: B039 Do not use mutable data structures for `ContextVar` defaults B039.py:22:26: B039 Do not use mutable data structures for `ContextVar` defaults
| |
20 | ContextVar("cv", default={}) 20 | # Bad
21 | ContextVar("cv", default=list()) 21 | ContextVar("cv", default=[])
22 | ContextVar("cv", default=set()) 22 | ContextVar("cv", default={})
| ^^^^^ B039 | ^^ B039
23 | ContextVar("cv", default=dict()) 23 | ContextVar("cv", default=list())
24 | ContextVar("cv", default=[char for char in "foo"]) 24 | ContextVar("cv", default=set())
| |
= help: Replace with `None`; initialize with `.set()`` = help: Replace with `None`; initialize with `.set()``
B039.py:23:26: B039 Do not use mutable data structures for `ContextVar` defaults B039.py:23:26: B039 Do not use mutable data structures for `ContextVar` defaults
| |
21 | ContextVar("cv", default=list()) 21 | ContextVar("cv", default=[])
22 | ContextVar("cv", default=set()) 22 | ContextVar("cv", default={})
23 | ContextVar("cv", default=dict()) 23 | ContextVar("cv", default=list())
| ^^^^^^ B039 | ^^^^^^ B039
24 | ContextVar("cv", default=[char for char in "foo"]) 24 | ContextVar("cv", default=set())
25 | ContextVar("cv", default={char for char in "foo"}) 25 | ContextVar("cv", default=dict())
| |
= help: Replace with `None`; initialize with `.set()`` = help: Replace with `None`; initialize with `.set()``
B039.py:24:26: B039 Do not use mutable data structures for `ContextVar` defaults B039.py:24:26: B039 Do not use mutable data structures for `ContextVar` defaults
| |
22 | ContextVar("cv", default=set()) 22 | ContextVar("cv", default={})
23 | ContextVar("cv", default=dict()) 23 | ContextVar("cv", default=list())
24 | ContextVar("cv", default=[char for char in "foo"]) 24 | ContextVar("cv", default=set())
| ^^^^^^^^^^^^^^^^^^^^^^^^ B039 | ^^^^^ B039
25 | ContextVar("cv", default={char for char in "foo"}) 25 | ContextVar("cv", default=dict())
26 | ContextVar("cv", default={char: idx for idx, char in enumerate("foo")}) 26 | ContextVar("cv", default=[char for char in "foo"])
| |
= help: Replace with `None`; initialize with `.set()`` = help: Replace with `None`; initialize with `.set()``
B039.py:25:26: B039 Do not use mutable data structures for `ContextVar` defaults B039.py:25:26: B039 Do not use mutable data structures for `ContextVar` defaults
| |
23 | ContextVar("cv", default=dict()) 23 | ContextVar("cv", default=list())
24 | ContextVar("cv", default=[char for char in "foo"]) 24 | ContextVar("cv", default=set())
25 | ContextVar("cv", default={char for char in "foo"}) 25 | ContextVar("cv", default=dict())
| ^^^^^^^^^^^^^^^^^^^^^^^^ B039 | ^^^^^^ B039
26 | ContextVar("cv", default={char: idx for idx, char in enumerate("foo")}) 26 | ContextVar("cv", default=[char for char in "foo"])
27 | ContextVar("cv", default=collections.deque()) 27 | ContextVar("cv", default={char for char in "foo"})
| |
= help: Replace with `None`; initialize with `.set()`` = help: Replace with `None`; initialize with `.set()``
B039.py:26:26: B039 Do not use mutable data structures for `ContextVar` defaults B039.py:26:26: B039 Do not use mutable data structures for `ContextVar` defaults
| |
24 | ContextVar("cv", default=[char for char in "foo"]) 24 | ContextVar("cv", default=set())
25 | ContextVar("cv", default={char for char in "foo"}) 25 | ContextVar("cv", default=dict())
26 | ContextVar("cv", default={char: idx for idx, char in enumerate("foo")}) 26 | ContextVar("cv", default=[char for char in "foo"])
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B039 | ^^^^^^^^^^^^^^^^^^^^^^^^ B039
27 | ContextVar("cv", default=collections.deque()) 27 | ContextVar("cv", default={char for char in "foo"})
28 | ContextVar("cv", default={char: idx for idx, char in enumerate("foo")})
| |
= help: Replace with `None`; initialize with `.set()`` = help: Replace with `None`; initialize with `.set()``
B039.py:27:26: B039 Do not use mutable data structures for `ContextVar` defaults B039.py:27:26: B039 Do not use mutable data structures for `ContextVar` defaults
| |
25 | ContextVar("cv", default={char for char in "foo"}) 25 | ContextVar("cv", default=dict())
26 | ContextVar("cv", default={char: idx for idx, char in enumerate("foo")}) 26 | ContextVar("cv", default=[char for char in "foo"])
27 | ContextVar("cv", default=collections.deque()) 27 | ContextVar("cv", default={char for char in "foo"})
| ^^^^^^^^^^^^^^^^^^^^^^^^ B039
28 | ContextVar("cv", default={char: idx for idx, char in enumerate("foo")})
29 | ContextVar("cv", default=collections.deque())
|
= help: Replace with `None`; initialize with `.set()``
B039.py:28:26: B039 Do not use mutable data structures for `ContextVar` defaults
|
26 | ContextVar("cv", default=[char for char in "foo"])
27 | ContextVar("cv", default={char for char in "foo"})
28 | ContextVar("cv", default={char: idx for idx, char in enumerate("foo")})
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B039
29 | ContextVar("cv", default=collections.deque())
30 | ContextVar("cv", default=set[str]())
|
= help: Replace with `None`; initialize with `.set()``
B039.py:29:26: B039 Do not use mutable data structures for `ContextVar` defaults
|
27 | ContextVar("cv", default={char for char in "foo"})
28 | ContextVar("cv", default={char: idx for idx, char in enumerate("foo")})
29 | ContextVar("cv", default=collections.deque())
| ^^^^^^^^^^^^^^^^^^^ B039 | ^^^^^^^^^^^^^^^^^^^ B039
28 | 30 | ContextVar("cv", default=set[str]())
29 | def bar() -> list[int]: 31 | ContextVar[set[str]]("cv", default=set[str]())
| |
= help: Replace with `None`; initialize with `.set()`` = help: Replace with `None`; initialize with `.set()``
B039.py:32:26: B039 Do not use mutable data structures for `ContextVar` defaults B039.py:30:26: B039 Do not use mutable data structures for `ContextVar` defaults
| |
30 | return [1, 2, 3] 28 | ContextVar("cv", default={char: idx for idx, char in enumerate("foo")})
31 | 29 | ContextVar("cv", default=collections.deque())
32 | ContextVar("cv", default=bar()) 30 | ContextVar("cv", default=set[str]())
| ^^^^^ B039 | ^^^^^^^^^^ B039
33 | ContextVar("cv", default=time.time()) 31 | ContextVar[set[str]]("cv", default=set[str]())
| |
= help: Replace with `None`; initialize with `.set()`` = help: Replace with `None`; initialize with `.set()``
B039.py:33:26: B039 Do not use mutable data structures for `ContextVar` defaults B039.py:31:36: B039 Do not use mutable data structures for `ContextVar` defaults
| |
32 | ContextVar("cv", default=bar()) 29 | ContextVar("cv", default=collections.deque())
33 | ContextVar("cv", default=time.time()) 30 | ContextVar("cv", default=set[str]())
| ^^^^^^^^^^^ B039 31 | ContextVar[set[str]]("cv", default=set[str]())
34 | | ^^^^^^^^^^ B039
35 | def baz(): ... 32 |
33 | def bar() -> list[int]:
| |
= help: Replace with `None`; initialize with `.set()`` = help: Replace with `None`; initialize with `.set()``
B039.py:36:26: B039 Do not use mutable data structures for `ContextVar` defaults B039.py:36:26: B039 Do not use mutable data structures for `ContextVar` defaults
| |
35 | def baz(): ... 34 | return [1, 2, 3]
36 | ContextVar("cv", default=baz()) 35 |
36 | ContextVar("cv", default=bar())
| ^^^^^ B039
37 | ContextVar("cv", default=time.time())
|
= help: Replace with `None`; initialize with `.set()``
B039.py:37:26: B039 Do not use mutable data structures for `ContextVar` defaults
|
36 | ContextVar("cv", default=bar())
37 | ContextVar("cv", default=time.time())
| ^^^^^^^^^^^ B039
38 |
39 | def baz(): ...
|
= help: Replace with `None`; initialize with `.set()``
B039.py:40:26: B039 Do not use mutable data structures for `ContextVar` defaults
|
39 | def baz(): ...
40 | ContextVar("cv", default=baz())
| ^^^^^ B039 | ^^^^^ B039
| |
= help: Replace with `None`; initialize with `.set()`` = help: Replace with `None`; initialize with `.set()``

View file

@ -279,7 +279,7 @@ pub fn is_immutable_func(
extend_immutable_calls: &[QualifiedName], extend_immutable_calls: &[QualifiedName],
) -> bool { ) -> bool {
semantic semantic
.resolve_qualified_name(func) .resolve_qualified_name(map_subscript(func))
.is_some_and(|qualified_name| { .is_some_and(|qualified_name| {
is_immutable_return_type(qualified_name.segments()) is_immutable_return_type(qualified_name.segments())
|| extend_immutable_calls || extend_immutable_calls
@ -306,7 +306,7 @@ pub fn is_mutable_expr(expr: &Expr, semantic: &SemanticModel) -> bool {
| Expr::ListComp(_) | Expr::ListComp(_)
| Expr::DictComp(_) | Expr::DictComp(_)
| Expr::SetComp(_) => true, | Expr::SetComp(_) => true,
Expr::Call(ast::ExprCall { func, .. }) => is_mutable_func(func, semantic), Expr::Call(ast::ExprCall { func, .. }) => is_mutable_func(map_subscript(func), semantic),
_ => false, _ => false,
} }
} }