summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJacob Walls <jacobtylerwalls@gmail.com>2022-03-30 21:29:58 -0400
committerPierre Sassoulas <pierre.sassoulas@gmail.com>2022-03-31 09:44:48 +0200
commit881262641ee1ca6fd6704b66b2b4ae37597e8fef (patch)
tree18c6a319c8474c7bd63fc5bad9b5a1cbaa8bfa86
parent5ea03af1268ae4776e8ddd9dc55e14f465710288 (diff)
downloadpylint-git-881262641ee1ca6fd6704b66b2b4ae37597e8fef.tar.gz
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.
-rw-r--r--ChangeLog6
-rw-r--r--pylint/checkers/variables.py40
-rw-r--r--tests/functional/u/used/used_before_assignment_filtered_comprehension.py42
3 files changed, 78 insertions, 10 deletions
diff --git a/ChangeLog b/ChangeLog
index 85b489f4d..11b85324e 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -20,6 +20,12 @@ What's New in Pylint 2.13.4?
============================
Release date: TBA
+* Fix false positive regression in 2.13.0 for ``used-before-assignment`` for
+ homonyms between variable assignments in try/except blocks and variables in
+ a comprehension's filter.
+
+ Closes #6035
+
* Include ``testing_pylintrc`` in source and wheel distributions.
Closes #6028
diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py
index 002eaf628..b92769822 100644
--- a/pylint/checkers/variables.py
+++ b/pylint/checkers/variables.py
@@ -623,13 +623,12 @@ scope_type : {self._atomic.scope_type}
):
return found_nodes
+ # And is not part of a test in a filtered comprehension
+ if VariablesChecker._has_homonym_in_comprehension_test(node):
+ return found_nodes
+
# Filter out assignments in ExceptHandlers that node is not contained in
- # unless this is a test in a filtered comprehension
- # Example: [e for e in range(3) if e] <--- followed by except e:
- if found_nodes and (
- not isinstance(parent_node, nodes.Comprehension)
- or node not in parent_node.ifs
- ):
+ if found_nodes:
found_nodes = [
n
for n in found_nodes
@@ -1500,10 +1499,7 @@ class VariablesChecker(BaseChecker):
# (like "if x" in "[x for x in expr() if x]")
# https://github.com/PyCQA/pylint/issues/5586
and not (
- (
- isinstance(node.parent.parent, nodes.Comprehension)
- and node.parent in node.parent.parent.ifs
- )
+ self._has_homonym_in_comprehension_test(node)
# Or homonyms against values to keyword arguments
# (like "var" in "[func(arg=var) for var in expr()]")
or (
@@ -2476,6 +2472,30 @@ class VariablesChecker(BaseChecker):
for _consumer in self._to_consume[index - 1 :: -1]
)
+ @staticmethod
+ def _has_homonym_in_comprehension_test(node: nodes.Name) -> bool:
+ """Return True if `node`'s frame contains a comprehension employing an
+ identical name in a test.
+
+ The name in the test could appear at varying depths:
+
+ Examples:
+ [x for x in range(3) if name]
+ [x for x in range(3) if name.num == 1]
+ [x for x in range(3)] if call(name.num)]
+ """
+ closest_comprehension = utils.get_node_first_ancestor_of_type(
+ node, nodes.Comprehension
+ )
+ return (
+ closest_comprehension is not None
+ and node.frame(future=True).parent_of(closest_comprehension)
+ and any(
+ test is node or test.parent_of(node)
+ for test in closest_comprehension.ifs
+ )
+ )
+
def _store_type_annotation_node(self, type_annotation):
"""Given a type annotation, store all the name nodes it refers to."""
if isinstance(type_annotation, nodes.Name):
diff --git a/tests/functional/u/used/used_before_assignment_filtered_comprehension.py b/tests/functional/u/used/used_before_assignment_filtered_comprehension.py
index f638b0be9..f0c569aad 100644
--- a/tests/functional/u/used/used_before_assignment_filtered_comprehension.py
+++ b/tests/functional/u/used/used_before_assignment_filtered_comprehension.py
@@ -7,3 +7,45 @@ def func():
except ZeroDivisionError:
value = 1
print(value)
+
+
+def func2():
+ """Same, but with attribute access."""
+ try:
+ print(value for value in range(1 / 0) if isinstance(value.num, int))
+ except ZeroDivisionError:
+ value = 1
+ print(value)
+
+
+def func3():
+ """Same, but with no call."""
+ try:
+ print(value for value in range(1 / 0) if value)
+ except ZeroDivisionError:
+ value = 1
+ print(value)
+
+
+def func4():
+ """https://github.com/PyCQA/pylint/issues/6035"""
+ assets = [asset for asset in range(3) if asset.name == "filename"]
+
+ try:
+ raise ValueError
+ except ValueError:
+ asset = assets[0]
+ print(asset)
+
+
+def func5():
+ """Similar, but with subscript notation"""
+ results = {}
+ # pylint: disable-next=consider-using-dict-items
+ filtered = [k for k in results if isinstance(results[k], dict)]
+
+ try:
+ 1 / 0
+ except ZeroDivisionError:
+ k = None
+ print(k, filtered)