Skip to content

Commit 242ad2a

Browse files
authored
Fix crash on TypeGuard in __call__ (#16516)
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.
1 parent fc811ae commit 242ad2a

File tree

2 files changed

+32
-10
lines changed

2 files changed

+32
-10
lines changed

mypy/checker.py

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5670,22 +5670,29 @@ def find_isinstance_check_helper(self, node: Expression) -> tuple[TypeMap, TypeM
56705670
if node.arg_kinds[0] != nodes.ARG_POS:
56715671
# the first argument might be used as a kwarg
56725672
called_type = get_proper_type(self.lookup_type(node.callee))
5673-
assert isinstance(called_type, (CallableType, Overloaded))
5673+
5674+
# TODO: there are some more cases in check_call() to handle.
5675+
if isinstance(called_type, Instance):
5676+
call = find_member(
5677+
"__call__", called_type, called_type, is_operator=True
5678+
)
5679+
if call is not None:
5680+
called_type = get_proper_type(call)
56745681

56755682
# *assuming* the overloaded function is correct, there's a couple cases:
56765683
# 1) The first argument has different names, but is pos-only. We don't
56775684
# care about this case, the argument must be passed positionally.
56785685
# 2) The first argument allows keyword reference, therefore must be the
56795686
# same between overloads.
5680-
name = called_type.items[0].arg_names[0]
5681-
5682-
if name in node.arg_names:
5683-
idx = node.arg_names.index(name)
5684-
# we want the idx-th variable to be narrowed
5685-
expr = collapse_walrus(node.args[idx])
5686-
else:
5687-
self.fail(message_registry.TYPE_GUARD_POS_ARG_REQUIRED, node)
5688-
return {}, {}
5687+
if isinstance(called_type, (CallableType, Overloaded)):
5688+
name = called_type.items[0].arg_names[0]
5689+
if name in node.arg_names:
5690+
idx = node.arg_names.index(name)
5691+
# we want the idx-th variable to be narrowed
5692+
expr = collapse_walrus(node.args[idx])
5693+
else:
5694+
self.fail(message_registry.TYPE_GUARD_POS_ARG_REQUIRED, node)
5695+
return {}, {}
56895696
if literal(expr) == LITERAL_TYPE:
56905697
# Note: we wrap the target type, so that we can special case later.
56915698
# Namely, for isinstance() we use a normal meet, while TypeGuard is

test-data/unit/check-typeguard.test

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,3 +694,18 @@ def foo(x: object) -> TypeGuard[List[str]]: ...
694694
def test(f: A[T]) -> T: ...
695695
reveal_type(test(foo)) # N: Revealed type is "builtins.str"
696696
[builtins fixtures/list.pyi]
697+
698+
[case testNoCrashOnDunderCallTypeGuard]
699+
from typing_extensions import TypeGuard
700+
701+
class A:
702+
def __call__(self, x) -> TypeGuard[int]:
703+
return True
704+
705+
a: A
706+
assert a(x=1)
707+
708+
x: object
709+
assert a(x=x)
710+
reveal_type(x) # N: Revealed type is "builtins.int"
711+
[builtins fixtures/tuple.pyi]

0 commit comments

Comments
 (0)