Skip to content

Commit 8076ac2

Browse files
Remove assumption of direct parentage in used-before-assignment homonym handling
The previous fixes for false positives involving homonyms with variables in comprehension tests in #5586 and #5817 still relied on assumptions of direct parentage.
1 parent 789a381 commit 8076ac2

File tree

3 files changed

+78
-10
lines changed

3 files changed

+78
-10
lines changed

ChangeLog

+6
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ What's New in Pylint 2.13.4?
4141
============================
4242
Release date: TBA
4343

44+
* Fix false positive regression in 2.13.0 for ``used-before-assignment`` for
45+
homonyms between variable assignments in try/except blocks and variables in
46+
a comprehension's filter.
47+
48+
Closes #6035
49+
4450
* Include ``testing_pylintrc`` in source and wheel distributions.
4551

4652
Closes #6028

pylint/checkers/variables.py

+30-10
Original file line numberDiff line numberDiff line change
@@ -629,13 +629,12 @@ def get_next_to_consume(self, node: nodes.Name) -> Optional[List[nodes.NodeNG]]:
629629
):
630630
return found_nodes
631631

632+
# And is not part of a test in a filtered comprehension
633+
if VariablesChecker._has_homonym_in_comprehension_test(node):
634+
return found_nodes
635+
632636
# Filter out assignments in ExceptHandlers that node is not contained in
633-
# unless this is a test in a filtered comprehension
634-
# Example: [e for e in range(3) if e] <--- followed by except e:
635-
if found_nodes and (
636-
not isinstance(parent_node, nodes.Comprehension)
637-
or node not in parent_node.ifs
638-
):
637+
if found_nodes:
639638
found_nodes = [
640639
n
641640
for n in found_nodes
@@ -1506,10 +1505,7 @@ def _check_consumer(
15061505
# (like "if x" in "[x for x in expr() if x]")
15071506
# https://github.com/PyCQA/pylint/issues/5586
15081507
and not (
1509-
(
1510-
isinstance(node.parent.parent, nodes.Comprehension)
1511-
and node.parent in node.parent.parent.ifs
1512-
)
1508+
self._has_homonym_in_comprehension_test(node)
15131509
# Or homonyms against values to keyword arguments
15141510
# (like "var" in "[func(arg=var) for var in expr()]")
15151511
or (
@@ -2482,6 +2478,30 @@ def _has_homonym_in_upper_function_scope(
24822478
for _consumer in self._to_consume[index - 1 :: -1]
24832479
)
24842480

2481+
@staticmethod
2482+
def _has_homonym_in_comprehension_test(node: nodes.Name) -> bool:
2483+
"""Return True if `node`'s frame contains a comprehension employing an
2484+
identical name in a test.
2485+
2486+
The name in the test could appear at varying depths:
2487+
2488+
Examples:
2489+
[x for x in range(3) if name]
2490+
[x for x in range(3) if name.num == 1]
2491+
[x for x in range(3)] if call(name.num)]
2492+
"""
2493+
closest_comprehension = utils.get_node_first_ancestor_of_type(
2494+
node, nodes.Comprehension
2495+
)
2496+
return (
2497+
closest_comprehension is not None
2498+
and node.frame(future=True).parent_of(closest_comprehension)
2499+
and any(
2500+
test is node or test.parent_of(node)
2501+
for test in closest_comprehension.ifs
2502+
)
2503+
)
2504+
24852505
def _store_type_annotation_node(self, type_annotation):
24862506
"""Given a type annotation, store all the name nodes it refers to."""
24872507
if isinstance(type_annotation, nodes.Name):

tests/functional/u/used/used_before_assignment_filtered_comprehension.py

+42
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,45 @@ def func():
77
except ZeroDivisionError:
88
value = 1
99
print(value)
10+
11+
12+
def func2():
13+
"""Same, but with attribute access."""
14+
try:
15+
print(value for value in range(1 / 0) if isinstance(value.num, int))
16+
except ZeroDivisionError:
17+
value = 1
18+
print(value)
19+
20+
21+
def func3():
22+
"""Same, but with no call."""
23+
try:
24+
print(value for value in range(1 / 0) if value)
25+
except ZeroDivisionError:
26+
value = 1
27+
print(value)
28+
29+
30+
def func4():
31+
"""https://github.com/PyCQA/pylint/issues/6035"""
32+
assets = [asset for asset in range(3) if asset.name == "filename"]
33+
34+
try:
35+
raise ValueError
36+
except ValueError:
37+
asset = assets[0]
38+
print(asset)
39+
40+
41+
def func5():
42+
"""Similar, but with subscript notation"""
43+
results = {}
44+
# pylint: disable-next=consider-using-dict-items
45+
filtered = [k for k in results if isinstance(results[k], dict)]
46+
47+
try:
48+
1 / 0
49+
except ZeroDivisionError:
50+
k = None
51+
print(k, filtered)

0 commit comments

Comments
 (0)