diff --git a/mypy/typeops.py b/mypy/typeops.py index 2eb3b284e729..e92fad0e872c 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -244,15 +244,15 @@ class C(D[E[T]], Generic[T]): ... return expand_type_by_instance(typ, inst_type) -def supported_self_type(typ: ProperType) -> bool: +def supported_self_type(typ: ProperType, allow_callable: bool = True) -> bool: """Is this a supported kind of explicit self-types? - Currently, this means a X or Type[X], where X is an instance or + Currently, this means an X or Type[X], where X is an instance or a type variable with an instance upper bound. """ if isinstance(typ, TypeType): return supported_self_type(typ.item) - if isinstance(typ, CallableType): + if allow_callable and isinstance(typ, CallableType): # Special case: allow class callable instead of Type[...] as cls annotation, # as well as callable self for callback protocols. return True @@ -306,7 +306,11 @@ class B(A): pass self_param_type = get_proper_type(func.arg_types[0]) variables: Sequence[TypeVarLikeType] - if func.variables and supported_self_type(self_param_type): + # Having a def __call__(self: Callable[...], ...) can cause infinite recursion. Although + # this special-casing looks not very principled, there is nothing meaningful we can infer + # from such definition, since it is inherently indefinitely recursive. + allow_callable = func.name is None or not func.name.startswith("__call__ of") + if func.variables and supported_self_type(self_param_type, allow_callable=allow_callable): from mypy.infer import infer_type_arguments if original_type is None: diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index 29abe9cb025b..e49a7a0e2e2f 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -2056,3 +2056,18 @@ reveal_type(C.copy(c)) # N: Revealed type is "__main__.C[builtins.int, builtins B.copy(42) # E: Value of type variable "Self" of "copy" of "B" cannot be "int" C.copy(42) # E: Value of type variable "Self" of "copy" of "B" cannot be "int" [builtins fixtures/tuple.pyi] + +[case testRecursiveSelfTypeCallMethodNoCrash] +from typing import Callable, TypeVar + +T = TypeVar("T") +class Partial: + def __call__(self: Callable[..., T]) -> T: ... + +class Partial2: + def __call__(self: Callable[..., T], x: T) -> T: ... + +p: Partial +reveal_type(p()) # N: Revealed type is "Never" +p2: Partial2 +reveal_type(p2(42)) # N: Revealed type is "builtins.int"