Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions doc/whatsnew/fragments/10510.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Fixed crash in 'unnecessary-list-index-lookup' when starting an enumeration using
minus the length of an iterable inside a dict comprehension when the len call was only
made in this dict comprehension, and not elsewhere. Also changed the approach,
to use inference in all cases but the simple ones, so we don't have to fix crashes
one by one for arbitrarily complex expressions in enumerate.

Closes #10510
28 changes: 15 additions & 13 deletions pylint/checkers/refactoring/refactoring_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -2452,18 +2452,20 @@ def _enumerate_with_start(
return False, confidence

def _get_start_value(self, node: nodes.NodeNG) -> tuple[int | None, Confidence]:
if (
isinstance(node, (nodes.Name, nodes.Call, nodes.Attribute))
or isinstance(node, nodes.UnaryOp)
and isinstance(node.operand, (nodes.Attribute, nodes.Name))
):
inferred = utils.safe_infer(node)
# inferred can be an astroid.base.Instance as in 'enumerate(x, int(y))' or
# not correctly inferred (None)
start_val = inferred.value if isinstance(inferred, nodes.Const) else None
return start_val, INFERENCE
if isinstance(node, nodes.UnaryOp):
return node.operand.value, HIGH
# Most common use cases are a constant integer or minus a constant integer. We
# don't need inference for that. If that's not the case, we assume arbitrary
# complexity and we use inference.
if isinstance(node, nodes.Const):
return node.value, HIGH
return None, HIGH
if isinstance(node, nodes.UnaryOp) and isinstance(node.operand, nodes.Const):
return node.operand.value, HIGH
inferred = utils.safe_infer(node)
if isinstance(inferred, nodes.Const):
return inferred.value, INFERENCE
# inferred can be an 'astroid.base.Instance' in 'enumerate(x, int(y))',
# for example. We're doing nothing in this case for now, as extracting
# the value is costly.

# At this point the most likely cases is that the node is uninferable
# But we don't have to check if it's actually uninferable.
return None, INFERENCE
Original file line number Diff line number Diff line change
Expand Up @@ -167,3 +167,7 @@ def random_uninferrable_start(pears):

for _, _ in enumerate(pears, random.choice([5, 42])):
...

# Regression test for https://github.com/pylint-dev/pylint/issues/10510
xs = [1, 2, 3]
test_dict = {j: i for i, j in enumerate(xs, -len(xs))}
Loading