Skip to content

Commit dd086bc

Browse files
hauntsaninjaIvan Levkivskyi
authored and
Ivan Levkivskyi
committed
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 <>
1 parent 524b60e commit dd086bc

File tree

2 files changed

+78
-2
lines changed

2 files changed

+78
-2
lines changed

Diff for: mypy/checker.py

+10-2
Original file line numberDiff line numberDiff line change
@@ -494,8 +494,16 @@ def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None:
494494
# decorator or if the implementation is untyped -- we gave up on the types.
495495
inner_type = get_proper_type(inner_type)
496496
if inner_type is not None and not isinstance(inner_type, AnyType):
497-
assert isinstance(inner_type, CallableType)
498-
impl_type = inner_type
497+
if isinstance(inner_type, CallableType):
498+
impl_type = inner_type
499+
elif isinstance(inner_type, Instance):
500+
inner_call = get_proper_type(
501+
find_member('__call__', inner_type, inner_type, is_operator=True)
502+
)
503+
if isinstance(inner_call, CallableType):
504+
impl_type = inner_call
505+
if impl_type is None:
506+
self.msg.not_callable(inner_type, defn.impl)
499507

500508
is_descriptor_get = defn.info and defn.name == "__get__"
501509
for i, item in enumerate(defn.items):

Diff for: test-data/unit/check-overloading.test

+68
Original file line numberDiff line numberDiff line change
@@ -5337,3 +5337,71 @@ def register(cls: Any) -> Any: return None
53375337
x = register(Foo)
53385338
reveal_type(x) # N: Revealed type is "builtins.int"
53395339
[builtins fixtures/dict.pyi]
5340+
5341+
5342+
[case testOverloadWithObjectDecorator]
5343+
from typing import Any, Callable, Union, overload
5344+
5345+
class A:
5346+
def __call__(self, *arg, **kwargs) -> None: ...
5347+
5348+
def dec_a(f: Callable[..., Any]) -> A:
5349+
return A()
5350+
5351+
@overload
5352+
def f_a(arg: int) -> None: ...
5353+
@overload
5354+
def f_a(arg: str) -> None: ...
5355+
@dec_a
5356+
def f_a(arg): ...
5357+
5358+
class B:
5359+
def __call__(self, arg: Union[int, str]) -> None: ...
5360+
5361+
def dec_b(f: Callable[..., Any]) -> B:
5362+
return B()
5363+
5364+
@overload
5365+
def f_b(arg: int) -> None: ...
5366+
@overload
5367+
def f_b(arg: str) -> None: ...
5368+
@dec_b
5369+
def f_b(arg): ...
5370+
5371+
class C:
5372+
def __call__(self, arg: int) -> None: ...
5373+
5374+
def dec_c(f: Callable[..., Any]) -> C:
5375+
return C()
5376+
5377+
@overload
5378+
def f_c(arg: int) -> None: ...
5379+
@overload
5380+
def f_c(arg: str) -> None: ...
5381+
@dec_c # E: Overloaded function implementation does not accept all possible arguments of signature 2
5382+
def f_c(arg): ...
5383+
[builtins fixtures/dict.pyi]
5384+
5385+
[case testOverloadWithErrorDecorator]
5386+
from typing import Any, Callable, TypeVar, overload
5387+
5388+
def dec_d(f: Callable[..., Any]) -> int: ...
5389+
5390+
@overload
5391+
def f_d(arg: int) -> None: ...
5392+
@overload
5393+
def f_d(arg: str) -> None: ...
5394+
@dec_d # E: "int" not callable
5395+
def f_d(arg): ...
5396+
5397+
Bad = TypeVar('Good') # type: ignore
5398+
5399+
def dec_e(f: Bad) -> Bad: ... # type: ignore
5400+
5401+
@overload
5402+
def f_e(arg: int) -> None: ...
5403+
@overload
5404+
def f_e(arg: str) -> None: ...
5405+
@dec_e # E: Bad? not callable
5406+
def f_e(arg): ...
5407+
[builtins fixtures/dict.pyi]

0 commit comments

Comments
 (0)