From ca4f7b1922f65846f046f22a0c1619354e2d51e4 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Fri, 22 Aug 2025 08:39:14 +0200 Subject: [PATCH] [fix] Better approach in 'unnecessary-list-index-lookup' to avoid crashes Closes #10510 --- doc/whatsnew/fragments/10510.bugfix | 7 +++++ .../refactoring/refactoring_checker.py | 27 ++++++++++--------- .../unnecessary_list_index_lookup.py | 4 +++ 3 files changed, 26 insertions(+), 12 deletions(-) create mode 100644 doc/whatsnew/fragments/10510.bugfix diff --git a/doc/whatsnew/fragments/10510.bugfix b/doc/whatsnew/fragments/10510.bugfix new file mode 100644 index 0000000000..d7653b3cb4 --- /dev/null +++ b/doc/whatsnew/fragments/10510.bugfix @@ -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 diff --git a/pylint/checkers/refactoring/refactoring_checker.py b/pylint/checkers/refactoring/refactoring_checker.py index e277da9dc1..c0a3eae8d8 100644 --- a/pylint/checkers/refactoring/refactoring_checker.py +++ b/pylint/checkers/refactoring/refactoring_checker.py @@ -2450,17 +2450,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 diff --git a/tests/functional/u/unnecessary/unnecessary_list_index_lookup.py b/tests/functional/u/unnecessary/unnecessary_list_index_lookup.py index 3c6a450234..6a2663765a 100644 --- a/tests/functional/u/unnecessary/unnecessary_list_index_lookup.py +++ b/tests/functional/u/unnecessary/unnecessary_list_index_lookup.py @@ -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))}