Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions doc/whatsnew/fragments/9668.false_positive
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Fix false positives for `possibly-used-before-assignment` when variables are exhaustively
assigned within a `match` block.

Closes #9668
12 changes: 12 additions & 0 deletions pylint/checkers/variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,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

Expand Down Expand Up @@ -768,6 +774,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)
Expand Down Expand Up @@ -1010,6 +1017,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
Expand Down
101 changes: 101 additions & 0 deletions tests/functional/u/used/used_before_assignment_py310.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,104 @@
print("x used to cause used-before-assignment!")
case _:
print("good thing it doesn't now!")


# pylint: disable = missing-function-docstring, redefined-outer-name, missing-class-docstring

# https://github.com/pylint-dev/pylint/issues/9668
from enum import Enum
from pylint.constants import PY311_PLUS
if PY311_PLUS:
from typing import assert_never # pylint: disable=no-name-in-module
else:
from typing_extensions import assert_never

class Example(Enum):
FOO = 1
BAR = 2

def check_value_if_then_match_return(example: Example, should_check: bool) -> str | None:
if should_check:
result = None
else:
match example:
case Example.FOO:
result = "foo"
case Example.BAR:
result = "bar"
case _:
return None

return result

def check_value_if_then_match_raise(example: Example, should_check: bool) -> str | None:
if should_check:
result = None
else:
match example:
case Example.FOO:
result = "foo"
case Example.BAR:
result = "bar"
case _:
raise ValueError("Not a valid enum")

return result

def check_value_if_then_match_assert_never(example: Example, should_check: bool) -> str | None:
if should_check:
result = None
else:
match example:
case Example.FOO:
result = "foo"
case Example.BAR:
result = "bar"
case _:
assert_never(example)

return result

def g(x):
if x is None:
y = 0
else:
match x:
case int():
y = x
case _:
raise TypeError(type(x))

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]
1 change: 1 addition & 0 deletions tests/functional/u/used/used_before_assignment_py310.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
possibly-used-before-assignment:108:11:108:17:check_value_if_then_match_non_exhaustive:Possibly using variable 'result' before assignment:CONTROL_FLOW