Skip to content

Commit bdd470d

Browse files
Prevent warnings on dict / list index lookup with destructuring assignment (#6808)
Co-authored-by: Jacob Walls <[email protected]>
1 parent 44a3d96 commit bdd470d

File tree

4 files changed

+43
-10
lines changed

4 files changed

+43
-10
lines changed

doc/whatsnew/2/2.14/full.rst

+5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ What's New in Pylint 2.14.1?
55
----------------------------
66
Release date: TBA
77

8+
* Avoid reporting ``unnecessary-dict-index-lookup`` or ``unnecessary-list-index-lookup``
9+
when the index lookup is part of a destructuring assignment.
10+
11+
Closes #6788
12+
813
* Fixed parsing of unrelated options in ``tox.ini``.
914

1015
Closes #6800

pylint/checkers/refactoring/refactoring_checker.py

+24-10
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,25 @@ def _will_be_released_automatically(node: nodes.Call) -> bool:
160160
return func.qname() in callables_taking_care_of_exit
161161

162162

163+
def _is_part_of_assignment_target(node: nodes.NodeNG) -> bool:
164+
"""Check whether use of a variable is happening as part of the left-hand
165+
side of an assignment.
166+
167+
This requires recursive checking, because destructuring assignment can have
168+
arbitrarily nested tuples and lists to unpack.
169+
"""
170+
if isinstance(node.parent, nodes.Assign):
171+
return node in node.parent.targets
172+
173+
if isinstance(node.parent, nodes.AugAssign):
174+
return node == node.parent.target
175+
176+
if isinstance(node.parent, (nodes.Tuple, nodes.List)):
177+
return _is_part_of_assignment_target(node.parent)
178+
179+
return False
180+
181+
163182
class ConsiderUsingWithStack(NamedTuple):
164183
"""Stack for objects that may potentially trigger a R1732 message
165184
if they are not used in a ``with`` block later on.
@@ -1917,15 +1936,13 @@ def _check_unnecessary_dict_index_lookup(
19171936

19181937
value = subscript.slice
19191938

1920-
if isinstance(node, nodes.For) and (
1921-
isinstance(subscript.parent, nodes.Assign)
1922-
and subscript in subscript.parent.targets
1923-
or isinstance(subscript.parent, nodes.AugAssign)
1924-
and subscript == subscript.parent.target
1939+
if isinstance(node, nodes.For) and _is_part_of_assignment_target(
1940+
subscript
19251941
):
19261942
# Ignore this subscript if it is the target of an assignment
19271943
# Early termination; after reassignment dict index lookup will be necessary
19281944
return
1945+
19291946
if isinstance(subscript.parent, nodes.Delete):
19301947
# Ignore this subscript if it's used with the delete keyword
19311948
return
@@ -2017,11 +2034,8 @@ def _check_unnecessary_list_index_lookup(
20172034
)
20182035
for child in children:
20192036
for subscript in child.nodes_of_class(nodes.Subscript):
2020-
if isinstance(node, nodes.For) and (
2021-
isinstance(subscript.parent, nodes.Assign)
2022-
and subscript in subscript.parent.targets
2023-
or isinstance(subscript.parent, nodes.AugAssign)
2024-
and subscript == subscript.parent.target
2037+
if isinstance(node, nodes.For) and _is_part_of_assignment_target(
2038+
subscript
20252039
):
20262040
# Ignore this subscript if it is the target of an assignment
20272041
# Early termination; after reassignment index lookup will be necessary

tests/functional/u/unnecessary/unnecessary_dict_index_lookup.py

+5
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,8 @@ class Foo:
119119
for input_output in d.items():
120120
f.input_output = input_output # pylint: disable=attribute-defined-outside-init
121121
print(d[f.input_output[0]])
122+
123+
# Regression test for https://github.com/PyCQA/pylint/issues/6788
124+
d = {'a': 1, 'b': 2, 'c': 3}
125+
for key, val in d.items():
126+
([d[key], x], y) = ([1, 2], 3)

tests/functional/u/unnecessary/unnecessary_list_index_lookup.py

+9
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,12 @@ def process_list_again(data):
5353
# Regression test for https://github.com/PyCQA/pylint/issues/6603
5454
for i, num in enumerate(): # raises TypeError, but shouldn't crash pylint
5555
pass
56+
57+
# Regression test for https://github.com/PyCQA/pylint/issues/6788
58+
num_list = [1, 2, 3]
59+
for a, b in enumerate(num_list):
60+
num_list[a], _ = (2, 1)
61+
62+
num_list = [1, 2, 3]
63+
for a, b in enumerate(num_list):
64+
([x, num_list[a]], _) = ([5, 6], 1)

0 commit comments

Comments
 (0)