Skip to content

Commit 1a47b19

Browse files
authored
Fix error for callback protocol matching against callable type object (#15042)
Fixes #15024
1 parent 11166b7 commit 1a47b19

File tree

4 files changed

+59
-19
lines changed

4 files changed

+59
-19
lines changed

mypy/messages.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from typing import Any, Callable, Collection, Iterable, Iterator, List, Sequence, cast
2020
from typing_extensions import Final
2121

22+
import mypy.typeops
2223
from mypy import errorcodes as codes, message_registry
2324
from mypy.erasetype import erase_type
2425
from mypy.errorcodes import ErrorCode
@@ -2711,7 +2712,7 @@ def get_conflict_protocol_types(
27112712
continue
27122713
supertype = find_member(member, right, left)
27132714
assert supertype is not None
2714-
subtype = find_member(member, left, left, class_obj=class_obj)
2715+
subtype = mypy.typeops.get_protocol_member(left, member, class_obj)
27152716
if not subtype:
27162717
continue
27172718
is_compat = is_subtype(subtype, supertype, ignore_pos_arg_names=True)

mypy/subtypes.py

+1-16
Original file line numberDiff line numberDiff line change
@@ -1046,23 +1046,8 @@ def f(self) -> A: ...
10461046
# We always bind self to the subtype. (Similarly to nominal types).
10471047
supertype = get_proper_type(find_member(member, right, left))
10481048
assert supertype is not None
1049-
if member == "__call__" and class_obj:
1050-
# Special case: class objects always have __call__ that is just the constructor.
1051-
# TODO: move this helper function to typeops.py?
1052-
import mypy.checkmember
10531049

1054-
def named_type(fullname: str) -> Instance:
1055-
return Instance(left.type.mro[-1], [])
1056-
1057-
subtype: ProperType | None = mypy.checkmember.type_object_type(
1058-
left.type, named_type
1059-
)
1060-
elif member == "__call__" and left.type.is_metaclass():
1061-
# Special case: we want to avoid falling back to metaclass __call__
1062-
# if constructor signature didn't match, this can cause many false negatives.
1063-
subtype = None
1064-
else:
1065-
subtype = get_proper_type(find_member(member, left, left, class_obj=class_obj))
1050+
subtype = mypy.typeops.get_protocol_member(left, member, class_obj)
10661051
# Useful for debugging:
10671052
# print(member, 'of', left, 'has type', subtype)
10681053
# print(member, 'of', right, 'has type', supertype)

mypy/typeops.py

+20
Original file line numberDiff line numberDiff line change
@@ -1050,3 +1050,23 @@ def fixup_partial_type(typ: Type) -> Type:
10501050
return UnionType.make_union([AnyType(TypeOfAny.unannotated), NoneType()])
10511051
else:
10521052
return Instance(typ.type, [AnyType(TypeOfAny.unannotated)] * len(typ.type.type_vars))
1053+
1054+
1055+
def get_protocol_member(left: Instance, member: str, class_obj: bool) -> ProperType | None:
1056+
if member == "__call__" and class_obj:
1057+
# Special case: class objects always have __call__ that is just the constructor.
1058+
from mypy.checkmember import type_object_type
1059+
1060+
def named_type(fullname: str) -> Instance:
1061+
return Instance(left.type.mro[-1], [])
1062+
1063+
return type_object_type(left.type, named_type)
1064+
1065+
if member == "__call__" and left.type.is_metaclass():
1066+
# Special case: we want to avoid falling back to metaclass __call__
1067+
# if constructor signature didn't match, this can cause many false negatives.
1068+
return None
1069+
1070+
from mypy.subtypes import find_member
1071+
1072+
return get_proper_type(find_member(member, left, left, class_obj=class_obj))

test-data/unit/check-protocols.test

+36-2
Original file line numberDiff line numberDiff line change
@@ -3522,7 +3522,12 @@ class C:
35223522
def test(arg: P) -> None: ...
35233523
test(B) # OK
35243524
test(C) # E: Argument 1 to "test" has incompatible type "Type[C]"; expected "P" \
3525-
# N: "C" has constructor incompatible with "__call__" of "P"
3525+
# N: "C" has constructor incompatible with "__call__" of "P" \
3526+
# N: Following member(s) of "C" have conflicts: \
3527+
# N: Expected: \
3528+
# N: def __call__(x: int, y: int) -> Any \
3529+
# N: Got: \
3530+
# N: def __init__(x: int, y: str) -> C
35263531

35273532
[case testProtocolClassObjectPureCallback]
35283533
from typing import Any, ClassVar, Protocol
@@ -3538,7 +3543,36 @@ class C:
35383543
def test(arg: P) -> None: ...
35393544
test(B) # OK
35403545
test(C) # E: Argument 1 to "test" has incompatible type "Type[C]"; expected "P" \
3541-
# N: "C" has constructor incompatible with "__call__" of "P"
3546+
# N: "C" has constructor incompatible with "__call__" of "P" \
3547+
# N: Following member(s) of "C" have conflicts: \
3548+
# N: Expected: \
3549+
# N: def __call__(x: int, y: int) -> Any \
3550+
# N: Got: \
3551+
# N: def __init__(x: int, y: str) -> C
3552+
[builtins fixtures/type.pyi]
3553+
3554+
[case testProtocolClassObjectCallableError]
3555+
from typing import Protocol, Any, Callable
3556+
3557+
class P(Protocol):
3558+
def __call__(self, app: int) -> Callable[[str], None]:
3559+
...
3560+
3561+
class C:
3562+
def __init__(self, app: str) -> None:
3563+
pass
3564+
3565+
def __call__(self, el: str) -> None:
3566+
return None
3567+
3568+
p: P = C # E: Incompatible types in assignment (expression has type "Type[C]", variable has type "P") \
3569+
# N: Following member(s) of "C" have conflicts: \
3570+
# N: Expected: \
3571+
# N: def __call__(app: int) -> Callable[[str], None] \
3572+
# N: Got: \
3573+
# N: def __init__(app: str) -> C \
3574+
# N: "P.__call__" has type "Callable[[Arg(int, 'app')], Callable[[str], None]]"
3575+
35423576
[builtins fixtures/type.pyi]
35433577

35443578
[case testProtocolTypeTypeAttribute]

0 commit comments

Comments
 (0)