diff --git a/doc/whatsnew/2/2.14/full.rst b/doc/whatsnew/2/2.14/full.rst index 25864d655c..ec885c2299 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 480078df8c..66773e2b38 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 @@ def _check_unnecessary_dict_index_lookup( 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 @@ def _check_unnecessary_list_index_lookup( ) 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 7ae5488ce7..79f9f6a351 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 @@ class 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 ba99d3b9ba..355d42a7f0 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 @@ def process_list_again(data): # 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)