Skip to content

Commit 1f67115

Browse files
committed
Tighten metaclass __call__ handling in protocols
1 parent 68233f6 commit 1f67115

File tree

4 files changed

+27
-5
lines changed

4 files changed

+27
-5
lines changed

mypy/constraints.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1066,8 +1066,8 @@ def infer_constraints_from_protocol_members(
10661066
inst, erase_typevars(temp), ignore_pos_arg_names=True
10671067
):
10681068
continue
1069-
# This exception matches the one in subtypes.py, see PR #14121 for context.
1070-
if member == "__call__" and instance.type.is_metaclass():
1069+
# This exception matches the one in typeops.py, see PR #14121 for context.
1070+
if member == "__call__" and instance.type.is_metaclass(precise=True):
10711071
continue
10721072
res.extend(infer_constraints(temp, inst, self.direction))
10731073
if mypy.subtypes.IS_SETTABLE in mypy.subtypes.get_member_flags(member, protocol):

mypy/nodes.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3359,11 +3359,11 @@ def calculate_metaclass_type(self) -> mypy.types.Instance | None:
33593359
return c
33603360
return None
33613361

3362-
def is_metaclass(self) -> bool:
3362+
def is_metaclass(self, *, precise: bool = False) -> bool:
33633363
return (
33643364
self.has_base("builtins.type")
33653365
or self.fullname == "abc.ABCMeta"
3366-
or self.fallback_to_any
3366+
or (self.fallback_to_any and not precise)
33673367
)
33683368

33693369
def has_base(self, fullname: str) -> bool:

mypy/typeops.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1257,7 +1257,7 @@ def named_type(fullname: str) -> Instance:
12571257

12581258
return type_object_type(left.type, named_type)
12591259

1260-
if member == "__call__" and left.type.is_metaclass():
1260+
if member == "__call__" and left.type.is_metaclass(precise=True):
12611261
# Special case: we want to avoid falling back to metaclass __call__
12621262
# if constructor signature didn't match, this can cause many false negatives.
12631263
return None

test-data/unit/check-protocols.test

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4505,3 +4505,25 @@ def bad() -> Proto:
45054505
class Impl:
45064506
@defer
45074507
def f(self) -> int: ...
4508+
4509+
[case testInferCallableProtoWithAnySubclass]
4510+
from typing import Any, Generic, Protocol, TypeVar
4511+
4512+
T = TypeVar("T", covariant=True)
4513+
4514+
Unknown: Any
4515+
class Mock(Unknown):
4516+
def __init__(self, **kwargs: Any) -> None: ...
4517+
def __call__(self, *args: Any, **kwargs: Any) -> Any: ...
4518+
4519+
class Factory(Protocol[T]):
4520+
def __call__(self, **kwargs: Any) -> T: ...
4521+
4522+
4523+
class Test(Generic[T]):
4524+
def __init__(self, f: Factory[T]) -> None:
4525+
...
4526+
4527+
t = Test(Mock())
4528+
reveal_type(t) # N: Revealed type is "__main__.Test[Any]"
4529+
[builtins fixtures/dict.pyi]

0 commit comments

Comments
 (0)