@@ -195,15 +195,91 @@ This behavior can be seen in the following example::
195195 else:
196196 reveal_type(x) # Unrelated
197197
198+ There are also cases beyond just mutability. In some cases, it may not be
199+ possible to narrow a type fully from information available to the ``TypeIs ``
200+ function. In such cases, raising an error is the only possible option, as you
201+ have neither enough information to confirm or deny a type narrowing operation.
202+ (Note: returning false (denying) results in unsafe negative narrowing in this
203+ case) This is most likely to occur with narrowing of generics.
204+
205+ To see why, we can look at the following example::
206+
207+ from typing_extensions import TypeVar, TypeIs
208+ from typing import Generic
209+
210+ X = TypeVar("X", str, int, str | int, covariant=True, default=str | int)
211+
212+ class A(Generic[X]):
213+ def __init__(self, i: X, /):
214+ self._i: X = i
215+
216+ @property
217+ def i(self) -> X:
218+ return self._i
219+
220+
221+ class B(A[X], Generic[X]):
222+ def __init__(self, i: X, j: X, /):
223+ super().__init__(i)
224+ self._j: X = j
225+
226+ @property
227+ def j(self) -> X:
228+ return self._j
229+
230+ def possible_problem(x: A) -> TypeIs[A[int]]:
231+ return isinstance(x.i, int)
232+
233+ def possible_correction(x: A) -> TypeIs[A[int]]:
234+ if type(x) is A:
235+ # only narrow cases we know about
236+ return isinstance(x.i, int)
237+ raise TypeError(
238+ f"Refusing to narrow Genenric type {type(x)!r}"
239+ f"from function that only knows about {A!r}"
240+ )
241+
242+ Because it is possible to attempt to narrow B,
243+ but A does not have appropriate information about B
244+ (or any other unknown subclass of A!) it's not possible to safely narrow
245+ in either direction. The general rule for generics is that if you do not know
246+ all the places a generic class is generic and do not check enough of them to be
247+ absolutely certain, you cannot return True, and if you do not have a definitive
248+ counter example to the type to be narrowed to you cannot return False.
249+ In practice, if soundness is prioritized over an unsafe narrowing,
250+ not knowing what you don't know is solvable by either
251+ erroring out when neither return option is safe, or by making the class to be
252+ narrowed final to avoid such a situation.
198253
199254Safety and soundness
200255--------------------
201256
202257While type narrowing is important for typing real-world Python code, many
203- forms of type narrowing are unsafe in the presence of mutability . Type checkers
258+ forms of type narrowing are unsafe. Type checkers
204259attempt to limit type narrowing in a way that minimizes unsafety while remaining
205260useful, but not all safety violations can be detected.
206261
262+ One example of this tradeoff building off of TypeIs
263+
264+ If you trust that users implementing the Sequence Protocol are doing so in a
265+ way that is safe to iterate over, and will not be mutated for the duration
266+ you are relying on it; then while the following function can never be fully
267+ sound, full soundness is not necessarily easier or better for your use::
268+
269+ def useful_unsoundness(s: Sequence[object]) -> TypeIs[Sequence[int]]:
270+ return all(isinstance(i, int) for i in s)
271+
272+ However, many cases of this sort can be extracted for safe use with an
273+ alternative construction if soundness is of a high priority,
274+ and the cost of a copy is acceptable::
275+
276+ def safer(s: Sequence[object]) -> Sequence[int]:
277+ ret = tuple(i for i in s if isinstance(i, int))
278+ if len(ret) != len(s):
279+ raise TypeError
280+ return ret
281+
282+
207283``isinstance() `` and ``issubclass() ``
208284~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
209285
0 commit comments