Skip to content

Commit e1ff8aa

Browse files
authored
Consider overlap between instances and callables (#17389)
Fixes #8869 The fix seems straightforward.
1 parent 10f18a8 commit e1ff8aa

File tree

2 files changed

+51
-2
lines changed

2 files changed

+51
-2
lines changed

mypy/meet.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from mypy.maptype import map_instance_to_supertype
88
from mypy.state import state
99
from mypy.subtypes import (
10+
find_member,
1011
is_callable_compatible,
1112
is_equivalent,
1213
is_proper_subtype,
@@ -477,9 +478,22 @@ def _type_object_overlap(left: Type, right: Type) -> bool:
477478
ignore_pos_arg_names=True,
478479
allow_partial_overlap=True,
479480
)
480-
elif isinstance(left, CallableType):
481+
482+
call = None
483+
other = None
484+
if isinstance(left, CallableType) and isinstance(right, Instance):
485+
call = find_member("__call__", right, right, is_operator=True)
486+
other = left
487+
if isinstance(right, CallableType) and isinstance(left, Instance):
488+
call = find_member("__call__", left, left, is_operator=True)
489+
other = right
490+
if isinstance(get_proper_type(call), FunctionLike):
491+
assert call is not None and other is not None
492+
return _is_overlapping_types(call, other)
493+
494+
if isinstance(left, CallableType):
481495
left = left.fallback
482-
elif isinstance(right, CallableType):
496+
if isinstance(right, CallableType):
483497
right = right.fallback
484498

485499
if isinstance(left, LiteralType) and isinstance(right, LiteralType):

test-data/unit/check-statements.test

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2307,3 +2307,38 @@ class Outer:
23072307
class Inner:
23082308
break # E: "break" outside loop
23092309
[builtins fixtures/list.pyi]
2310+
2311+
[case testCallableInstanceOverlapAllowed]
2312+
# flags: --warn-unreachable
2313+
from typing import Any, Callable, List
2314+
2315+
class CAny:
2316+
def __call__(self) -> Any: ...
2317+
class CNone:
2318+
def __call__(self) -> None: ...
2319+
class CWrong:
2320+
def __call__(self, x: int) -> None: ...
2321+
2322+
def describe(func: Callable[[], None]) -> str:
2323+
if isinstance(func, CAny):
2324+
return "CAny"
2325+
elif isinstance(func, CNone):
2326+
return "CNone"
2327+
elif isinstance(func, CWrong):
2328+
return "CWrong" # E: Statement is unreachable
2329+
else:
2330+
return "other"
2331+
2332+
class C(CAny):
2333+
def __call__(self) -> None: ...
2334+
2335+
def f():
2336+
pass
2337+
2338+
describe(CAny())
2339+
describe(C())
2340+
describe(CNone())
2341+
describe(CWrong()) # E: Argument 1 to "describe" has incompatible type "CWrong"; expected "Callable[[], None]" \
2342+
# N: "CWrong.__call__" has type "Callable[[Arg(int, 'x')], None]"
2343+
describe(f)
2344+
[builtins fixtures/isinstancelist.pyi]

0 commit comments

Comments
 (0)