Skip to content

Commit

Permalink
Add an exception for IndexError inside uninferable_final_decorator (
Browse files Browse the repository at this point in the history
#6532)


Co-authored-by: Pierre Sassoulas <pierre.sassoulas@gmail.com>
Co-authored-by: Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
  • Loading branch information
3 people committed May 9, 2022
1 parent 45cbae2 commit 9ed3720
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 12 deletions.
3 changes: 3 additions & 0 deletions ChangeLog
Expand Up @@ -20,6 +20,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?
Expand Down
4 changes: 4 additions & 0 deletions doc/whatsnew/2.13.rst
Expand Up @@ -639,3 +639,7 @@ Other Changes
``open``

Closes #6414

* Fix ``IndexError`` crash in ``uninferable_final_decorators`` method.

Relates to #6531
30 changes: 18 additions & 12 deletions pylint/checkers/utils.py
Expand Up @@ -820,28 +820,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"
Expand Down
30 changes: 30 additions & 0 deletions 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

0 comments on commit 9ed3720

Please sign in to comment.