diff --git a/doc/whatsnew/fragments/9668.false_positive b/doc/whatsnew/fragments/9668.false_positive new file mode 100644 index 0000000000..9668bd217e --- /dev/null +++ b/doc/whatsnew/fragments/9668.false_positive @@ -0,0 +1,4 @@ +Fix false positives for `possibly-used-before-assignment` when variables are exhaustively +assigned within a `match` block. + +Closes #9668 diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 7a55bc68b2..4baf6aabf8 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -674,6 +674,12 @@ def _inferred_to_define_name_raise_or_return( if isinstance(node, (nodes.With, nodes.For, nodes.While)): return NamesConsumer._defines_name_raises_or_returns_recursive(name, node) + if isinstance(node, nodes.Match): + return all( + NamesConsumer._defines_name_raises_or_returns_recursive(name, case) + for case in node.cases + ) + if not isinstance(node, nodes.If): return False @@ -723,6 +729,7 @@ def _branch_handles_name(self, name: str, body: Iterable[nodes.NodeNG]) -> bool: nodes.With, nodes.For, nodes.While, + nodes.Match, ), ) and self._inferred_to_define_name_raise_or_return(name, if_body_stmt) @@ -975,6 +982,11 @@ def _defines_name_raises_or_returns_recursive( and NamesConsumer._defines_name_raises_or_returns_recursive(name, stmt) ): return True + if isinstance(stmt, nodes.Match): + return all( + NamesConsumer._defines_name_raises_or_returns_recursive(name, case) + for case in stmt.cases + ) return False @staticmethod diff --git a/tests/functional/u/used/used_before_assignment_py310.py b/tests/functional/u/used/used_before_assignment_py310.py index 3734cc2d55..0f6db9c1c4 100644 --- a/tests/functional/u/used/used_before_assignment_py310.py +++ b/tests/functional/u/used/used_before_assignment_py310.py @@ -33,7 +33,7 @@ def check_value_if_then_match_return(example: Example, should_check: bool) -> st case _: return None - return result # [possibly-used-before-assignment] FALSE POSITIVE + return result def check_value_if_then_match_raise(example: Example, should_check: bool) -> str | None: if should_check: @@ -47,7 +47,7 @@ def check_value_if_then_match_raise(example: Example, should_check: bool) -> str case _: raise ValueError("Not a valid enum") - return result # [possibly-used-before-assignment] FALSE POSITIVE + return result def check_value_if_then_match_assert_never(example: Example, should_check: bool) -> str | None: if should_check: @@ -61,7 +61,7 @@ def check_value_if_then_match_assert_never(example: Example, should_check: bool) case _: assert_never(example) - return result # [possibly-used-before-assignment] FALSE POSITIVE + return result def g(x): if x is None: @@ -73,4 +73,36 @@ def g(x): case _: raise TypeError(type(x)) - return y # [possibly-used-before-assignment] FALSE POSITIVE + return y + +def check_value_if_then_match_nested( + example: Example, example_inner: Example, should_check: bool +) -> str | None: + if should_check: + result = None + else: + match example: + case Example.FOO: + match example_inner: + case Example.BAR: + result = "bar" + case _: + return None + case _: + return None + + return result + +def check_value_if_then_match_non_exhaustive(example: Example, should_check: bool) -> str | None: + if should_check: + result = None + else: + match example: + case Example.FOO: + result = "foo" + case Example.BAR: + pass + case _: + return None + + return result # [possibly-used-before-assignment] diff --git a/tests/functional/u/used/used_before_assignment_py310.txt b/tests/functional/u/used/used_before_assignment_py310.txt index 75ae6e6774..5f2d3074a7 100644 --- a/tests/functional/u/used/used_before_assignment_py310.txt +++ b/tests/functional/u/used/used_before_assignment_py310.txt @@ -1,4 +1 @@ -possibly-used-before-assignment:36:11:36:17:check_value_if_then_match_return:Possibly using variable 'result' before assignment:CONTROL_FLOW -possibly-used-before-assignment:50:11:50:17:check_value_if_then_match_raise:Possibly using variable 'result' before assignment:CONTROL_FLOW -possibly-used-before-assignment:64:11:64:17:check_value_if_then_match_assert_never:Possibly using variable 'result' before assignment:CONTROL_FLOW -possibly-used-before-assignment:76:11:76:12:g:Possibly using variable 'y' before assignment:CONTROL_FLOW +possibly-used-before-assignment:108:11:108:17:check_value_if_then_match_non_exhaustive:Possibly using variable 'result' before assignment:CONTROL_FLOW