Skip to content

Conversation

@machin0r
Copy link

Add support for functools.lru_cache decorator, Fixes #16261

Description

Problem: Functions decorated with @lru_cache lose their original type signatures, preventing mypy from validating function calls properly.

Solution:

  • Added functools_lru_cache_callback to preserve function signatures for @lru_cache
  • Added lru_cache_wrapper_call_callback to validate calls to cached functions
  • Registered callbacks in DefaultPlugin for both decorator creation and wrapper calls
  • Added tests for different lru_cache patterns

Supported patterns:

  • @lru_cache - bare decorator
  • @lru_cache() - empty parentheses
  • @lru_cache(maxsize=128) - parameters

Example

Before:

from functools import lru_cache
@lru_cache()
def f(x: int) -> str:
    return str(x)

f()  # No error, mypy can't validate arguments
f("str")  # No error, mypy can't check types

After:
from functools import lru_cache
@lru_cache()
def f(x: int) -> str:
    return str(x)

f()  # error: Missing positional argument "x" in call to "f"  [call-arg]
f("str")  # error: Argument 1 to "f" has incompatible type "str"; expected "int"  [arg-type]

Files changed:

  • mypy/plugins/functools.py - Implementation
  • mypy/plugins/default.py - Plugin registration
  • test-data/unit/check-functools.test - Test cases
  • test-data/unit/lib-stub/functools.pyi - Type stub updates

@machin0r machin0r marked this pull request as draft July 13, 2025 17:14
@machin0r machin0r marked this pull request as ready for review July 13, 2025 17:47
@github-actions

This comment has been minimized.

@machin0r machin0r force-pushed the implement-lru-cache-support branch from 9aa3d9f to a4372e8 Compare July 13, 2025 18:35
@machin0r machin0r marked this pull request as draft July 13, 2025 18:46
@machin0r machin0r force-pushed the implement-lru-cache-support branch from a4372e8 to d9d5d00 Compare July 13, 2025 18:49
@github-actions

This comment has been minimized.

@machin0r machin0r marked this pull request as ready for review July 13, 2025 19:46
@cdce8p cdce8p added the topic-plugins The plugin API and ideas for new plugins label Jul 15, 2025
@machin0r
Copy link
Author

Hi! This PR has been open for a while now, I just wanted to check if there's anything I can clarify or improve to help with review.

Happy to make any changes needed!

@machin0r
Copy link
Author

Hey just following up on this again, is there anything I can do to help move this along?

@github-actions

This comment has been minimized.

@machin0r machin0r force-pushed the implement-lru-cache-support branch from 671ce92 to edfda9b Compare December 23, 2025 17:18
@github-actions

This comment has been minimized.

@machin0r machin0r force-pushed the implement-lru-cache-support branch from 2c022ea to 3e81c9d Compare December 23, 2025 23:19
- Add lru_cache callback to functools plugin for type validation
 - Register callbacks in default plugin for decorator and wrapper calls
 - Support different lru_cache patterns: @lru_cache, @lru_cache(), @lru_cache(maxsize=N)

Fixes issue python#16261
@machin0r machin0r force-pushed the implement-lru-cache-support branch from 98f0193 to d9466f0 Compare December 23, 2025 23:38
@github-actions
Copy link
Contributor

Diff from mypy_primer, showing the effect of this PR on open source code:

psycopg (https://github.com/psycopg/psycopg)
+ psycopg/psycopg/rows.py:136: error: Argument 2 to "_make_nt" has incompatible type "*Generator[bytes | None, None, None]"; expected "bytes"  [arg-type]

meson (https://github.com/mesonbuild/meson)
+ mesonbuild/arglist.py:133:21: error: Missing positional argument "arg" in call to "_can_dedup" of "CompilerArgs"  [call-arg]
+ mesonbuild/arglist.py:133:37: error: Argument 1 to "_can_dedup" of "CompilerArgs" has incompatible type "str"; expected "type[CompilerArgs]"  [arg-type]
+ mesonbuild/arglist.py:139:21: error: Missing positional argument "arg" in call to "_can_dedup" of "CompilerArgs"  [call-arg]
+ mesonbuild/arglist.py:139:37: error: Argument 1 to "_can_dedup" of "CompilerArgs" has incompatible type "str"; expected "type[CompilerArgs]"  [arg-type]
+ mesonbuild/arglist.py:302:21: error: Missing positional argument "arg" in call to "_can_dedup" of "CompilerArgs"  [call-arg]
+ mesonbuild/arglist.py:302:37: error: Argument 1 to "_can_dedup" of "CompilerArgs" has incompatible type "str"; expected "type[CompilerArgs]"  [arg-type]
+ mesonbuild/arglist.py:309:16: error: Missing positional argument "arg" in call to "_should_prepend" of "CompilerArgs"  [call-arg]
+ mesonbuild/arglist.py:309:37: error: Argument 1 to "_should_prepend" of "CompilerArgs" has incompatible type "str"; expected "type[CompilerArgs]"  [arg-type]
+ mesonbuild/backend/backends.py:1154:34: error: Missing positional argument "target" in call to "extract_dll_paths" of "Backend"  [call-arg]
+ mesonbuild/backend/backends.py:1154:57: error: Argument 1 to "extract_dll_paths" of "Backend" has incompatible type "BuildTarget"; expected "type[Backend]"  [arg-type]

paasta (https://github.com/yelp/paasta)
+ paasta_tools/cli/cmds/validate.py:1195: error: Argument 1 to "get_config_file_dict" has incompatible type "Path"; expected "str"  [arg-type]

core (https://github.com/home-assistant/core)
+ homeassistant/components/camera/webrtc.py:54: error: Missing positional argument "cls" in call to "_get_type" of "WebRTCMessage"  [call-arg]
+ homeassistant/components/xiaomi_aqara/config_flow.py:229: error: Argument 1 to "format_mac" has incompatible type "str | None"; expected "str"  [arg-type]
+ homeassistant/components/bsblan/config_flow.py:310: error: Argument 1 to "format_mac" has incompatible type "str | None"; expected "str"  [arg-type]
+ homeassistant/components/wled/config_flow.py:75: error: Argument 1 to "format_mac" has incompatible type "str | None"; expected "str"  [arg-type]
+ homeassistant/components/mqtt_room/sensor.py:199: error: Argument 1 to "_slugify_upper" has incompatible type "Any | None"; expected "str"  [arg-type]
+ homeassistant/components/ecovacs/sensor.py:389: error: Argument "battery_level" to "icon_for_battery_level" has incompatible type "str | int | float | None"; expected "int | None"  [arg-type]
+ homeassistant/components/esphome/light.py:182: error: Argument 1 to "_filter_color_modes" has incompatible type "tuple[Any, ...]"; expected "list[Any]"  [arg-type]
+ homeassistant/components/esphome/light.py:191: error: Argument 1 to "_filter_color_modes" has incompatible type "tuple[Any, ...]"; expected "list[Any]"  [arg-type]
+ homeassistant/components/esphome/light.py:202: error: Argument 1 to "_filter_color_modes" has incompatible type "tuple[Any, ...]"; expected "list[Any]"  [arg-type]
+ homeassistant/components/esphome/light.py:211: error: Argument 1 to "_filter_color_modes" has incompatible type "tuple[Any, ...]"; expected "list[Any]"  [arg-type]
+ homeassistant/components/esphome/light.py:212: error: Argument 1 to "_filter_color_modes" has incompatible type "tuple[Any, ...]"; expected "list[Any]"  [arg-type]
+ homeassistant/components/esphome/light.py:217: error: Argument 1 to "_filter_color_modes" has incompatible type "tuple[Any, ...]"; expected "list[Any]"  [arg-type]
+ homeassistant/components/esphome/light.py:229: error: Argument 1 to "_filter_color_modes" has incompatible type "tuple[Any, ...]"; expected "list[Any]"  [arg-type]
+ homeassistant/components/esphome/light.py:246: error: Argument 1 to "_filter_color_modes" has incompatible type "tuple[Any, ...]"; expected "list[Any]"  [arg-type]
+ homeassistant/components/esphome/light.py:251: error: Argument 1 to "_filter_color_modes" has incompatible type "tuple[Any, ...]"; expected "list[Any]"  [arg-type]
+ homeassistant/components/esphome/light.py:265: error: Argument 1 to "_filter_color_modes" has incompatible type "tuple[Any, ...]"; expected "list[Any]"  [arg-type]
+ homeassistant/components/esphome/light.py:346: error: Argument 1 to "_filter_color_modes" has incompatible type "tuple[Any, ...]"; expected "list[Any]"  [arg-type]
+ homeassistant/components/esphome/manager.py:593: error: Argument 1 to "format_mac" has incompatible type "str | None"; expected "str"  [arg-type]
+ homeassistant/components/esphome/config_flow.py:664: error: Argument 1 to "format_mac" has incompatible type "str | None"; expected "str"  [arg-type]
+ homeassistant/components/esphome/config_flow.py:707: error: Argument 1 to "format_mac" has incompatible type "str | None"; expected "str"  [arg-type]

mitmproxy (https://github.com/mitmproxy/mitmproxy)
+ mitmproxy/coretypes/serializable.py:83: error: Missing positional argument "cls" in call to "__fields" of "SerializableDataclass"  [call-arg]
+ mitmproxy/coretypes/serializable.py:99: error: Missing positional argument "cls" in call to "__fields" of "SerializableDataclass"  [call-arg]
+ mitmproxy/tools/console/common.py:808: error: Argument "request_timestamp" to "format_dns_flow" has incompatible type "float | None"; expected "float"  [arg-type]
+ mitmproxy/tools/console/common.py:816: error: Argument "error_message" to "format_dns_flow" has incompatible type "str | None"; expected "str"  [arg-type]

rich (https://github.com/Textualize/rich)
+ rich/progress_bar.py:145: error: Argument 3 to "_get_pulse_segments" of "ProgressBar" has incompatible type "str | None"; expected "str"  [arg-type]

pandas (https://github.com/pandas-dev/pandas)
+ pandas/core/arrays/base.py:2741: error: Missing positional argument "is_numeric" in call to "_get_cython_function" of "WrappedCythonOp"  [call-arg]
+ pandas/core/arrays/base.py:2741: error: Argument 1 to "_get_cython_function" of "WrappedCythonOp" has incompatible type "str"; expected "type[WrappedCythonOp]"  [arg-type]
+ pandas/core/arrays/base.py:2741: error: Argument 3 to "_get_cython_function" of "WrappedCythonOp" has incompatible type "dtype[object_ | Any]"; expected "str"  [arg-type]
+ pandas/core/arrays/base.py:2741: error: Argument 4 to "_get_cython_function" of "WrappedCythonOp" has incompatible type "bool"; expected "dtype[Any]"  [arg-type]
+ pandas/core/groupby/ops.py:409: error: Missing positional argument "is_numeric" in call to "_get_cython_function" of "WrappedCythonOp"  [call-arg]
+ pandas/core/groupby/ops.py:409: error: Argument 1 to "_get_cython_function" of "WrappedCythonOp" has incompatible type "str"; expected "type[WrappedCythonOp]"  [arg-type]
+ pandas/core/groupby/ops.py:409: error: Argument 3 to "_get_cython_function" of "WrappedCythonOp" has incompatible type "dtype[Any]"; expected "str"  [arg-type]
+ pandas/core/groupby/ops.py:409: error: Argument 4 to "_get_cython_function" of "WrappedCythonOp" has incompatible type "bool"; expected "dtype[Any]"  [arg-type]

ibis (https://github.com/ibis-project/ibis)
+ ibis/backends/tests/test_temporal.py:2221: error: Argument "exclude" to "_get_backend_names" has incompatible type "tuple[str, str]"; expected "tuple[str]"  [arg-type]
+ ibis/backends/tests/test_temporal.py:2239: error: Argument "exclude" to "_get_backend_names" has incompatible type "tuple[str, str, str, str, str, str]"; expected "tuple[str]"  [arg-type]

poetry (https://github.com/python-poetry/poetry)
+ tests/utils/test_password_manager.py:261: error: Missing positional argument "cls" in call to "is_available" of "PoetryKeyring"  [call-arg]
+ tests/utils/test_password_manager.py:269: error: Missing positional argument "cls" in call to "is_available" of "PoetryKeyring"  [call-arg]
+ tests/utils/test_password_manager.py:277: error: Missing positional argument "cls" in call to "is_available" of "PoetryKeyring"  [call-arg]
+ tests/utils/test_password_manager.py:285: error: Missing positional argument "cls" in call to "is_available" of "PoetryKeyring"  [call-arg]
+ tests/utils/test_password_manager.py:291: error: Missing positional argument "cls" in call to "is_available" of "PoetryKeyring"  [call-arg]
+ tests/utils/test_password_manager.py:299: error: Missing positional argument "cls" in call to "is_available" of "PoetryKeyring"  [call-arg]

@machin0r
Copy link
Author

Hi @JukkaL @hauntsaninja ,

This PR has been open for a while, I'd love your review or guidance on what’s needed to get it merged.
Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

topic-plugins The plugin API and ideas for new plugins

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Parameters for a call to a function wrapped in a lru_cache are not checked

2 participants