@@ -389,3 +389,169 @@ or rewrite the function to be slightly more verbose:
389389 elif b is not None :
390390 return b
391391 return C()
392+
393+
394+ .. _typeis :
395+
396+ TypeIs
397+ ------
398+
399+ Mypy supports TypeIs (:pep: `742 `).
400+
401+ A `TypeIs narrowing function <https://typing.readthedocs.io/en/latest/spec/narrowing.html#typeis >`_
402+ allows you to define custom type checks that can narrow the type of a variable
403+ in `both the if and else <https://docs.python.org/3.13/library/typing.html#typing.TypeIs>_ `
404+ branches of a conditional, similar to how the built-in isinstance() function works.
405+
406+ TypeIs is new in Python 3.13 — for use in older Python versions, use the backport
407+ from `typing_extensions <https://typing-extensions.readthedocs.io/en/latest/>_ `
408+
409+ Consider the following example using TypeIs:
410+
411+ .. code-block :: python
412+
413+ from typing import TypeIs
414+
415+ def is_str (x : object ) -> TypeIs[str ]:
416+ return isinstance (x, str )
417+
418+ def process (x : int | str ) -> None :
419+ if is_str(x):
420+ reveal_type(x) # Revealed type is 'str'
421+ print (x.upper()) # Valid: x is str
422+ else :
423+ reveal_type(x) # Revealed type is 'int'
424+ print (x + 1 ) # Valid: x is int
425+
426+ In this example, the function is_str is a type narrowing function
427+ that returns TypeIs[str]. When used in an if statement, x is narrowed
428+ to str in the if branch and to int in the else branch.
429+
430+ Key points:
431+
432+
433+ - The function must accept at least one positional argument.
434+
435+ - The return type is annotated as ``TypeIs[T] ``, where ``T `` is the type you
436+ want to narrow to.
437+
438+ - The function must return a ``bool `` value.
439+
440+ - In the ``if `` branch (when the function returns ``True ``), the type of the
441+ argument is narrowed to the intersection of its original type and ``T ``.
442+
443+ - In the ``else `` branch (when the function returns ``False ``), the type of
444+ the argument is narrowed to the intersection of its original type and the
445+ complement of ``T ``.
446+
447+
448+ TypeIs vs TypeGuard
449+ ~~~~~~~~~~~~~~~~~~~
450+
451+ While both TypeIs and TypeGuard allow you to define custom type narrowing
452+ functions, they differ in important ways:
453+
454+ - **Type narrowing behavior **: TypeIs narrows the type in both the if and else branches,
455+ whereas TypeGuard narrows only in the if branch.
456+
457+ - **Compatibility requirement **: TypeIs requires that the narrowed type T be
458+ compatible with the input type of the function. TypeGuard does not have this restriction.
459+
460+ - **Type inference **: With TypeIs, the type checker may infer a more precise type by
461+ combining existing type information with T.
462+
463+ Here's an example demonstrating the behavior with TypeGuard:
464+
465+ .. code-block :: python
466+
467+ from typing import TypeGuard, reveal_type
468+
469+ def is_str (x : object ) -> TypeGuard[str ]:
470+ return isinstance (x, str )
471+
472+ def process (x : int | str ) -> None :
473+ if is_str(x):
474+ reveal_type(x) # Revealed type is "builtins.str"
475+ print (x.upper()) # ok: x is str
476+ else :
477+ reveal_type(x) # Revealed type is "Union[builtins.int, builtins.str]"
478+ print (x + 1 ) # ERROR: Unsupported operand types for + ("str" and "int") [operator]
479+
480+ Generic TypeIs
481+ ~~~~~~~~~~~~~~
482+
483+ ``TypeIs `` functions can also work with generic types:
484+
485+ .. code-block :: python
486+
487+ from typing import TypeVar, TypeIs
488+
489+ T = TypeVar(' T' )
490+
491+ def is_two_element_tuple (val : tuple[T, ... ]) -> TypeIs[tuple[T, T]]:
492+ return len (val) == 2
493+
494+ def process (names : tuple[str , ... ]) -> None :
495+ if is_two_element_tuple(names):
496+ reveal_type(names) # Revealed type is 'tuple[str, str]'
497+ else :
498+ reveal_type(names) # Revealed type is 'tuple[str, ...]'
499+
500+
501+ TypeIs with Additional Parameters
502+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
503+ TypeIs functions can accept additional parameters beyond the first.
504+ The type narrowing applies only to the first argument.
505+
506+ .. code-block :: python
507+
508+ from typing import Any, TypeVar, reveal_type, TypeIs
509+
510+ T = TypeVar(' T' )
511+
512+ def is_instance_of (val : Any, typ : type[T]) -> TypeIs[T]:
513+ return isinstance (val, typ)
514+
515+ def process (x : Any) -> None :
516+ if is_instance_of(x, int ):
517+ reveal_type(x) # Revealed type is 'int'
518+ print (x + 1 ) # ok
519+ else :
520+ reveal_type(x) # Revealed type is 'Any'
521+
522+ TypeIs in Methods
523+ ~~~~~~~~~~~~~~~~~
524+
525+ A method can also serve as a ``TypeIs `` function. Note that in instance or
526+ class methods, the type narrowing applies to the second parameter
527+ (after ``self `` or ``cls ``).
528+
529+ .. code-block :: python
530+
531+ class Validator :
532+ def is_valid (self , instance : object ) -> TypeIs[str ]:
533+ return isinstance (instance, str )
534+
535+ def process (self , to_validate : object ) -> None :
536+ if Validator().is_valid(to_validate):
537+ reveal_type(to_validate) # Revealed type is 'str'
538+ print (to_validate.upper()) # ok: to_validate is str
539+
540+
541+ Assignment Expressions with TypeIs
542+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
543+
544+ You can use the assignment expression operator ``:= `` with ``TypeIs `` to create a new variable and narrow its type simultaneously.
545+
546+ .. code-block :: python
547+
548+ from typing import TypeIs, reveal_type
549+
550+ def is_float (x : object ) -> TypeIs[float ]:
551+ return isinstance (x, float )
552+
553+ def main (a : object ) -> None :
554+ if is_float(x := a):
555+ reveal_type(x) # Revealed type is 'float'
556+ # x is narrowed to float in this block
557+ print (x + 1.0 )
0 commit comments