Skip to content

Commit

Permalink
Fix crash on Callable self in __call__ (#16453)
Browse files Browse the repository at this point in the history
Fixes #16450

The fix is a bit ad-hoc, but OTOH there is nothing meaningful we can
infer in such situation, so it is probably OK.
  • Loading branch information
ilevkivskyi authored and JukkaL committed Nov 15, 2023
1 parent fe79a59 commit f862d3e
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 4 deletions.
12 changes: 8 additions & 4 deletions mypy/typeops.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
15 changes: 15 additions & 0 deletions test-data/unit/check-selftype.test
Original file line number Diff line number Diff line change
Expand Up @@ -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"

0 comments on commit f862d3e

Please sign in to comment.