Skip to content

Commit 8812626

Browse files
jacobtylerwallsPierre-Sassoulas
authored andcommitted
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 5ea03af commit 8812626

3 files changed

Lines changed: 78 additions & 10 deletions

File tree

ChangeLog

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ What's New in Pylint 2.13.4?
2020
============================
2121
Release date: TBA
2222

23+
* Fix false positive regression in 2.13.0 for ``used-before-assignment`` for
24+
homonyms between variable assignments in try/except blocks and variables in
25+
a comprehension's filter.
26+
27+
Closes #6035
28+
2329
* Include ``testing_pylintrc`` in source and wheel distributions.
2430

2531
Closes #6028

pylint/checkers/variables.py

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -623,13 +623,12 @@ def get_next_to_consume(self, node: nodes.Name) -> Optional[List[nodes.NodeNG]]:
623623
):
624624
return found_nodes
625625

626+
# And is not part of a test in a filtered comprehension
627+
if VariablesChecker._has_homonym_in_comprehension_test(node):
628+
return found_nodes
629+
626630
# Filter out assignments in ExceptHandlers that node is not contained in
627-
# unless this is a test in a filtered comprehension
628-
# Example: [e for e in range(3) if e] <--- followed by except e:
629-
if found_nodes and (
630-
not isinstance(parent_node, nodes.Comprehension)
631-
or node not in parent_node.ifs
632-
):
631+
if found_nodes:
633632
found_nodes = [
634633
n
635634
for n in found_nodes
@@ -1500,10 +1499,7 @@ def _check_consumer(
15001499
# (like "if x" in "[x for x in expr() if x]")
15011500
# https://github.com/PyCQA/pylint/issues/5586
15021501
and not (
1503-
(
1504-
isinstance(node.parent.parent, nodes.Comprehension)
1505-
and node.parent in node.parent.parent.ifs
1506-
)
1502+
self._has_homonym_in_comprehension_test(node)
15071503
# Or homonyms against values to keyword arguments
15081504
# (like "var" in "[func(arg=var) for var in expr()]")
15091505
or (
@@ -2476,6 +2472,30 @@ def _has_homonym_in_upper_function_scope(
24762472
for _consumer in self._to_consume[index - 1 :: -1]
24772473
)
24782474

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

tests/functional/u/used/used_before_assignment_filtered_comprehension.py

Lines changed: 42 additions & 0 deletions
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)