Skip to content

Commit 6d1bcc1

Browse files
authored
Fix class objects falling back to metaclass for callback protocol (#14121)
Fixes #10482 This is not very principled, but should work except people will want to explicitly check some metaclass `__call__`.
1 parent 401798f commit 6d1bcc1

File tree

4 files changed

+28
-3
lines changed

4 files changed

+28
-3
lines changed

mypy/messages.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1899,7 +1899,7 @@ def report_protocol_problems(
18991899
missing = get_missing_protocol_members(subtype, supertype, skip=skip)
19001900
if (
19011901
missing
1902-
and len(missing) < len(supertype.type.protocol_members)
1902+
and (len(missing) < len(supertype.type.protocol_members) or missing == ["__call__"])
19031903
and len(missing) <= MAX_ITEMS
19041904
):
19051905
if missing == ["__call__"] and class_obj:

mypy/subtypes.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,6 +1005,10 @@ def named_type(fullname: str) -> Instance:
10051005
subtype: ProperType | None = mypy.checkmember.type_object_type(
10061006
left.type, named_type
10071007
)
1008+
elif member == "__call__" and left.type.is_metaclass():
1009+
# Special case: we want to avoid falling back to metaclass __call__
1010+
# if constructor signature didn't match, this can cause many false negatives.
1011+
subtype = None
10081012
else:
10091013
subtype = get_proper_type(find_member(member, left, left, class_obj=class_obj))
10101014
# Useful for debugging:

test-data/unit/check-protocols.test

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3526,6 +3526,23 @@ test(B) # OK
35263526
test(C) # E: Argument 1 to "test" has incompatible type "Type[C]"; expected "P" \
35273527
# N: "C" has constructor incompatible with "__call__" of "P"
35283528

3529+
[case testProtocolClassObjectPureCallback]
3530+
from typing import Any, ClassVar, Protocol
3531+
3532+
class P(Protocol):
3533+
def __call__(self, x: int, y: int) -> Any: ...
3534+
3535+
class B:
3536+
def __init__(self, x: int, y: int) -> None: ...
3537+
class C:
3538+
def __init__(self, x: int, y: str) -> None: ...
3539+
3540+
def test(arg: P) -> None: ...
3541+
test(B) # OK
3542+
test(C) # E: Argument 1 to "test" has incompatible type "Type[C]"; expected "P" \
3543+
# N: "C" has constructor incompatible with "__call__" of "P"
3544+
[builtins fixtures/type.pyi]
3545+
35293546
[case testProtocolTypeTypeAttribute]
35303547
from typing import ClassVar, Protocol, Type
35313548

test-data/unit/fixtures/type.pyi

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
# builtins stub used in type-related test cases.
22

3-
from typing import Generic, TypeVar, List, Union
3+
from typing import Any, Generic, TypeVar, List, Union
44

5-
T = TypeVar('T')
5+
T = TypeVar("T")
6+
S = TypeVar("S")
67

78
class object:
89
def __init__(self) -> None: pass
@@ -12,13 +13,16 @@ class list(Generic[T]): pass
1213

1314
class type(Generic[T]):
1415
__name__: str
16+
def __call__(self, *args: Any, **kwargs: Any) -> Any: pass
1517
def __or__(self, other: Union[type, None]) -> type: pass
1618
def __ror__(self, other: Union[type, None]) -> type: pass
1719
def mro(self) -> List['type']: pass
1820

1921
class tuple(Generic[T]): pass
22+
class dict(Generic[T, S]): pass
2023
class function: pass
2124
class bool: pass
2225
class int: pass
2326
class str: pass
2427
class unicode: pass
28+
class ellipsis: pass

0 commit comments

Comments
 (0)