From 713df5ea34f8f92941511320aa921bb0c453dceb Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Sat, 27 Nov 2021 18:46:11 -0800 Subject: [PATCH] Fix crash with overload and callable object decorators (#11630) Fixes #8356, as identified by @pranavrajpal Fixes #9112 Fixes #9967 Note that we still don't fully support the singledispatch pattern in #8356, since we get 'overloaded function has no attribute "register"', but that's much easier to work around than a crash. Co-authored-by: hauntsaninja <> --- mypy/checker.py | 12 ++++- test-data/unit/check-overloading.test | 68 +++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 7a991acf9376..6505100cec6f 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -508,8 +508,16 @@ def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None: # decorator or if the implementation is untyped -- we gave up on the types. inner_type = get_proper_type(inner_type) if inner_type is not None and not isinstance(inner_type, AnyType): - assert isinstance(inner_type, CallableType) - impl_type = inner_type + if isinstance(inner_type, CallableType): + impl_type = inner_type + elif isinstance(inner_type, Instance): + inner_call = get_proper_type( + find_member('__call__', inner_type, inner_type, is_operator=True) + ) + if isinstance(inner_call, CallableType): + impl_type = inner_call + if impl_type is None: + self.msg.not_callable(inner_type, defn.impl) is_descriptor_get = defn.info and defn.name == "__get__" for i, item in enumerate(defn.items): diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index bf7acdc1cd51..8aa29f650270 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -5339,3 +5339,71 @@ def register(cls: Any) -> Any: return None x = register(Foo) reveal_type(x) # N: Revealed type is "builtins.int" [builtins fixtures/dict.pyi] + + +[case testOverloadWithObjectDecorator] +from typing import Any, Callable, Union, overload + +class A: + def __call__(self, *arg, **kwargs) -> None: ... + +def dec_a(f: Callable[..., Any]) -> A: + return A() + +@overload +def f_a(arg: int) -> None: ... +@overload +def f_a(arg: str) -> None: ... +@dec_a +def f_a(arg): ... + +class B: + def __call__(self, arg: Union[int, str]) -> None: ... + +def dec_b(f: Callable[..., Any]) -> B: + return B() + +@overload +def f_b(arg: int) -> None: ... +@overload +def f_b(arg: str) -> None: ... +@dec_b +def f_b(arg): ... + +class C: + def __call__(self, arg: int) -> None: ... + +def dec_c(f: Callable[..., Any]) -> C: + return C() + +@overload +def f_c(arg: int) -> None: ... +@overload +def f_c(arg: str) -> None: ... +@dec_c # E: Overloaded function implementation does not accept all possible arguments of signature 2 +def f_c(arg): ... +[builtins fixtures/dict.pyi] + +[case testOverloadWithErrorDecorator] +from typing import Any, Callable, TypeVar, overload + +def dec_d(f: Callable[..., Any]) -> int: ... + +@overload +def f_d(arg: int) -> None: ... +@overload +def f_d(arg: str) -> None: ... +@dec_d # E: "int" not callable +def f_d(arg): ... + +Bad = TypeVar('Good') # type: ignore + +def dec_e(f: Bad) -> Bad: ... # type: ignore + +@overload +def f_e(arg: int) -> None: ... +@overload +def f_e(arg: str) -> None: ... +@dec_e # E: Bad? not callable +def f_e(arg): ... +[builtins fixtures/dict.pyi]