← Back to index

narrowing_typeis.py

True Positive
False Positive
False Negative
Optional (detected)
Warning or Info
TP: 9
FP: 1
FN: 0
Optional: 0 / 0
1"""
2Tests TypeIs functionality.
3"""
4
5# Specification: https://typing.readthedocs.io/en/latest/spec/narrowing.html#typeis
6
7from collections.abc import Awaitable
8from typing import Any, Callable, Protocol, Self, TypeGuard, TypeVar, assert_type
9from typing_extensions import TypeIs
12T = TypeVar("T")
14def is_two_element_tuple(val: tuple[T, ...]) -> TypeIs[tuple[T, T]]:
15 return len(val) == 2
17def func1(names: tuple[str, ...]):
18 if is_two_element_tuple(names):
19 assert_type(names, tuple[str, str])
20 else:
21 assert_type(names, tuple[str, ...])
24# > The final narrowed type may be narrower than **R**, due to the constraints of the
25# > argument's previously-known type
27def is_awaitable(val: object) -> TypeIs[Awaitable[Any]]:
28 return isinstance(val, Awaitable)
30async def func2(val: int | Awaitable[int]):
31 if is_awaitable(val):
32 # Not `assert_type(val, Awaitable[int])` because a conformant
33 # implementation could allow the possibility that instead it is
34 # `int & Awaitable[Any]`, an awaitable subclass of int.
35 x: int = await val
Unexpected error [invalid-assignment] Object of type `object` is not assignable to `int`
36 return x
37 else:
38 # We can't say much here. The strictly correct answer is
39 # (int | Awaitable[int]) & ~Awaitable[Any], but conformant implementations
40 # may simplify this.
41 # But it should definitely remain assignable to `int | Awaitable[int]`.
42 y: int | Awaitable[int] = val
43 return y
46T_A = TypeVar("T_A", bound="A")
48class A:
49 def tg_1(self, val: object) -> TypeIs[int]:
50 return isinstance(val, int)
52 @classmethod
53 def tg_2(cls, val: object) -> TypeIs[int]:
54 return isinstance(val, int)
56 @staticmethod
57 def tg_3(val: object) -> TypeIs[int]:
58 return isinstance(val, int)
60 def tg4(self, val: object) -> TypeIs[Self]:
61 return isinstance(val, type(self))
63 def tg5(self: T_A, val: object) -> TypeIs[T_A]:
64 return isinstance(val, type(self))
66class B(A):
67 pass
69# > The type narrowing behavior is applied to the first positional argument
70# > passed to the function. The function may accept additional arguments,
71# > but they are not affected by type narrowing.
74def func3() -> None:
75 val1 = object()
76 if A().tg_1(val1):
77 assert_type(val1, int)
79 val2 = object()
80 if A().tg_2(val2):
81 assert_type(val2, int)
83 val3 = object()
84 if A.tg_2(val3):
85 assert_type(val3, int)
87 val4 = object()
88 if A().tg_3(val4):
89 assert_type(val4, int)
91 val5 = object()
92 if A.tg_3(val5):
93 assert_type(val5, int)
95 val6 = object()
96 if B().tg4(val6):
97 assert_type(val6, B)
99 val7 = object()
100 if B().tg4(val7):
101 assert_type(val7, B)
104# > If a type narrowing function
105# > is implemented as an instance method or class method, the first positional
106# > argument maps to the second parameter (after self or cls).
108class C:
109 # Type checker should emit error here.
110 def tg_1(self) -> TypeIs[int]: # E
[invalid-type-guard-definition] `TypeIs` function must have a parameter to narrow
111 return False
113 @classmethod
114 # Type checker should emit error here.
115 def tg_2(cls) -> TypeIs[int]: # E
[invalid-type-guard-definition] `TypeIs` function must have a parameter to narrow
116 return False
118# > ``TypeIs`` is also valid as the return type of a callable, for example
119# > in callback protocols and in the ``Callable`` special form. In these
120# > contexts, it is treated as a subtype of bool. For example, ``Callable[..., TypeIs[int]]``
121# > is assignable to ``Callable[..., bool]``.
124def takes_callable_bool(f: Callable[[object], bool]) -> None:
125 pass
128def takes_callable_str(f: Callable[[object], str]) -> None:
129 pass
132def simple_typeguard(val: object) -> TypeIs[int]:
133 return isinstance(val, int)
136takes_callable_bool(simple_typeguard) # OK
137takes_callable_str(simple_typeguard) # E
[invalid-argument-type] Argument to function `takes_callable_str` is incorrect: Expected `(object, /) -> str`, found `def simple_typeguard(val: object) -> TypeIs[int]`
140class CallableBoolProto(Protocol):
141 def __call__(self, val: object) -> bool: ...
144class CallableStrProto(Protocol):
145 def __call__(self, val: object) -> str: ...
148def takes_callable_bool_proto(f: CallableBoolProto) -> None:
149 pass
152def takes_callable_str_proto(f: CallableStrProto) -> None:
153 pass
156takes_callable_bool_proto(simple_typeguard) # OK
157takes_callable_str_proto(simple_typeguard) # E
[invalid-argument-type] Argument to function `takes_callable_str_proto` is incorrect: Expected `CallableStrProto`, found `def simple_typeguard(val: object) -> TypeIs[int]`
159# TypeIs and TypeGuard are not compatible with each other.
161def takes_typeguard(f: Callable[[object], TypeGuard[int]]) -> None:
162 pass
164def takes_typeis(f: Callable[[object], TypeIs[int]]) -> None:
165 pass
167def is_int_typeis(val: object) -> TypeIs[int]:
168 return isinstance(val, int)
170def is_int_typeguard(val: object) -> TypeGuard[int]:
171 return isinstance(val, int)
173takes_typeguard(is_int_typeguard) # OK
174takes_typeguard(is_int_typeis) # E
[invalid-argument-type] Argument to function `takes_typeguard` is incorrect: Expected `(object, /) -> TypeGuard[int]`, found `def is_int_typeis(val: object) -> TypeIs[int]`
175takes_typeis(is_int_typeguard) # E
[invalid-argument-type] Argument to function `takes_typeis` is incorrect: Expected `(object, /) -> TypeIs[int]`, found `def is_int_typeguard(val: object) -> TypeGuard[int]`
176takes_typeis(is_int_typeis) # OK
179# > Unlike ``TypeGuard``, ``TypeIs`` is invariant in its argument type:
180# > ``TypeIs[B]`` is not a subtype of ``TypeIs[A]``,
181# > even if ``B`` is a subtype of ``A``.
183def takes_int_typeis(f: Callable[[object], TypeIs[int]]) -> None:
184 pass
187def int_typeis(val: object) -> TypeIs[int]:
188 return isinstance(val, int)
191def bool_typeis(val: object) -> TypeIs[bool]:
192 return isinstance(val, bool)
195takes_int_typeis(int_typeis) # OK
196takes_int_typeis(bool_typeis) # E
[invalid-argument-type] Argument to function `takes_int_typeis` is incorrect: Expected `(object, /) -> TypeIs[int]`, found `def bool_typeis(val: object) -> TypeIs[bool]`
198# > It is an error to narrow to a type that is not consistent with the input type
200def bad_typeis(x: int) -> TypeIs[str]: # E
[invalid-type-guard-definition] Narrowed type `str` is not assignable to the declared parameter type `int`
201 return isinstance(x, str)
204def bad_typeis_variance(x: list[object]) -> TypeIs[list[int]]: # E
[invalid-type-guard-definition] Narrowed type `list[int]` is not assignable to the declared parameter type `list[object]`
205 return all(isinstance(x, int) for x in x)