diff --git a/docs/source/kinds_of_types.rst b/docs/source/kinds_of_types.rst index 263534b595731..c7ef63ceec3f6 100644 --- a/docs/source/kinds_of_types.rst +++ b/docs/source/kinds_of_types.rst @@ -241,6 +241,95 @@ more specific type: since the caller may have to use :py:func:`isinstance` before doing anything interesting with the value. +.. _alternative_union_syntax: + +Alternative union syntax +------------------------ + +`PEP 604 `_ introduced an alternative way +for writing union types. Starting with **Python 3.10** it is possible to write +``Union[int, str]`` as ``int | str``. Any of the following options is possible + +.. code-block:: python + + from typing import List + + # Use as Union + t1: int | str # equivalent to Union[int, str] + + # Use as Optional + t2: int | None # equivalent to Optional[int] + + # Use in generics + t3: List[int | str] # equivalent to List[Union[int, str]] + + # Use in type aliases + T4 = int | None + x: T4 + + # Quoted variable annotations + t5: "int | str" + + # Quoted function annotations + def f(t6: "int | str") -> None: ... + + # Type comments + t6 = 42 # type: int | str + +It is possible to use most of these even for earlier versions. However there are some +limitations to be aware of. + +.. _alternative_union_syntax_stub_files: + +Stub files +"""""""""" + +All options are supported, regardless of the Python version the project uses. + +.. _alternative_union_syntax_37: + +Python 3.7 - 3.9 +"""""""""""""""" + +It is necessary to add ``from __future__ import annotations`` to delay the evaluation +of type annotations. Not using it would result in a ``TypeError``. +This does not apply for **type comments**, **quoted function** and **quoted variable** annotations, +as those also work for earlier versions, see :ref:`below `. + +.. warning:: + + Type aliases are **NOT** supported! Those result in a ``TypeError`` regardless + if the evaluation of type annotations is delayed. + + Dynamic evaluation of annotations is **NOT** possible (e.g. ``typing.get_type_hints`` and ``eval``). + See `note PEP 604 `_. Use ``typing.Union`` instead! + +.. code-block:: python + + from __future__ import annotations + + t1: int | None + + # Type aliases + T2 = int | None # TypeError! + +.. _alternative_union_syntax_older_version: + +Older versions +"""""""""""""" + ++------------------------------------------+-----------+-----------+-----------+ +| Python Version | 3.6 | 3.0 - 3.5 | 2.7 | ++==========================================+===========+===========+===========+ +| Type comments | yes | yes | yes | ++------------------------------------------+-----------+-----------+-----------+ +| Quoted function annotations | yes | yes | | ++------------------------------------------+-----------+-----------+-----------+ +| Quoted variable annotations | yes | | | ++------------------------------------------+-----------+-----------+-----------+ +| Everything else | | | | ++------------------------------------------+-----------+-----------+-----------+ + .. _strict_optional: Optional types and the None type diff --git a/mypy/fastparse.py b/mypy/fastparse.py index 6fb2e339a5a2a..82088f8e81288 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -242,7 +242,7 @@ def parse_type_comment(type_comment: str, line=line, override_column=column, assume_str_is_unicode=assume_str_is_unicode, - is_type_comment=True).visit(typ.body) + is_evaluated=False).visit(typ.body) return ignored, converted @@ -1279,14 +1279,14 @@ def __init__(self, line: int = -1, override_column: int = -1, assume_str_is_unicode: bool = True, - is_type_comment: bool = False, + is_evaluated: bool = True, ) -> None: self.errors = errors self.line = line self.override_column = override_column self.node_stack = [] # type: List[AST] self.assume_str_is_unicode = assume_str_is_unicode - self.is_type_comment = is_type_comment + self.is_evaluated = is_evaluated def convert_column(self, column: int) -> int: """Apply column override if defined; otherwise return column. @@ -1436,7 +1436,7 @@ def visit_BinOp(self, n: ast3.BinOp) -> Type: return UnionType([left, right], line=self.line, column=self.convert_column(n.col_offset), - is_evaluated=(not self.is_type_comment), + is_evaluated=self.is_evaluated, uses_pep604_syntax=True) def visit_NameConstant(self, n: NameConstant) -> Type: diff --git a/test-data/unit/check-union-or-syntax.test b/test-data/unit/check-union-or-syntax.test index fe231af0d53d0..348811ee9d1f8 100644 --- a/test-data/unit/check-union-or-syntax.test +++ b/test-data/unit/check-union-or-syntax.test @@ -74,21 +74,41 @@ reveal_type(x) # N: Revealed type is 'builtins.list[Union[builtins.int, builtin [builtins fixtures/list.pyi] -[case testUnionOrSyntaxWithQuotedTypes] -# flags: --python-version 3.10 +[case testUnionOrSyntaxWithQuotedFunctionTypes] +# flags: --python-version 3.4 from typing import Union def f(x: 'Union[int, str, None]') -> 'Union[int, None]': reveal_type(x) # N: Revealed type is 'Union[builtins.int, builtins.str, None]' return 42 reveal_type(f) # N: Revealed type is 'def (x: Union[builtins.int, builtins.str, None]) -> Union[builtins.int, None]' -# flags: --python-version 3.10 def g(x: "int | str | None") -> "int | None": reveal_type(x) # N: Revealed type is 'Union[builtins.int, builtins.str, None]' return 42 reveal_type(g) # N: Revealed type is 'def (x: Union[builtins.int, builtins.str, None]) -> Union[builtins.int, None]' +[case testUnionOrSyntaxWithQuotedVariableTypes] +# flags: --python-version 3.6 +y: "int | str" = 42 +reveal_type(y) # N: Revealed type is 'Union[builtins.int, builtins.str]' + + +[case testUnionOrSyntaxWithTypeAliasWorking] +# flags: --python-version 3.10 +from typing import Union +T = Union[int, str] +x: T +reveal_type(x) # N: Revealed type is 'Union[builtins.int, builtins.str]' + + +[case testUnionOrSyntaxWithTypeAliasNotAllowed] +# flags: --python-version 3.9 +from __future__ import annotations +T = int | str # E: Unsupported left operand type for | ("Type[int]") +[builtins fixtures/tuple.pyi] + + [case testUnionOrSyntaxInComment] # flags: --python-version 3.6 x = 1 # type: int | str