From e1ff8aa30f291ec1613bc9893528067b269309bc Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Wed, 19 Jun 2024 18:49:41 +0100 Subject: [PATCH] Consider overlap between instances and callables (#17389) Fixes https://github.com/python/mypy/issues/8869 The fix seems straightforward. --- mypy/meet.py | 18 ++++++++++++-- test-data/unit/check-statements.test | 35 ++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/mypy/meet.py b/mypy/meet.py index 48e5dfaa18ee..401200a11cc1 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -7,6 +7,7 @@ from mypy.maptype import map_instance_to_supertype from mypy.state import state from mypy.subtypes import ( + find_member, is_callable_compatible, is_equivalent, is_proper_subtype, @@ -477,9 +478,22 @@ def _type_object_overlap(left: Type, right: Type) -> bool: ignore_pos_arg_names=True, allow_partial_overlap=True, ) - elif isinstance(left, CallableType): + + call = None + other = None + if isinstance(left, CallableType) and isinstance(right, Instance): + call = find_member("__call__", right, right, is_operator=True) + other = left + if isinstance(right, CallableType) and isinstance(left, Instance): + call = find_member("__call__", left, left, is_operator=True) + other = right + if isinstance(get_proper_type(call), FunctionLike): + assert call is not None and other is not None + return _is_overlapping_types(call, other) + + if isinstance(left, CallableType): left = left.fallback - elif isinstance(right, CallableType): + if isinstance(right, CallableType): right = right.fallback if isinstance(left, LiteralType) and isinstance(right, LiteralType): diff --git a/test-data/unit/check-statements.test b/test-data/unit/check-statements.test index 71cc80719779..34df5a8ab336 100644 --- a/test-data/unit/check-statements.test +++ b/test-data/unit/check-statements.test @@ -2307,3 +2307,38 @@ class Outer: class Inner: break # E: "break" outside loop [builtins fixtures/list.pyi] + +[case testCallableInstanceOverlapAllowed] +# flags: --warn-unreachable +from typing import Any, Callable, List + +class CAny: + def __call__(self) -> Any: ... +class CNone: + def __call__(self) -> None: ... +class CWrong: + def __call__(self, x: int) -> None: ... + +def describe(func: Callable[[], None]) -> str: + if isinstance(func, CAny): + return "CAny" + elif isinstance(func, CNone): + return "CNone" + elif isinstance(func, CWrong): + return "CWrong" # E: Statement is unreachable + else: + return "other" + +class C(CAny): + def __call__(self) -> None: ... + +def f(): + pass + +describe(CAny()) +describe(C()) +describe(CNone()) +describe(CWrong()) # E: Argument 1 to "describe" has incompatible type "CWrong"; expected "Callable[[], None]" \ + # N: "CWrong.__call__" has type "Callable[[Arg(int, 'x')], None]" +describe(f) +[builtins fixtures/isinstancelist.pyi]