diff options
author | Tim Martin <tim@asymptotic.co.uk> | 2022-06-04 07:02:51 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-06-04 08:02:51 +0200 |
commit | bdd470d074aa6fda47298f64a487b66d9234ec0a (patch) | |
tree | 9c33769831ff36ce8862a0e73eb86aae06be82f7 | |
parent | 44a3d96e1af985c6ad290a51555b9c7ced0a7079 (diff) | |
download | pylint-git-bdd470d074aa6fda47298f64a487b66d9234ec0a.tar.gz |
Prevent warnings on dict / list index lookup with destructuring assignment (#6808)
Co-authored-by: Jacob Walls <jacobtylerwalls@gmail.com>
4 files changed, 43 insertions, 10 deletions
diff --git a/doc/whatsnew/2/2.14/full.rst b/doc/whatsnew/2/2.14/full.rst index 25864d655..ec885c229 100644 --- a/doc/whatsnew/2/2.14/full.rst +++ b/doc/whatsnew/2/2.14/full.rst @@ -5,6 +5,11 @@ What's New in Pylint 2.14.1? ---------------------------- Release date: TBA +* Avoid reporting ``unnecessary-dict-index-lookup`` or ``unnecessary-list-index-lookup`` + when the index lookup is part of a destructuring assignment. + + Closes #6788 + * Fixed parsing of unrelated options in ``tox.ini``. Closes #6800 diff --git a/pylint/checkers/refactoring/refactoring_checker.py b/pylint/checkers/refactoring/refactoring_checker.py index 480078df8..66773e2b3 100644 --- a/pylint/checkers/refactoring/refactoring_checker.py +++ b/pylint/checkers/refactoring/refactoring_checker.py @@ -160,6 +160,25 @@ def _will_be_released_automatically(node: nodes.Call) -> bool: return func.qname() in callables_taking_care_of_exit +def _is_part_of_assignment_target(node: nodes.NodeNG) -> bool: + """Check whether use of a variable is happening as part of the left-hand + side of an assignment. + + This requires recursive checking, because destructuring assignment can have + arbitrarily nested tuples and lists to unpack. + """ + if isinstance(node.parent, nodes.Assign): + return node in node.parent.targets + + if isinstance(node.parent, nodes.AugAssign): + return node == node.parent.target + + if isinstance(node.parent, (nodes.Tuple, nodes.List)): + return _is_part_of_assignment_target(node.parent) + + return False + + class ConsiderUsingWithStack(NamedTuple): """Stack for objects that may potentially trigger a R1732 message if they are not used in a ``with`` block later on. @@ -1917,15 +1936,13 @@ class RefactoringChecker(checkers.BaseTokenChecker): value = subscript.slice - if isinstance(node, nodes.For) and ( - isinstance(subscript.parent, nodes.Assign) - and subscript in subscript.parent.targets - or isinstance(subscript.parent, nodes.AugAssign) - and subscript == subscript.parent.target + if isinstance(node, nodes.For) and _is_part_of_assignment_target( + subscript ): # Ignore this subscript if it is the target of an assignment # Early termination; after reassignment dict index lookup will be necessary return + if isinstance(subscript.parent, nodes.Delete): # Ignore this subscript if it's used with the delete keyword return @@ -2017,11 +2034,8 @@ class RefactoringChecker(checkers.BaseTokenChecker): ) for child in children: for subscript in child.nodes_of_class(nodes.Subscript): - if isinstance(node, nodes.For) and ( - isinstance(subscript.parent, nodes.Assign) - and subscript in subscript.parent.targets - or isinstance(subscript.parent, nodes.AugAssign) - and subscript == subscript.parent.target + if isinstance(node, nodes.For) and _is_part_of_assignment_target( + subscript ): # Ignore this subscript if it is the target of an assignment # Early termination; after reassignment index lookup will be necessary diff --git a/tests/functional/u/unnecessary/unnecessary_dict_index_lookup.py b/tests/functional/u/unnecessary/unnecessary_dict_index_lookup.py index 7ae5488ce..79f9f6a35 100644 --- a/tests/functional/u/unnecessary/unnecessary_dict_index_lookup.py +++ b/tests/functional/u/unnecessary/unnecessary_dict_index_lookup.py @@ -119,3 +119,8 @@ f = Foo() for input_output in d.items(): f.input_output = input_output # pylint: disable=attribute-defined-outside-init print(d[f.input_output[0]]) + +# Regression test for https://github.com/PyCQA/pylint/issues/6788 +d = {'a': 1, 'b': 2, 'c': 3} +for key, val in d.items(): + ([d[key], x], y) = ([1, 2], 3) diff --git a/tests/functional/u/unnecessary/unnecessary_list_index_lookup.py b/tests/functional/u/unnecessary/unnecessary_list_index_lookup.py index ba99d3b9b..355d42a7f 100644 --- a/tests/functional/u/unnecessary/unnecessary_list_index_lookup.py +++ b/tests/functional/u/unnecessary/unnecessary_list_index_lookup.py @@ -53,3 +53,12 @@ for i, (a, b) in enumerate(pairs): # Regression test for https://github.com/PyCQA/pylint/issues/6603 for i, num in enumerate(): # raises TypeError, but shouldn't crash pylint pass + +# Regression test for https://github.com/PyCQA/pylint/issues/6788 +num_list = [1, 2, 3] +for a, b in enumerate(num_list): + num_list[a], _ = (2, 1) + +num_list = [1, 2, 3] +for a, b in enumerate(num_list): + ([x, num_list[a]], _) = ([5, 6], 1) |