Skip to content

Commit c86480c

Browse files
ilevkivskyiJukkaL
authored andcommitted
Tighten metaclass __call__ handling in protocols (#19191)
Fixes #19184 This fixes an (edge-case) regression introduced in 1.16. Fix is straightforward, only ignore `__call__` if it comes from an _actual_ metaclass.
1 parent cb3c6ec commit c86480c

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
@@ -3371,11 +3371,11 @@ def calculate_metaclass_type(self) -> mypy.types.Instance | None:
33713371
return c
33723372
return None
33733373

3374-
def is_metaclass(self) -> bool:
3374+
def is_metaclass(self, *, precise: bool = False) -> bool:
33753375
return (
33763376
self.has_base("builtins.type")
33773377
or self.fullname == "abc.ABCMeta"
3378-
or self.fallback_to_any
3378+
or (self.fallback_to_any and not precise)
33793379
)
33803380

33813381
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
@@ -4460,3 +4460,25 @@ f2(a4) # E: Argument 1 to "f2" has incompatible type "A4"; expected "P2" \
44604460
# N: foo: expected "B1", got "str" \
44614461
# N: foo: expected setter type "C1", got "str"
44624462
[builtins fixtures/property.pyi]
4463+
4464+
[case testInferCallableProtoWithAnySubclass]
4465+
from typing import Any, Generic, Protocol, TypeVar
4466+
4467+
T = TypeVar("T", covariant=True)
4468+
4469+
Unknown: Any
4470+
class Mock(Unknown):
4471+
def __init__(self, **kwargs: Any) -> None: ...
4472+
def __call__(self, *args: Any, **kwargs: Any) -> Any: ...
4473+
4474+
class Factory(Protocol[T]):
4475+
def __call__(self, **kwargs: Any) -> T: ...
4476+
4477+
4478+
class Test(Generic[T]):
4479+
def __init__(self, f: Factory[T]) -> None:
4480+
...
4481+
4482+
t = Test(Mock())
4483+
reveal_type(t) # N: Revealed type is "__main__.Test[Any]"
4484+
[builtins fixtures/dict.pyi]

0 commit comments

Comments
 (0)