Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix class objects falling back to metaclass for callback protocol #14121

Merged
merged 1 commit into from
Nov 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 4 additions & 0 deletions mypy/subtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
17 changes: 17 additions & 0 deletions test-data/unit/check-protocols.test
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
8 changes: 6 additions & 2 deletions test-data/unit/fixtures/type.pyi
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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