From cd767c7bb3a9a5ff08ace05acac3f7f5ed515b55 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 17 Nov 2022 19:26:13 +0000 Subject: [PATCH] Fix class objects falling back to metaclass for callback protocol --- mypy/messages.py | 2 +- mypy/subtypes.py | 4 ++++ test-data/unit/check-protocols.test | 17 +++++++++++++++++ test-data/unit/fixtures/type.pyi | 8 ++++++-- 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index b6e34d38e365..2f487972d647 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -1899,7 +1899,7 @@ def report_protocol_problems( missing = get_missing_protocol_members(subtype, supertype, skip=skip) if ( missing - and len(missing) < len(supertype.type.protocol_members) + and (len(missing) < len(supertype.type.protocol_members) or missing == ["__call__"]) and len(missing) <= MAX_ITEMS ): if missing == ["__call__"] and class_obj: diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 2ebecb5d4093..14109587191c 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -1005,6 +1005,10 @@ def named_type(fullname: str) -> Instance: subtype: ProperType | None = mypy.checkmember.type_object_type( left.type, named_type ) + elif member == "__call__" and left.type.is_metaclass(): + # Special case: we want to avoid falling back to metaclass __call__ + # if constructor signature didn't match, this can cause many false negatives. + subtype = None else: subtype = get_proper_type(find_member(member, left, left, class_obj=class_obj)) # Useful for debugging: diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index 35b62defc558..8c4aef9b5be0 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -3526,6 +3526,23 @@ test(B) # OK test(C) # E: Argument 1 to "test" has incompatible type "Type[C]"; expected "P" \ # N: "C" has constructor incompatible with "__call__" of "P" +[case testProtocolClassObjectPureCallback] +from typing import Any, ClassVar, Protocol + +class P(Protocol): + def __call__(self, x: int, y: int) -> Any: ... + +class B: + def __init__(self, x: int, y: int) -> None: ... +class C: + def __init__(self, x: int, y: str) -> None: ... + +def test(arg: P) -> None: ... +test(B) # OK +test(C) # E: Argument 1 to "test" has incompatible type "Type[C]"; expected "P" \ + # N: "C" has constructor incompatible with "__call__" of "P" +[builtins fixtures/type.pyi] + [case testProtocolTypeTypeAttribute] from typing import ClassVar, Protocol, Type diff --git a/test-data/unit/fixtures/type.pyi b/test-data/unit/fixtures/type.pyi index 77feb41ba70b..33dfb5475efa 100644 --- a/test-data/unit/fixtures/type.pyi +++ b/test-data/unit/fixtures/type.pyi @@ -1,8 +1,9 @@ # builtins stub used in type-related test cases. -from typing import Generic, TypeVar, List, Union +from typing import Any, Generic, TypeVar, List, Union -T = TypeVar('T') +T = TypeVar("T") +S = TypeVar("S") class object: def __init__(self) -> None: pass @@ -12,13 +13,16 @@ class list(Generic[T]): pass class type(Generic[T]): __name__: str + def __call__(self, *args: Any, **kwargs: Any) -> Any: pass def __or__(self, other: Union[type, None]) -> type: pass def __ror__(self, other: Union[type, None]) -> type: pass def mro(self) -> List['type']: pass class tuple(Generic[T]): pass +class dict(Generic[T, S]): pass class function: pass class bool: pass class int: pass class str: pass class unicode: pass +class ellipsis: pass