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

mypy failed to return correct overloaded func type when decorate func accepts more than one parameter #18167

Open
uriyyo opened this issue Nov 19, 2024 · 2 comments
Labels
bug mypy got something wrong topic-overloads topic-paramspec PEP 612, ParamSpec, Concatenate

Comments

@uriyyo
Copy link
Member

uriyyo commented Nov 19, 2024

Bug Report

mypy failed to return the correct overloaded func type when decorate func accepts more than one parameter.

To Reproduce

from functools import wraps
from typing import overload, Any, TypeVar, Callable, Type

from typing_extensions import ParamSpec, reveal_type


@overload
def foo(a: str, /) -> str:
    pass


@overload
def foo(a: int, /) -> int:
    pass


def foo(a: Any) -> Any:
    return a


P = ParamSpec("P")
T = TypeVar("T")
R = TypeVar("R")


def forbid_return_type(
    func: Callable[P, T],
    tp: Type[Any],
    /,
) -> Callable[P, T]:
    @wraps(func)
    def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
        result = func(*args, **kwargs)

        if isinstance(result, tp):
            raise TypeError(f"Return type {tp} is forbidden")

        return result

    return wrapper


foo_as_int = forbid_return_type(foo, str)

reveal_type(foo_as_int(1))
reveal_type(foo_as_int("1"))

Expected Behavior

note: Revealed type is "builtins.int"
note: Revealed type is "builtins.str"

Actual Behavior

note: Revealed type is "builtins.str"
error: Argument 1 has incompatible type "int"; expected "str"  [arg-type]
note: Revealed type is "builtins.str"

Your Environment

  • Mypy version used: mypy 1.14.0+dev
  • Mypy command-line flags: -
  • Mypy configuration options from mypy.ini (and other config files): -
  • Python version used: 3.8.19

Both codes bellow work as expected:

from functools import wraps
from typing import overload, Any, TypeVar, Callable

from typing_extensions import ParamSpec, reveal_type


@overload
def foo(a: str, /) -> str:
    pass


@overload
def foo(a: int, /) -> int:
    pass


def foo(a: Any) -> Any:
    return a


P = ParamSpec("P")
T = TypeVar("T")
R = TypeVar("R")


def forbid_return_type(
    func: Callable[P, T],
) -> Callable[P, T]:
    @wraps(func)
    def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
        result = func(*args, **kwargs)
        return result

    return wrapper


foo_as_int = forbid_return_type(foo)

reveal_type(foo_as_int(1))
reveal_type(foo_as_int("1"))
from functools import wraps
from typing import Any, TypeVar, Callable, Type, Union

from typing_extensions import ParamSpec, reveal_type


def foo(a: Union[int, str]) -> Union[int, str]:
    return a


P = ParamSpec("P")
T = TypeVar("T")
R = TypeVar("R")


def forbid_return_type(
    func: Callable[P, T],
    tp: Type[Any],
    /,
) -> Callable[P, T]:
    @wraps(func)
    def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
        result = func(*args, **kwargs)

        if isinstance(result, tp):
            raise TypeError(f"Return type {tp} is forbidden")

        return result

    return wrapper


foo_as_int = forbid_return_type(foo, str)

reveal_type(foo_as_int(1))
reveal_type(foo_as_int("1"))

So it's not working only when the function accepts an overloaded function and another argument.

I checked with pyright and it correctly handles code.

  /Users/yuriikarabas/projects/mypy/temp.py:45:13 - information: Type of "foo_as_int(1)" is "int"
  /Users/yuriikarabas/projects/mypy/temp.py:46:13 - information: Type of "foo_as_int("1")" is "str"
0 errors, 0 warnings, 2 informations 
@uriyyo uriyyo added the bug mypy got something wrong label Nov 19, 2024
@brianschubert brianschubert added topic-overloads topic-paramspec PEP 612, ParamSpec, Concatenate labels Nov 19, 2024
@uriyyo
Copy link
Member Author

uriyyo commented Nov 19, 2024

I guess issue is here:

mypy/mypy/checkexpr.py

Lines 1399 to 1401 in 6759dbd

if len(callee_type.arg_types) != 1 or len(args) != 1:
# TODO: can we handle more general cases?
return None

@brianschubert
Copy link
Collaborator

#13540 looks related

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong topic-overloads topic-paramspec PEP 612, ParamSpec, Concatenate
Projects
None yet
Development

No branches or pull requests

2 participants