Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Backport maintenance/2.15.x] Fix inference involving @functools.lru_cache decorator #2260

Merged
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
11 changes: 5 additions & 6 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,15 @@ What's New in astroid 2.15.7?
=============================
Release date: TBA



What's New in astroid 2.15.7?
=============================
Release date: 2023-07-08

* Fix a crash when inferring a ``typing.TypeVar`` call.

Closes pylint-dev/pylint#8802

* Fix inference of functions with ``@functools.lru_cache`` decorators without
parentheses.

Closes pylint-dev/pylint#8868


What's New in astroid 2.15.6?
=============================
Expand Down
8 changes: 5 additions & 3 deletions astroid/brain/brain_functools.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,15 +129,17 @@ def _looks_like_lru_cache(node) -> bool:
if not node.decorators:
return False
for decorator in node.decorators.nodes:
if not isinstance(decorator, Call):
if not isinstance(decorator, (Attribute, Call)):
continue
if _looks_like_functools_member(decorator, "lru_cache"):
return True
return False


def _looks_like_functools_member(node, member) -> bool:
"""Check if the given Call node is a functools.partial call."""
def _looks_like_functools_member(node: Attribute | Call, member: str) -> bool:
"""Check if the given Call node is the wanted member of functools."""
if isinstance(node, Attribute):
return node.attrname == member
if isinstance(node.func, Name):
return node.func.name == member
if isinstance(node.func, Attribute):
Expand Down
28 changes: 14 additions & 14 deletions tests/test_object_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -809,26 +809,26 @@ def test_str_argument_not_required(self) -> None:
assert not args.elts


class LruCacheModelTest(unittest.TestCase):
def test_lru_cache(self) -> None:
ast_nodes = builder.extract_node(
"""
@pytest.mark.parametrize("parentheses", (True, False))
def test_lru_cache(parentheses) -> None:
ast_nodes = builder.extract_node(
f"""
import functools
class Foo(object):
@functools.lru_cache()
@functools.lru_cache{"()" if parentheses else ""}
def foo():
pass
f = Foo()
f.foo.cache_clear #@
f.foo.__wrapped__ #@
f.foo.cache_info() #@
"""
)
assert isinstance(ast_nodes, list)
cache_clear = next(ast_nodes[0].infer())
self.assertIsInstance(cache_clear, astroid.BoundMethod)
wrapped = next(ast_nodes[1].infer())
self.assertIsInstance(wrapped, astroid.FunctionDef)
self.assertEqual(wrapped.name, "foo")
cache_info = next(ast_nodes[2].infer())
self.assertIsInstance(cache_info, astroid.Instance)
)
assert isinstance(ast_nodes, list)
cache_clear = next(ast_nodes[0].infer())
assert isinstance(cache_clear, astroid.BoundMethod)
wrapped = next(ast_nodes[1].infer())
assert isinstance(wrapped, astroid.FunctionDef)
assert wrapped.name == "foo"
cache_info = next(ast_nodes[2].infer())
assert isinstance(cache_info, astroid.Instance)
Loading