Repository: python/mypy

Issue:
Support PEP 613
## 🚀 Feature

Support type aliasing as in [PEP 613](https://www.python.org/dev/peps/pep-0613/)

## Pitch

Conclusion in this comment of a closed issue: https://github.com/python/mypy/issues/9402#issuecomment-686165936



Discussion:
One small note here is that we should be mindful of the intersection with `from __future__ import annotations` and PEP 585. That is, it would be nice to support the following on Python 3.7:
```
from __future__ import annotations
x: TypeAlias = "list[int]"
```
Any update or merge request to work on?
For anyone interested in seeing this feature, I suspect it's going to be relatively easy to implement since it won't affect any type-checking logic: it's just additions to the parser and semantic analyzer to recognize type aliases in a new context. Contributions would be welcome!

Resolving patch:
diff --git a/mypy/semanal.py b/mypy/semanal.py
--- a/mypy/semanal.py
+++ b/mypy/semanal.py
@@ -2554,8 +2554,15 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
         if len(s.lvalues) > 1 or not isinstance(lvalue, NameExpr):
             # First rule: Only simple assignments like Alias = ... create aliases.
             return False
-        if s.unanalyzed_type is not None:
+
+        pep_613 = False
+        if s.unanalyzed_type is not None and isinstance(s.unanalyzed_type, UnboundType):
+            lookup = self.lookup(s.unanalyzed_type.name, s, suppress_errors=True)
+            if lookup and lookup.fullname in ("typing.TypeAlias", "typing_extensions.TypeAlias"):
+                pep_613 = True
+        if s.unanalyzed_type is not None and not pep_613:
             # Second rule: Explicit type (cls: Type[A] = A) always creates variable, not alias.
+            # unless using PEP 613 `cls: TypeAlias = A`
             return False
 
         existing = self.current_symbol_table().get(lvalue.name)
@@ -2580,7 +2587,7 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
             return False
 
         non_global_scope = self.type or self.is_func_scope()
-        if isinstance(s.rvalue, RefExpr) and non_global_scope:
+        if isinstance(s.rvalue, RefExpr) and non_global_scope and not pep_613:
             # Fourth rule (special case): Non-subscripted right hand side creates a variable
             # at class and function scopes. For example:
             #
@@ -2593,7 +2600,7 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
             # annotations (see the second rule).
             return False
         rvalue = s.rvalue
-        if not self.can_be_type_alias(rvalue):
+        if not self.can_be_type_alias(rvalue) and not pep_613:
             return False
 
         if existing and not isinstance(existing.node, (PlaceholderNode, TypeAlias)):
diff --git a/mypy/typeanal.py b/mypy/typeanal.py
--- a/mypy/typeanal.py
+++ b/mypy/typeanal.py
@@ -258,6 +258,8 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool)
                 return res
             elif isinstance(node, TypeInfo):
                 return self.analyze_type_with_type_info(node, t.args, t)
+            elif node.fullname in ("typing_extensions.TypeAlias", "typing.TypeAlias"):
+                return AnyType(TypeOfAny.special_form)
             else:
                 return self.analyze_unbound_type_without_type_info(t, sym, defining_literal)
         else:  # sym is None


Test patch:
diff --git a/test-data/unit/check-type-aliases.test b/test-data/unit/check-type-aliases.test
--- a/test-data/unit/check-type-aliases.test
+++ b/test-data/unit/check-type-aliases.test
@@ -659,3 +659,64 @@ reveal_type(w)  # N: Revealed type is "__main__.Out.In"
 reveal_type(x)  # N: Revealed type is "__main__.Out.In.Inner"
 reveal_type(y)  # N: Revealed type is "__main__.Out.In.Inner"
 reveal_type(z)  # N: Revealed type is "__main__.Out.In"
+
+
+[case testSimplePep613]
+from typing_extensions import TypeAlias
+x: TypeAlias = str
+a: x
+reveal_type(a)  # N: Revealed type is "builtins.str"
+
+y: TypeAlias = "str"
+b: y
+reveal_type(b)  # N: Revealed type is "builtins.str"
+
+z: TypeAlias = "int | str"
+c: z
+reveal_type(c)  # N: Revealed type is "Union[builtins.int, builtins.str]"
+[builtins fixtures/tuple.pyi]
+
+[case testForwardRefPep613]
+from typing_extensions import TypeAlias
+
+x: TypeAlias = "MyClass"
+a: x
+reveal_type(a)  # N: Revealed type is "__main__.MyClass"
+
+class MyClass: ...
+[builtins fixtures/tuple.pyi]
+
+[case testInvalidPep613]
+from typing_extensions import TypeAlias
+
+x: TypeAlias = list(int)  # E: Invalid type alias: expression is not a valid type \
+                          # E: Too many arguments for "list"
+a: x
+[builtins fixtures/tuple.pyi]
+
+[case testFunctionScopePep613]
+from typing_extensions import TypeAlias
+
+def f() -> None:
+    x: TypeAlias = str
+    a: x
+    reveal_type(a)  # N: Revealed type is "builtins.str"
+
+    y: TypeAlias = "str"
+    b: y
+    reveal_type(b)  # N: Revealed type is "builtins.str"
+[builtins fixtures/tuple.pyi]
+
+[case testImportCyclePep613]
+# cmd: mypy -m t t2
+[file t.py]
+MYPY = False
+if MYPY:
+    from t2 import A
+x: A
+reveal_type(x)  # N: Revealed type is "builtins.str"
+[file t2.py]
+from typing_extensions import TypeAlias
+A: TypeAlias = str
+[builtins fixtures/bool.pyi]
+[out]