summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Martin <tim@asymptotic.co.uk>2022-06-04 07:02:51 +0100
committerGitHub <noreply@github.com>2022-06-04 08:02:51 +0200
commitbdd470d074aa6fda47298f64a487b66d9234ec0a (patch)
tree9c33769831ff36ce8862a0e73eb86aae06be82f7
parent44a3d96e1af985c6ad290a51555b9c7ced0a7079 (diff)
downloadpylint-git-bdd470d074aa6fda47298f64a487b66d9234ec0a.tar.gz
Prevent warnings on dict / list index lookup with destructuring assignment (#6808)
Co-authored-by: Jacob Walls <jacobtylerwalls@gmail.com>
-rw-r--r--doc/whatsnew/2/2.14/full.rst5
-rw-r--r--pylint/checkers/refactoring/refactoring_checker.py34
-rw-r--r--tests/functional/u/unnecessary/unnecessary_dict_index_lookup.py5
-rw-r--r--tests/functional/u/unnecessary/unnecessary_list_index_lookup.py9
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)