diff --git a/docs/source/kinds_of_types.rst b/docs/source/kinds_of_types.rst
index 263534b59573..facc5da5a64c 100644
--- a/docs/source/kinds_of_types.rst
+++ b/docs/source/kinds_of_types.rst
@@ -241,6 +241,96 @@ 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`` or **Python 3.10** instead if you need those!
+
+.. 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 6fb2e339a5a2..82088f8e8128 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 fe231af0d53d..348811ee9d1f 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