summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Byrne <31762852+mbyrnepr2@users.noreply.github.com>2022-05-07 21:23:26 +0200
committerGitHub <noreply@github.com>2022-05-07 21:23:26 +0200
commitccd87b41819153ffe33f19d9e7db499aba19bbca (patch)
treed7382509efbd1c641b377dcc66f59904f5419e3c
parentd13af82135e12b0ccc7d928a011b00821907e969 (diff)
downloadpylint-git-ccd87b41819153ffe33f19d9e7db499aba19bbca.tar.gz
Add an exception for `IndexError` inside `uninferable_final_decorator` (#6532)
Co-authored-by: Pierre Sassoulas <pierre.sassoulas@gmail.com> Co-authored-by: Daniƫl van Noord <13665637+DanielNoord@users.noreply.github.com>
-rw-r--r--ChangeLog3
-rw-r--r--doc/whatsnew/2.13.rst4
-rw-r--r--pylint/checkers/utils.py30
-rw-r--r--tests/functional/r/regression/regression_6531_crash_index_error.py30
4 files changed, 55 insertions, 12 deletions
diff --git a/ChangeLog b/ChangeLog
index 84c91e9ea..245910e58 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -317,6 +317,9 @@ What's New in Pylint 2.13.9?
============================
Release date: TBA
+* Fix ``IndexError`` crash in ``uninferable_final_decorators`` method.
+
+ Relates to #6531
What's New in Pylint 2.13.8?
diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst
index 895d5d369..b7a3eeff4 100644
--- a/doc/whatsnew/2.13.rst
+++ b/doc/whatsnew/2.13.rst
@@ -639,3 +639,7 @@ Other Changes
``open``
Closes #6414
+
+* Fix ``IndexError`` crash in ``uninferable_final_decorators`` method.
+
+ Relates to #6531
diff --git a/pylint/checkers/utils.py b/pylint/checkers/utils.py
index ed263fc4e..3f267e33c 100644
--- a/pylint/checkers/utils.py
+++ b/pylint/checkers/utils.py
@@ -847,28 +847,34 @@ def uninferable_final_decorators(
"""
decorators = []
for decorator in getattr(node, "nodes", []):
+ import_nodes: tuple[nodes.Import | nodes.ImportFrom] | None = None
+
+ # Get the `Import` node. The decorator is of the form: @module.name
if isinstance(decorator, nodes.Attribute):
- try:
- import_node = decorator.expr.lookup(decorator.expr.name)[1][0]
- except AttributeError:
- continue
+ inferred = safe_infer(decorator.expr)
+ if isinstance(inferred, nodes.Module) and inferred.qname() == "typing":
+ _, import_nodes = decorator.expr.lookup(decorator.expr.name)
+
+ # Get the `ImportFrom` node. The decorator is of the form: @name
elif isinstance(decorator, nodes.Name):
- lookup_values = decorator.lookup(decorator.name)
- if lookup_values[1]:
- import_node = lookup_values[1][0]
- else:
- continue # pragma: no cover # Covered on Python < 3.8
- else:
+ _, import_nodes = decorator.lookup(decorator.name)
+
+ # The `final` decorator is expected to be found in the
+ # import_nodes. Continue if we don't find any `Import` or `ImportFrom`
+ # nodes for this decorator.
+ if not import_nodes:
continue
+ import_node = import_nodes[0]
if not isinstance(import_node, (astroid.Import, astroid.ImportFrom)):
continue
import_names = dict(import_node.names)
- # from typing import final
+ # Check if the import is of the form: `from typing import final`
is_from_import = ("final" in import_names) and import_node.modname == "typing"
- # import typing
+
+ # Check if the import is of the form: `import typing`
is_import = ("typing" in import_names) and getattr(
decorator, "attrname", None
) == "final"
diff --git a/tests/functional/r/regression/regression_6531_crash_index_error.py b/tests/functional/r/regression/regression_6531_crash_index_error.py
new file mode 100644
index 000000000..6cdc96617
--- /dev/null
+++ b/tests/functional/r/regression/regression_6531_crash_index_error.py
@@ -0,0 +1,30 @@
+"""Regression test for https://github.com/PyCQA/pylint/issues/6531."""
+
+# pylint: disable=missing-docstring, redefined-outer-name
+
+import pytest
+
+
+class Wallet:
+ def __init__(self):
+ self.balance = 0
+
+ def add_cash(self, earned):
+ self.balance += earned
+
+ def spend_cash(self, spent):
+ self.balance -= spent
+
+@pytest.fixture
+def my_wallet():
+ '''Returns a Wallet instance with a zero balance'''
+ return Wallet()
+
+@pytest.mark.parametrize("earned,spent,expected", [
+ (30, 10, 20),
+ (20, 2, 18),
+])
+def test_transactions(my_wallet, earned, spent, expected):
+ my_wallet.add_cash(earned)
+ my_wallet.spend_cash(spent)
+ assert my_wallet.balance == expected