From 782044b2db21aafca563e4cc8cfcf737ebf6c420 Mon Sep 17 00:00:00 2001 From: harupy Date: Fri, 22 Nov 2024 22:48:49 +0900 Subject: [PATCH 1/5] Add test cases --- .../resources/test/fixtures/flake8_bugbear/B039.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B039.py b/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B039.py index 7d96075f15697e..d5ddbee8a1d075 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B039.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B039.py @@ -14,6 +14,8 @@ ContextVar("cv", default=MappingProxyType({})) ContextVar("cv", default=re.compile("foo")) ContextVar("cv", default=float(1)) +ContextVar("cv", default=frozenset[str]()) +ContextVar[frozenset[str]]("cv", default=frozenset[str]()) # Bad ContextVar("cv", default=[]) @@ -25,6 +27,8 @@ ContextVar("cv", default={char for char in "foo"}) ContextVar("cv", default={char: idx for idx, char in enumerate("foo")}) ContextVar("cv", default=collections.deque()) +ContextVar("cv", default=set[str]()) +ContextVar[set[str]]("cv", default=set[str]()) def bar() -> list[int]: return [1, 2, 3] From ff6355ff110a0a9f00c16d0dac834851f4a3d2a3 Mon Sep 17 00:00:00 2001 From: harupy Date: Fri, 22 Nov 2024 23:03:13 +0900 Subject: [PATCH 2/5] Apply map_subscript Signed-off-by: harupy --- .../flake8_bugbear/rules/mutable_contextvar_default.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_contextvar_default.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_contextvar_default.rs index b6ce0fc2b92ee4..add53a4329275b 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_contextvar_default.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_contextvar_default.rs @@ -1,5 +1,6 @@ use ruff_diagnostics::{Diagnostic, 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::{self as ast, Expr}; use ruff_python_semantic::analyze::typing::{is_immutable_func, is_mutable_expr, is_mutable_func}; @@ -92,11 +93,11 @@ pub(crate) fn mutable_contextvar_default(checker: &mut Checker, call: &ast::Expr || matches!( default, Expr::Call(ast::ExprCall { func, .. }) - if !is_mutable_func(func, checker.semantic()) - && !is_immutable_func(func, checker.semantic(), &extend_immutable_calls))) + if !is_mutable_func(map_subscript(func), checker.semantic()) + && !is_immutable_func(map_subscript(func), checker.semantic(), &extend_immutable_calls))) && checker .semantic() - .resolve_qualified_name(&call.func) + .resolve_qualified_name(map_subscript(&call.func)) .is_some_and(|qualified_name| { matches!(qualified_name.segments(), ["contextvars", "ContextVar"]) }) From ed61306c01e8710604c9d031252044ed5f370e51 Mon Sep 17 00:00:00 2001 From: harupy Date: Fri, 22 Nov 2024 23:13:17 +0900 Subject: [PATCH 3/5] Fix --- .../rules/flake8_bugbear/rules/mutable_contextvar_default.rs | 4 ++-- crates/ruff_python_semantic/src/analyze/typing.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_contextvar_default.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_contextvar_default.rs index add53a4329275b..dba296aa08d911 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_contextvar_default.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/mutable_contextvar_default.rs @@ -93,8 +93,8 @@ pub(crate) fn mutable_contextvar_default(checker: &mut Checker, call: &ast::Expr || matches!( default, Expr::Call(ast::ExprCall { func, .. }) - if !is_mutable_func(map_subscript(func), checker.semantic()) - && !is_immutable_func(map_subscript(func), checker.semantic(), &extend_immutable_calls))) + if !is_mutable_func(func, checker.semantic()) + && !is_immutable_func(func, checker.semantic(), &extend_immutable_calls))) && checker .semantic() .resolve_qualified_name(map_subscript(&call.func)) diff --git a/crates/ruff_python_semantic/src/analyze/typing.rs b/crates/ruff_python_semantic/src/analyze/typing.rs index d915bfa8b69015..37f74fa8f51a6e 100644 --- a/crates/ruff_python_semantic/src/analyze/typing.rs +++ b/crates/ruff_python_semantic/src/analyze/typing.rs @@ -279,7 +279,7 @@ pub fn is_immutable_func( extend_immutable_calls: &[QualifiedName], ) -> bool { semantic - .resolve_qualified_name(func) + .resolve_qualified_name(map_subscript(func)) .is_some_and(|qualified_name| { is_immutable_return_type(qualified_name.segments()) || extend_immutable_calls @@ -306,7 +306,7 @@ pub fn is_mutable_expr(expr: &Expr, semantic: &SemanticModel) -> bool { | Expr::ListComp(_) | Expr::DictComp(_) | 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, } } From 2bbcc3d9101672cb3cf48aa19ae21169ca17b5e7 Mon Sep 17 00:00:00 2001 From: harupy Date: Fri, 22 Nov 2024 23:15:43 +0900 Subject: [PATCH 4/5] Update snapshots --- ...__flake8_bugbear__tests__B039_B039.py.snap | 153 ++++++++++-------- 1 file changed, 87 insertions(+), 66 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B039_B039.py.snap b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B039_B039.py.snap index 808b68e4b992dd..bdd923b556a5d7 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B039_B039.py.snap +++ b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B039_B039.py.snap @@ -1,128 +1,149 @@ --- source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs -snapshot_kind: text --- -B039.py:19: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 | -18 | # Bad -19 | ContextVar("cv", default=[]) +20 | # Bad +21 | ContextVar("cv", default=[]) | ^^ B039 -20 | ContextVar("cv", default={}) -21 | ContextVar("cv", default=list()) +22 | ContextVar("cv", default={}) +23 | 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 +B039.py:22:26: B039 Do not use mutable data structures for `ContextVar` defaults | -18 | # Bad -19 | ContextVar("cv", default=[]) -20 | ContextVar("cv", default={}) +20 | # Bad +21 | ContextVar("cv", default=[]) +22 | ContextVar("cv", default={}) | ^^ B039 -21 | ContextVar("cv", default=list()) -22 | ContextVar("cv", default=set()) +23 | ContextVar("cv", default=list()) +24 | 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:23:26: B039 Do not use mutable data structures for `ContextVar` defaults | -19 | ContextVar("cv", default=[]) -20 | ContextVar("cv", default={}) -21 | ContextVar("cv", default=list()) +21 | ContextVar("cv", default=[]) +22 | ContextVar("cv", default={}) +23 | ContextVar("cv", default=list()) | ^^^^^^ B039 -22 | ContextVar("cv", default=set()) -23 | ContextVar("cv", default=dict()) +24 | ContextVar("cv", default=set()) +25 | ContextVar("cv", default=dict()) | = help: Replace with `None`; initialize with `.set()`` -B039.py:22: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 | -20 | ContextVar("cv", default={}) -21 | ContextVar("cv", default=list()) -22 | ContextVar("cv", default=set()) +22 | ContextVar("cv", default={}) +23 | ContextVar("cv", default=list()) +24 | ContextVar("cv", default=set()) | ^^^^^ B039 -23 | ContextVar("cv", default=dict()) -24 | ContextVar("cv", default=[char for char in "foo"]) +25 | ContextVar("cv", default=dict()) +26 | ContextVar("cv", default=[char for char in "foo"]) | = help: Replace with `None`; initialize with `.set()`` -B039.py:23: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 | -21 | ContextVar("cv", default=list()) -22 | ContextVar("cv", default=set()) -23 | ContextVar("cv", default=dict()) +23 | ContextVar("cv", default=list()) +24 | ContextVar("cv", default=set()) +25 | ContextVar("cv", default=dict()) | ^^^^^^ B039 -24 | ContextVar("cv", default=[char for char in "foo"]) -25 | ContextVar("cv", default={char for char in "foo"}) +26 | ContextVar("cv", default=[char for char in "foo"]) +27 | ContextVar("cv", default={char for char in "foo"}) | = help: Replace with `None`; initialize with `.set()`` -B039.py:24: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 | -22 | ContextVar("cv", default=set()) -23 | ContextVar("cv", default=dict()) -24 | ContextVar("cv", default=[char for char in "foo"]) +24 | ContextVar("cv", default=set()) +25 | ContextVar("cv", default=dict()) +26 | ContextVar("cv", default=[char for char in "foo"]) | ^^^^^^^^^^^^^^^^^^^^^^^^ B039 -25 | ContextVar("cv", default={char for char in "foo"}) -26 | ContextVar("cv", default={char: idx for idx, char in enumerate("foo")}) +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()`` -B039.py:25: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 | -23 | ContextVar("cv", default=dict()) -24 | ContextVar("cv", default=[char for char in "foo"]) -25 | ContextVar("cv", default={char for char in "foo"}) +25 | ContextVar("cv", default=dict()) +26 | ContextVar("cv", default=[char for char in "foo"]) +27 | ContextVar("cv", default={char for char in "foo"}) | ^^^^^^^^^^^^^^^^^^^^^^^^ B039 -26 | ContextVar("cv", default={char: idx for idx, char in enumerate("foo")}) -27 | ContextVar("cv", default=collections.deque()) +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:26:26: B039 Do not use mutable data structures for `ContextVar` defaults +B039.py:28:26: B039 Do not use mutable data structures for `ContextVar` defaults | -24 | ContextVar("cv", default=[char for char in "foo"]) -25 | ContextVar("cv", default={char for char in "foo"}) -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={char for char in "foo"}) +28 | ContextVar("cv", default={char: idx for idx, char in enumerate("foo")}) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B039 -27 | ContextVar("cv", default=collections.deque()) +29 | ContextVar("cv", default=collections.deque()) +30 | ContextVar("cv", default=set[str]()) | = help: Replace with `None`; initialize with `.set()`` -B039.py:27:26: B039 Do not use mutable data structures for `ContextVar` defaults +B039.py:29:26: B039 Do not use mutable data structures for `ContextVar` defaults | -25 | ContextVar("cv", default={char for char in "foo"}) -26 | ContextVar("cv", default={char: idx for idx, char in enumerate("foo")}) -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")}) +29 | ContextVar("cv", default=collections.deque()) | ^^^^^^^^^^^^^^^^^^^ B039 -28 | -29 | def bar() -> list[int]: +30 | ContextVar("cv", default=set[str]()) +31 | ContextVar[set[str]]("cv", default=set[str]()) + | + = help: Replace with `None`; initialize with `.set()`` + +B039.py:30:26: B039 Do not use mutable data structures for `ContextVar` defaults + | +28 | ContextVar("cv", default={char: idx for idx, char in enumerate("foo")}) +29 | ContextVar("cv", default=collections.deque()) +30 | ContextVar("cv", default=set[str]()) + | ^^^^^^^^^^ B039 +31 | ContextVar[set[str]]("cv", default=set[str]()) | = help: Replace with `None`; initialize with `.set()`` -B039.py:32: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 | -30 | return [1, 2, 3] -31 | -32 | ContextVar("cv", default=bar()) +29 | ContextVar("cv", default=collections.deque()) +30 | ContextVar("cv", default=set[str]()) +31 | ContextVar[set[str]]("cv", default=set[str]()) + | ^^^^^^^^^^ B039 +32 | +33 | def bar() -> list[int]: + | + = help: Replace with `None`; initialize with `.set()`` + +B039.py:36:26: B039 Do not use mutable data structures for `ContextVar` defaults + | +34 | return [1, 2, 3] +35 | +36 | ContextVar("cv", default=bar()) | ^^^^^ B039 -33 | ContextVar("cv", default=time.time()) +37 | ContextVar("cv", default=time.time()) | = help: Replace with `None`; initialize with `.set()`` -B039.py:33:26: B039 Do not use mutable data structures for `ContextVar` defaults +B039.py:37:26: B039 Do not use mutable data structures for `ContextVar` defaults | -32 | ContextVar("cv", default=bar()) -33 | ContextVar("cv", default=time.time()) +36 | ContextVar("cv", default=bar()) +37 | ContextVar("cv", default=time.time()) | ^^^^^^^^^^^ B039 -34 | -35 | def baz(): ... +38 | +39 | def baz(): ... | = help: Replace with `None`; initialize with `.set()`` -B039.py:36:26: B039 Do not use mutable data structures for `ContextVar` defaults +B039.py:40:26: B039 Do not use mutable data structures for `ContextVar` defaults | -35 | def baz(): ... -36 | ContextVar("cv", default=baz()) +39 | def baz(): ... +40 | ContextVar("cv", default=baz()) | ^^^^^ B039 | = help: Replace with `None`; initialize with `.set()`` From f890196a6fc2c96a63be5c4303fbb9f91c9461ef Mon Sep 17 00:00:00 2001 From: harupy Date: Sat, 23 Nov 2024 00:51:58 +0900 Subject: [PATCH 5/5] RRevert snapshot_kind --- .../ruff_linter__rules__flake8_bugbear__tests__B039_B039.py.snap | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B039_B039.py.snap b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B039_B039.py.snap index bdd923b556a5d7..429f011542c6e8 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B039_B039.py.snap +++ b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B039_B039.py.snap @@ -1,5 +1,6 @@ --- source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs +snapshot_kind: text --- B039.py:21:26: B039 Do not use mutable data structures for `ContextVar` defaults |