From 085b88f1d3560445b35f67f414ab305b830f9f79 Mon Sep 17 00:00:00 2001 From: hauntsaninja <> Date: Sat, 27 Nov 2021 01:05:18 -0800 Subject: [PATCH 1/3] Fix crash with overload and callable object decorators Fixes the crash in #8356, as identified by @pranavrajpal Note that we still don't support the singledispatch in that issue, since we get 'overloaded function has no attribute "register"', but that's much easier to work around than a crash. --- mypy/checker.py | 10 ++++-- test-data/unit/check-overloading.test | 44 +++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 763176a3e6ae..c80eb8b9b83a 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -507,8 +507,14 @@ 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 = find_member('__call__', inner_type, inner_type, is_operator=True) + assert inner_call is not None + impl_type = cast(CallableType, inner_call) + else: + assert False 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..5f34cdd511c3 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -5339,3 +5339,47 @@ 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] From e600b0eca60591323d456a45bf1e513caeb79a73 Mon Sep 17 00:00:00 2001 From: hauntsaninja <> Date: Sat, 27 Nov 2021 01:37:50 -0800 Subject: [PATCH 2/3] handle dumb decorators better --- mypy/checker.py | 10 +++++++--- test-data/unit/check-overloading.test | 9 +++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index c80eb8b9b83a..acc6abfa5fbc 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -510,11 +510,15 @@ def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None: if isinstance(inner_type, CallableType): impl_type = inner_type elif isinstance(inner_type, Instance): - inner_call = find_member('__call__', inner_type, inner_type, is_operator=True) - assert inner_call is not None - impl_type = cast(CallableType, inner_call) + inner_call = get_proper_type( + find_member('__call__', inner_type, inner_type, is_operator=True) + ) + if isinstance(inner_call, CallableType): + impl_type = inner_call else: assert False + 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 5f34cdd511c3..de7261e47492 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -5382,4 +5382,13 @@ def f_c(arg: int) -> None: ... 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): ... + +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): ... [builtins fixtures/dict.pyi] From 3f135c3066106a87818eb9f25533309f6e7c2697 Mon Sep 17 00:00:00 2001 From: hauntsaninja <> Date: Sat, 27 Nov 2021 14:03:11 -0800 Subject: [PATCH 3/3] also fix 9967 --- mypy/checker.py | 2 -- test-data/unit/check-overloading.test | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index acc6abfa5fbc..3c7e9648783c 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -515,8 +515,6 @@ def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None: ) if isinstance(inner_call, CallableType): impl_type = inner_call - else: - assert False if impl_type is None: self.msg.not_callable(inner_type, defn.impl) diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index de7261e47492..8aa29f650270 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -5382,6 +5382,10 @@ def f_c(arg: int) -> None: ... 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: ... @@ -5391,4 +5395,15 @@ def f_d(arg: int) -> None: ... 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]