Skip to content

Commit 7b13ff8

Browse files
authored
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 902df03 commit 7b13ff8

File tree

2 files changed

+78
-2
lines changed

2 files changed

+78
-2
lines changed

mypy/checker.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -508,8 +508,16 @@ def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None:
508508
# decorator or if the implementation is untyped -- we gave up on the types.
509509
inner_type = get_proper_type(inner_type)
510510
if inner_type is not None and not isinstance(inner_type, AnyType):
511-
assert isinstance(inner_type, CallableType)
512-
impl_type = inner_type
511+
if isinstance(inner_type, CallableType):
512+
impl_type = inner_type
513+
elif isinstance(inner_type, Instance):
514+
inner_call = get_proper_type(
515+
find_member('__call__', inner_type, inner_type, is_operator=True)
516+
)
517+
if isinstance(inner_call, CallableType):
518+
impl_type = inner_call
519+
if impl_type is None:
520+
self.msg.not_callable(inner_type, defn.impl)
513521

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

test-data/unit/check-overloading.test

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

0 commit comments

Comments
 (0)