@@ -227,3 +227,78 @@ This code fails at runtime, because the narrower returns ``False`` (1 is not a `
227227and the ``else `` branch is taken in ``takes_narrower() ``.
228228If the call ``takes_narrower(1, is_bool) `` was allowed, type checkers would fail to
229229detect this error.
230+ 
231+ In some cases, it may not be possible to narrow a type fully from information
232+ available to the TypeIs function. In such cases, raising an error is the only
233+ possible option, as you have neither enough information to confirm or deny a
234+ type narrowing operation. This is most likely to occur with narrowing of generics.
235+ 
236+ To see why, we can look at the following example::
237+ 
238+     from typing_extensions import TypeVar, TypeIs 
239+     from typing import Generic 
240+ 
241+     X = TypeVar("X", str, int, str | int, covariant=True, default=str | int) 
242+ 
243+     class A(Generic[X]): 
244+         def __init__(self, i: X, /): 
245+             self._i: X = i 
246+ 
247+         @property 
248+         def i(self) -> X: 
249+             return self._i 
250+ 
251+ 
252+     class B(A[X], Generic[X]): 
253+         def __init__(self, i: X, j: X, /): 
254+             super().__init__(i) 
255+             self._j: X = j 
256+ 
257+         @property 
258+         def j(self) -> X: 
259+             return self._j 
260+ 
261+     def possible_problem(x: A) -> TypeIs[A[int]]: 
262+         return isinstance(x.i, int) 
263+ 
264+     def possible_correction(x: A) -> TypeIs[A[int]]: 
265+         if type(x) is A: 
266+             # only narrow cases we know about 
267+             return isinstance(x.i, int) 
268+         raise TypeError( 
269+             f"Refusing to narrow Genenric type {type(x)!r}" 
270+             f"from function that only knows about {A!r}" 
271+         ) 
272+ 
273+ Because it is possible to attempt to narrow B,
274+ but A does not have appropriate information about B
275+ (or any other unknown subclass of A!) it's not possible to safely narrow
276+ in either direction. The general rule for generics is that if you do not know
277+ all the places a generic class is generic and do not enough of them to be
278+ absolutely certain, you cannot return True, and if you do not have a definitive
279+ counter example to the type to be narrowed to you cannot return False.
280+ In practice, if soundness is prioritized over an unsafe narrowing,
281+ not knowing what you don't know is solvable by erroring out
282+ or by making the class to be narrowed final to avoid such a situation.
283+ 
284+ In practice, such correctness is not always neccessary, and may work against
285+ your needs. for example, if you trust that users implementing
286+ the Sequence Protocol are doing so in a way that is safe to iterate over,
287+ the following function can never be fully sound, but fully soundness is not necessarily
288+ easier or better for your use::
289+ 
290+     def useful_unsoundness(s: Sequence[object]) -> TypeIs[Sequence[int]]: 
291+         return all(isinstance(i, int) for i in s) 
292+ 
293+ However, many cases of this sort can be extracted for safe use with an alternative construction
294+ if soundness is of a high priority, and the cost of a copy is acceptable::
295+ 
296+     def safer(s: Sequence[object]) -> Sequence[int]: 
297+         ret = tuple(i for i in s if isinstance(i, int)) 
298+         if len(ret) != len(s): 
299+             raise TypeError 
300+         return ret 
301+ 
302+ Ultimately, TypeIs allows a very large amount of flexibility in handling type-narrowing,
303+ at the cost of more of the issues of evaluating when it is use is safe being left
304+ in the hands of developers.
0 commit comments