From 2968620c2143e8611513d41ca22b54b9671bfbbc Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Fri, 17 Nov 2023 22:43:37 +0000 Subject: [PATCH] Fix crash on TypeGuard in __call__ --- mypy/checker.py | 27 +++++++++++++++++---------- test-data/unit/check-typeguard.test | 15 +++++++++++++++ 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index b9a9d3affb90..7c6f59fafdc8 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -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 diff --git a/test-data/unit/check-typeguard.test b/test-data/unit/check-typeguard.test index b3b168e5c7c6..c48887bb016a 100644 --- a/test-data/unit/check-typeguard.test +++ b/test-data/unit/check-typeguard.test @@ -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]