diff options
author | Jacob Walls <jacobtylerwalls@gmail.com> | 2022-03-30 21:29:58 -0400 |
---|---|---|
committer | Pierre Sassoulas <pierre.sassoulas@gmail.com> | 2022-03-31 09:44:48 +0200 |
commit | 881262641ee1ca6fd6704b66b2b4ae37597e8fef (patch) | |
tree | 18c6a319c8474c7bd63fc5bad9b5a1cbaa8bfa86 | |
parent | 5ea03af1268ae4776e8ddd9dc55e14f465710288 (diff) | |
download | pylint-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-- | ChangeLog | 6 | ||||
-rw-r--r-- | pylint/checkers/variables.py | 40 | ||||
-rw-r--r-- | tests/functional/u/used/used_before_assignment_filtered_comprehension.py | 42 |
3 files changed, 78 insertions, 10 deletions
@@ -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) |