Skip to content

Commit 7038ef4

Browse files
[Backport maintenance/3.3.x] Resolve possibly-used-before-assignment false positives from match block assignments (#10393) (#10395)
2 parents 5addaaa + 045f179 commit 7038ef4

File tree

4 files changed

+118
-0
lines changed

4 files changed

+118
-0
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix false positives for `possibly-used-before-assignment` when variables are exhaustively
2+
assigned within a `match` block.
3+
4+
Closes #9668

pylint/checkers/variables.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,12 @@ def _inferred_to_define_name_raise_or_return(
720720
if isinstance(node, (nodes.With, nodes.For, nodes.While)):
721721
return NamesConsumer._defines_name_raises_or_returns_recursive(name, node)
722722

723+
if isinstance(node, nodes.Match):
724+
return all(
725+
NamesConsumer._defines_name_raises_or_returns_recursive(name, case)
726+
for case in node.cases
727+
)
728+
723729
if not isinstance(node, nodes.If):
724730
return False
725731

@@ -768,6 +774,7 @@ def _branch_handles_name(self, name: str, body: Iterable[nodes.NodeNG]) -> bool:
768774
nodes.With,
769775
nodes.For,
770776
nodes.While,
777+
nodes.Match,
771778
),
772779
)
773780
and self._inferred_to_define_name_raise_or_return(name, if_body_stmt)
@@ -1010,6 +1017,11 @@ def _defines_name_raises_or_returns_recursive(
10101017
and NamesConsumer._defines_name_raises_or_returns_recursive(name, stmt)
10111018
):
10121019
return True
1020+
if isinstance(stmt, nodes.Match):
1021+
return all(
1022+
NamesConsumer._defines_name_raises_or_returns_recursive(name, case)
1023+
for case in stmt.cases
1024+
)
10131025
return False
10141026

10151027
@staticmethod

tests/functional/u/used/used_before_assignment_py310.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,104 @@
55
print("x used to cause used-before-assignment!")
66
case _:
77
print("good thing it doesn't now!")
8+
9+
10+
# pylint: disable = missing-function-docstring, redefined-outer-name, missing-class-docstring
11+
12+
# https://github.com/pylint-dev/pylint/issues/9668
13+
from enum import Enum
14+
from pylint.constants import PY311_PLUS
15+
if PY311_PLUS:
16+
from typing import assert_never # pylint: disable=no-name-in-module
17+
else:
18+
from typing_extensions import assert_never
19+
20+
class Example(Enum):
21+
FOO = 1
22+
BAR = 2
23+
24+
def check_value_if_then_match_return(example: Example, should_check: bool) -> str | None:
25+
if should_check:
26+
result = None
27+
else:
28+
match example:
29+
case Example.FOO:
30+
result = "foo"
31+
case Example.BAR:
32+
result = "bar"
33+
case _:
34+
return None
35+
36+
return result
37+
38+
def check_value_if_then_match_raise(example: Example, should_check: bool) -> str | None:
39+
if should_check:
40+
result = None
41+
else:
42+
match example:
43+
case Example.FOO:
44+
result = "foo"
45+
case Example.BAR:
46+
result = "bar"
47+
case _:
48+
raise ValueError("Not a valid enum")
49+
50+
return result
51+
52+
def check_value_if_then_match_assert_never(example: Example, should_check: bool) -> str | None:
53+
if should_check:
54+
result = None
55+
else:
56+
match example:
57+
case Example.FOO:
58+
result = "foo"
59+
case Example.BAR:
60+
result = "bar"
61+
case _:
62+
assert_never(example)
63+
64+
return result
65+
66+
def g(x):
67+
if x is None:
68+
y = 0
69+
else:
70+
match x:
71+
case int():
72+
y = x
73+
case _:
74+
raise TypeError(type(x))
75+
76+
return y
77+
78+
def check_value_if_then_match_nested(
79+
example: Example, example_inner: Example, should_check: bool
80+
) -> str | None:
81+
if should_check:
82+
result = None
83+
else:
84+
match example:
85+
case Example.FOO:
86+
match example_inner:
87+
case Example.BAR:
88+
result = "bar"
89+
case _:
90+
return None
91+
case _:
92+
return None
93+
94+
return result
95+
96+
def check_value_if_then_match_non_exhaustive(example: Example, should_check: bool) -> str | None:
97+
if should_check:
98+
result = None
99+
else:
100+
match example:
101+
case Example.FOO:
102+
result = "foo"
103+
case Example.BAR:
104+
pass
105+
case _:
106+
return None
107+
108+
return result # [possibly-used-before-assignment]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
possibly-used-before-assignment:108:11:108:17:check_value_if_then_match_non_exhaustive:Possibly using variable 'result' before assignment:CONTROL_FLOW

0 commit comments

Comments
 (0)