← Back to index

narrowing_typeguard.py

True Positive
False Positive
False Negative
Optional (detected)
Warning or Info
TP: 4
FP: 0
FN: 0
Optional: 0 / 0
1"""
2Tests TypeGuard functionality.
3"""
4
5# Specification: https://typing.readthedocs.io/en/latest/spec/narrowing.html#typeguard
6
7from typing import Any, Callable, Protocol, Self, TypeGuard, TypeVar, assert_type
8
9
10T = TypeVar("T")
12def is_two_element_tuple(val: tuple[T, ...]) -> TypeGuard[tuple[T, T]]:
13 return len(val) == 2
15def func1(names: tuple[str, ...]):
16 if is_two_element_tuple(names):
17 assert_type(names, tuple[str, str])
18 else:
19 assert_type(names, tuple[str, ...])
22def is_str_list(val: list[object], allow_empty: bool) -> TypeGuard[list[str]]:
23 if len(val) == 0:
24 return allow_empty
25 return all(isinstance(x, str) for x in val)
27def is_set_of(val: set[Any], type: type[T]) -> TypeGuard[set[T]]:
28 return all(isinstance(x, type) for x in val)
30def func2(val: set[object]):
31 if is_set_of(val, int):
32 assert_type(val, set[int])
33 else:
34 assert_type(val, set[object])
37T_A = TypeVar("T_A", bound="A")
39class A:
40 def tg_1(self, val: object) -> TypeGuard[int]:
41 return isinstance(val, int)
43 @classmethod
44 def tg_2(cls, val: object) -> TypeGuard[int]:
45 return isinstance(val, int)
47 @staticmethod
48 def tg_3(val: object) -> TypeGuard[int]:
49 return isinstance(val, int)
51 def tg4(self, val: object) -> TypeGuard[Self]:
52 return isinstance(val, type(self))
54 def tg5(self: T_A, val: object) -> TypeGuard[T_A]:
55 return isinstance(val, type(self))
57class B(A):
58 pass
60# > Type checkers should assume that type narrowing should be applied to
61# > the expression that is passed as the first positional argument to a
62# > user-defined type guard. If the type guard function accepts more than
63# > one argument, no type narrowing is applied to those additional argument
64# > expressions.
66def func3() -> None:
67 val1 = object()
68 if A().tg_1(val1):
69 assert_type(val1, int)
71 val2 = object()
72 if A().tg_2(val2):
73 assert_type(val2, int)
75 val3 = object()
76 if A.tg_2(val3):
77 assert_type(val3, int)
79 val4 = object()
80 if A().tg_3(val4):
81 assert_type(val4, int)
83 val5 = object()
84 if A.tg_3(val5):
85 assert_type(val5, int)
87 val6 = object()
88 if B().tg4(val6):
89 assert_type(val6, B)
91 val7 = object()
92 if B().tg4(val7):
93 assert_type(val7, B)
96# > If a type guard function is implemented as an instance method or class
97# > method, the first positional argument maps to the second parameter
98# > (after “self” or “cls”).
100class C:
101 # Type checker should emit error here.
102 def tg_1(self) -> TypeGuard[int]: # E
[invalid-type-guard-definition] `TypeGuard` function must have a parameter to narrow
103 return False
105 @classmethod
106 # Type checker should emit error here.
107 def tg_2(cls) -> TypeGuard[int]: # E
[invalid-type-guard-definition] `TypeGuard` function must have a parameter to narrow
108 return False
110# > ``TypeGuard`` is also valid as the return type of a ``Callable`` type. In that
111# > context, it is treated as a subtype of bool. For example, ``Callable[..., TypeGuard[int]]``
112# > is assignable to ``Callable[..., bool]``.
115def takes_callable_bool(f: Callable[[object], bool]) -> None:
116 pass
119def takes_callable_str(f: Callable[[object], str]) -> None:
120 pass
123def simple_typeguard(val: object) -> TypeGuard[int]:
124 return isinstance(val, int)
127takes_callable_bool(simple_typeguard) # OK
128takes_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) -> TypeGuard[int]`
131class CallableBoolProto(Protocol):
132 def __call__(self, val: object) -> bool: ...
135class CallableStrProto(Protocol):
136 def __call__(self, val: object) -> str: ...
139def takes_callable_bool_proto(f: CallableBoolProto) -> None:
140 pass
143def takes_callable_str_proto(f: CallableStrProto) -> None:
144 pass
147takes_callable_bool_proto(simple_typeguard) # OK
148takes_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) -> TypeGuard[int]`
150# > Unlike ``TypeGuard``, ``TypeIs`` is invariant in its argument type:
151# > ``TypeIs[B]`` is not a subtype of ``TypeIs[A]``,
152# > even if ``B`` is a subtype of ``A``.
154def takes_int_typeguard(f: Callable[[object], TypeGuard[int]]) -> None:
155 pass
158def int_typeguard(val: object) -> TypeGuard[int]:
159 return isinstance(val, int)
162def bool_typeguard(val: object) -> TypeGuard[bool]:
163 return isinstance(val, bool)
166takes_int_typeguard(int_typeguard) # OK
167takes_int_typeguard(bool_typeguard) # OK