Generic Callable return type inferred as first overload from overloaded function
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 20.6k
- Forks
- 3.3k
- PR merge metrics
- PR metrics pending
Description
Apologies in advance if this duplicates an existing issue. I reviewed a bunch, but none seemed like an exact match. #15015 and #15737 were the closest.
https://mypy-play.net/?mypy=latest&python=3.13&gist=24cbdbfe356ffc9d12b75d5960dc2c4c
from collections.abc import Callable
from typing import Any, TypeVar, overload
T = TypeVar('T')
def call(func: Callable[..., T], *args, **kwargs) -> T:
return func(*args, **kwargs)
@overload
def f(x: int) -> int:
...
@overload
def f(x: str) -> str:
...
def f(x):
return x
reveal_type(call(f, 1))
reveal_type(call(f, "a"))
Expected result: I expected that mypy would either:
- evaluate the overload for each call and reveal
intfor the first call,strfor the second; or - raise an error that
Tcan't be resolved. - (or maybe reveal
int | str? or evenAny? would prefer one of the first two though.)
Actual result: mypy silently uses the return type from the first overload, does not raise any error.
main.py:19: note: Revealed type is "int"
main.py:20: note: Revealed type is "int"
Success: no issues found in 1 source file
My actual use case is that I wanted to write:
# requires: boto3, boto3-stubs[logs]
import functools
import boto3
get_client = functools.cache(boto3.Session().client)
reveal_type(get_client('logs'))
# expected: Revealed type is "mypy_boto3_logs.client.CloudWatchLogsClient"
# actual: Revealed type is "mypy_boto3_accessanalyzer.client.AccessAnalyzerClient" if mypy-boto3-accessanalyzer is installed, "Any" otherwise
boto3.Session.client is an overloaded method. As above, mypy didn't raise any error, but just took the first overload.
The relevant declaration of functools.cache: https://github.com/python/typeshed/blob/1d3abc42077c2ea47985363a4fab387357f3aa77/stdlib/functools.pyi#L263
def cache(user_function: Callable[..., _T], /) -> _lru_cache_wrapper[_T]: ...
If the entire Callable is used (with or without ParamSpec), mypy is able to see the overloads. I don't need the other methods of _lru_cache_wrapper, so this workaround works for me:
from collections.abc import Callable
import functools
from typing import Any, TypeVar, cast
import boto3
T = TypeVar('T', bound=Callable[..., Any])
def cache(func: T) -> T:
return cast(T, functools.cache(func))
get_client = cache(boto3.Session().client)
reveal_type(get_client('logs'))
# Revealed type is "mypy_boto3_logs.client.CloudWatchLogsClient"
Thank you for maintaining mypy!
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by running the linked mypy-play reproducer and compare the Callable[..., T] case with the whole-Callable workaround. Read the generic Callable inference path related to functools.cache's _T declaration, then add coverage showing that overloaded calls do not silently use the first overload and instead produce one of the expected outcomes.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- compilers, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100