Skip to content

Commit 19c205d

Browse files
false positive unnecessary-list-index-lookup for enumerate (#7685)
* do not report unnecessary list index lookup if start arg is passed * account for calling start with 0 or negative num Co-authored-by: Pierre Sassoulas <[email protected]>
1 parent 7b0bc19 commit 19c205d

File tree

4 files changed

+113
-3
lines changed

4 files changed

+113
-3
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
``unnecessary-list-index-lookup`` will not be wrongly emitted if ``enumerate`` is called with ``start``.
2+
3+
Closes #7682

pylint/checkers/refactoring/refactoring_checker.py

+57-3
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from pylint import checkers
2222
from pylint.checkers import utils
2323
from pylint.checkers.utils import node_frame_class
24-
from pylint.interfaces import HIGH, INFERENCE
24+
from pylint.interfaces import HIGH, INFERENCE, Confidence
2525

2626
if TYPE_CHECKING:
2727
from pylint.lint import PyLinter
@@ -2110,6 +2110,12 @@ def _check_unnecessary_list_index_lookup(
21102110
# destructured, so we can't necessarily use it.
21112111
return
21122112

2113+
has_start_arg, confidence = self._enumerate_with_start(node)
2114+
if has_start_arg:
2115+
# enumerate is being called with start arg/kwarg so resulting index lookup
2116+
# is not redundant, hence we should not report an error.
2117+
return
2118+
21132119
iterating_object_name = node.iter.args[0].name
21142120
value_variable = node.target.elts[1]
21152121

@@ -2191,13 +2197,61 @@ def _check_unnecessary_list_index_lookup(
21912197
"unnecessary-list-index-lookup",
21922198
node=subscript,
21932199
args=(node.target.elts[1].name,),
2194-
confidence=HIGH,
2200+
confidence=confidence,
21952201
)
21962202

21972203
for subscript in bad_nodes:
21982204
self.add_message(
21992205
"unnecessary-list-index-lookup",
22002206
node=subscript,
22012207
args=(node.target.elts[1].name,),
2202-
confidence=HIGH,
2208+
confidence=confidence,
22032209
)
2210+
2211+
def _enumerate_with_start(
2212+
self, node: nodes.For | nodes.Comprehension
2213+
) -> tuple[bool, Confidence]:
2214+
"""Check presence of `start` kwarg or second argument to enumerate.
2215+
2216+
For example:
2217+
2218+
`enumerate([1,2,3], start=1)`
2219+
`enumerate([1,2,3], 1)`
2220+
2221+
If `start` is assigned to `0`, the default value, this is equivalent to
2222+
not calling `enumerate` with start.
2223+
"""
2224+
confidence = HIGH
2225+
2226+
if len(node.iter.args) > 1:
2227+
# We assume the second argument to `enumerate` is the `start` int arg.
2228+
# It's a reasonable assumption for now as it's the only possible argument:
2229+
# https://docs.python.org/3/library/functions.html#enumerate
2230+
start_arg = node.iter.args[1]
2231+
start_val, confidence = self._get_start_value(start_arg)
2232+
if start_val is None:
2233+
return False, confidence
2234+
return not start_val == 0, confidence
2235+
2236+
for keyword in node.iter.keywords:
2237+
if keyword.arg == "start":
2238+
start_val, confidence = self._get_start_value(keyword.value)
2239+
if start_val is None:
2240+
return False, confidence
2241+
return not start_val == 0, confidence
2242+
2243+
return False, confidence
2244+
2245+
def _get_start_value(self, node: nodes.NodeNG) -> tuple[int | None, Confidence]:
2246+
confidence = HIGH
2247+
2248+
if isinstance(node, (nodes.Name, nodes.Call)):
2249+
inferred = utils.safe_infer(node)
2250+
start_val = inferred.value if inferred else None
2251+
confidence = INFERENCE
2252+
elif isinstance(node, nodes.UnaryOp):
2253+
start_val = node.operand.value
2254+
else:
2255+
start_val = node.value
2256+
2257+
return start_val, confidence

tests/functional/u/unnecessary/unnecessary_list_index_lookup.py

+49
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,52 @@ def process_list_again(data):
8181
if i == 3: # more complex condition actually
8282
parts.insert(i, "X")
8383
print(part, parts[i])
84+
85+
# regression tests for https://github.com/PyCQA/pylint/issues/7682
86+
series = [1, 2, 3, 4, 5]
87+
output_list = [
88+
(item, series[index])
89+
for index, item in enumerate(series, start=1)
90+
if index < len(series)
91+
]
92+
93+
output_list = [
94+
(item, series[index])
95+
for index, item in enumerate(series, 1)
96+
if index < len(series)
97+
]
98+
99+
for idx, val in enumerate(series, start=2):
100+
print(series[idx])
101+
102+
for idx, val in enumerate(series, 2):
103+
print(series[idx])
104+
105+
for idx, val in enumerate(series, start=-2):
106+
print(series[idx])
107+
108+
for idx, val in enumerate(series, -2):
109+
print(series[idx])
110+
111+
for idx, val in enumerate(series, start=0):
112+
print(series[idx]) # [unnecessary-list-index-lookup]
113+
114+
for idx, val in enumerate(series, 0):
115+
print(series[idx]) # [unnecessary-list-index-lookup]
116+
117+
START = 0
118+
for idx, val in enumerate(series, start=START):
119+
print(series[idx]) # [unnecessary-list-index-lookup]
120+
121+
for idx, val in enumerate(series, START):
122+
print(series[idx]) # [unnecessary-list-index-lookup]
123+
124+
START = [1, 2, 3]
125+
for i, k in enumerate(series, len(START)):
126+
print(series[idx])
127+
128+
def return_start(start):
129+
return start
130+
131+
for i, k in enumerate(series, return_start(20)):
132+
print(series[idx])

tests/functional/u/unnecessary/unnecessary_list_index_lookup.txt

+4
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,7 @@ unnecessary-list-index-lookup:8:10:8:22::Unnecessary list index lookup, use 'val
22
unnecessary-list-index-lookup:43:52:43:64::Unnecessary list index lookup, use 'val' instead:HIGH
33
unnecessary-list-index-lookup:46:10:46:22::Unnecessary list index lookup, use 'val' instead:HIGH
44
unnecessary-list-index-lookup:74:10:74:27::Unnecessary list index lookup, use 'val' instead:HIGH
5+
unnecessary-list-index-lookup:112:10:112:21::Unnecessary list index lookup, use 'val' instead:HIGH
6+
unnecessary-list-index-lookup:115:10:115:21::Unnecessary list index lookup, use 'val' instead:HIGH
7+
unnecessary-list-index-lookup:119:10:119:21::Unnecessary list index lookup, use 'val' instead:INFERENCE
8+
unnecessary-list-index-lookup:122:10:122:21::Unnecessary list index lookup, use 'val' instead:INFERENCE

0 commit comments

Comments
 (0)