Skip to content
43 changes: 23 additions & 20 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -6183,21 +6183,26 @@ def find_isinstance_check_helper(
attr = try_getting_str_literals(node.args[1], self.lookup_type(node.args[1]))
if literal(expr) == LITERAL_TYPE and attr and len(attr) == 1:
return self.hasattr_type_maps(expr, self.lookup_type(expr), attr[0])
elif isinstance(node.callee, RefExpr):
if node.callee.type_guard is not None or node.callee.type_is is not None:
else:
type_is, type_guard = None, None
called_type = self.lookup_type_or_none(node.callee)
if called_type is not None:
called_type = get_proper_type(called_type)
# TODO: there are some more cases in check_call() to handle.
# If the callee is an instance, try to extract TypeGuard/TypeIs from its __call__ method.
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)
if isinstance(called_type, CallableType):
type_is, type_guard = called_type.type_is, called_type.type_guard

# If the callee is a RefExpr, extract TypeGuard/TypeIs directly.
if isinstance(node.callee, RefExpr):
type_is, type_guard = node.callee.type_is, node.callee.type_guard
if type_guard is not None or type_is is not None:
# TODO: Follow *args, **kwargs
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))

# 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.
Expand All @@ -6210,9 +6215,7 @@ def find_isinstance_check_helper(
# we want the idx-th variable to be narrowed
expr = collapse_walrus(node.args[idx])
else:
kind = (
"guard" if node.callee.type_guard is not None else "narrower"
)
kind = "guard" if type_guard is not None else "narrower"
self.fail(
message_registry.TYPE_GUARD_POS_ARG_REQUIRED.format(kind), node
)
Expand All @@ -6223,15 +6226,15 @@ def find_isinstance_check_helper(
# considered "always right" (i.e. even if the types are not overlapping).
# Also note that a care must be taken to unwrap this back at read places
# where we use this to narrow down declared type.
if node.callee.type_guard is not None:
return {expr: TypeGuardedType(node.callee.type_guard)}, {}
if type_guard is not None:
return {expr: TypeGuardedType(type_guard)}, {}
else:
assert node.callee.type_is is not None
assert type_is is not None
return conditional_types_to_typemaps(
expr,
*self.conditional_types_with_intersection(
self.lookup_type(expr),
[TypeRange(node.callee.type_is, is_upper_bound=False)],
[TypeRange(type_is, is_upper_bound=False)],
expr,
consider_runtime_isinstance=False,
),
Expand Down
47 changes: 47 additions & 0 deletions test-data/unit/check-typeguard.test
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,53 @@ assert a(x=x)
reveal_type(x) # N: Revealed type is "builtins.int"
[builtins fixtures/tuple.pyi]

# https://github.com/python/mypy/issues/19575
[case testNoCrashOnDunderCallTypeGuardTemporaryObject]
from typing_extensions import TypeGuard
class E:
def __init__(self) -> None: ...
def __call__(self, o: object) -> TypeGuard[int]:
return True
x = object()
if E()(x):
reveal_type(x) # N: Revealed type is "builtins.int"
[builtins fixtures/tuple.pyi]

[case testNoCrashOnDunderCallTypeIsTemporaryObject]
from typing_extensions import TypeIs
class E:
def __init__(self) -> None: ...
def __call__(self, o: object) -> TypeIs[int]:
return True
x = object()
if E()(x):
reveal_type(x) # N: Revealed type is "builtins.int"
[builtins fixtures/tuple.pyi]

[case testNoCrashOnDunderCallTypeIsTemporaryObjectGeneric]
from typing import Generic, TypeVar
from typing_extensions import TypeIs
T = TypeVar("T")
class E(Generic[T]):
def __init__(self) -> None: ...
def __call__(self, o: object) -> TypeIs[T]:
return True
x = object()
if E[int]()(x):
reveal_type(x) # N: Revealed type is "builtins.int"
[builtins fixtures/tuple.pyi]

[case testTypeGuardTemporaryObjectWithKeywordArg]
from typing_extensions import TypeGuard
class E:
def __init__(self) -> None: ...
def __call__(self, o: object) -> TypeGuard[int]:
return True
x = object()
if E()(o=x):
reveal_type(x) # N: Revealed type is "builtins.int"
[builtins fixtures/tuple.pyi]

[case testTypeGuardRestrictAwaySingleInvariant]
from typing import List
from typing_extensions import TypeGuard
Expand Down
Loading