Skip to content

Commit

Permalink
Fix crash on TypeGuard in __call__ (#16516)
Browse files Browse the repository at this point in the history
Fixes #16187

Note there may be some more similar crashes, I don't handle them all
properly, for now I leave a TODO and replace the `assert` with `if`, so
at least we should not crash on an unhandled case.
  • Loading branch information
ilevkivskyi committed Nov 22, 2023
1 parent fc811ae commit 242ad2a
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 10 deletions.
27 changes: 17 additions & 10 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -5670,22 +5670,29 @@ def find_isinstance_check_helper(self, node: Expression) -> tuple[TypeMap, TypeM
if node.arg_kinds[0] != nodes.ARG_POS:
# the first argument might be used as a kwarg
called_type = get_proper_type(self.lookup_type(node.callee))
assert isinstance(called_type, (CallableType, Overloaded))

# TODO: there are some more cases in check_call() to handle.
if isinstance(called_type, Instance):
call = find_member(
"__call__", called_type, called_type, is_operator=True
)
if call is not None:
called_type = get_proper_type(call)

# *assuming* the overloaded function is correct, there's a couple cases:
# 1) The first argument has different names, but is pos-only. We don't
# care about this case, the argument must be passed positionally.
# 2) The first argument allows keyword reference, therefore must be the
# same between overloads.
name = called_type.items[0].arg_names[0]

if name in node.arg_names:
idx = node.arg_names.index(name)
# we want the idx-th variable to be narrowed
expr = collapse_walrus(node.args[idx])
else:
self.fail(message_registry.TYPE_GUARD_POS_ARG_REQUIRED, node)
return {}, {}
if isinstance(called_type, (CallableType, Overloaded)):
name = called_type.items[0].arg_names[0]
if name in node.arg_names:
idx = node.arg_names.index(name)
# we want the idx-th variable to be narrowed
expr = collapse_walrus(node.args[idx])
else:
self.fail(message_registry.TYPE_GUARD_POS_ARG_REQUIRED, node)
return {}, {}
if literal(expr) == LITERAL_TYPE:
# Note: we wrap the target type, so that we can special case later.
# Namely, for isinstance() we use a normal meet, while TypeGuard is
Expand Down
15 changes: 15 additions & 0 deletions test-data/unit/check-typeguard.test
Original file line number Diff line number Diff line change
Expand Up @@ -694,3 +694,18 @@ def foo(x: object) -> TypeGuard[List[str]]: ...
def test(f: A[T]) -> T: ...
reveal_type(test(foo)) # N: Revealed type is "builtins.str"
[builtins fixtures/list.pyi]

[case testNoCrashOnDunderCallTypeGuard]
from typing_extensions import TypeGuard

class A:
def __call__(self, x) -> TypeGuard[int]:
return True

a: A
assert a(x=1)

x: object
assert a(x=x)
reveal_type(x) # N: Revealed type is "builtins.int"
[builtins fixtures/tuple.pyi]

0 comments on commit 242ad2a

Please sign in to comment.