Skip to content

Commit df827c9

Browse files
authored
Support PEP 613 (#11305)
Resolves #9404 Co-authored-by: hauntsaninja <>
1 parent 8b106c1 commit df827c9

File tree

4 files changed

+75
-3
lines changed

4 files changed

+75
-3
lines changed

mypy/semanal.py

+10-3
Original file line numberDiff line numberDiff line change
@@ -2554,8 +2554,15 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
25542554
if len(s.lvalues) > 1 or not isinstance(lvalue, NameExpr):
25552555
# First rule: Only simple assignments like Alias = ... create aliases.
25562556
return False
2557-
if s.unanalyzed_type is not None:
2557+
2558+
pep_613 = False
2559+
if s.unanalyzed_type is not None and isinstance(s.unanalyzed_type, UnboundType):
2560+
lookup = self.lookup(s.unanalyzed_type.name, s, suppress_errors=True)
2561+
if lookup and lookup.fullname in ("typing.TypeAlias", "typing_extensions.TypeAlias"):
2562+
pep_613 = True
2563+
if s.unanalyzed_type is not None and not pep_613:
25582564
# Second rule: Explicit type (cls: Type[A] = A) always creates variable, not alias.
2565+
# unless using PEP 613 `cls: TypeAlias = A`
25592566
return False
25602567

25612568
existing = self.current_symbol_table().get(lvalue.name)
@@ -2580,7 +2587,7 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
25802587
return False
25812588

25822589
non_global_scope = self.type or self.is_func_scope()
2583-
if isinstance(s.rvalue, RefExpr) and non_global_scope:
2590+
if isinstance(s.rvalue, RefExpr) and non_global_scope and not pep_613:
25842591
# Fourth rule (special case): Non-subscripted right hand side creates a variable
25852592
# at class and function scopes. For example:
25862593
#
@@ -2593,7 +2600,7 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
25932600
# annotations (see the second rule).
25942601
return False
25952602
rvalue = s.rvalue
2596-
if not self.can_be_type_alias(rvalue):
2603+
if not self.can_be_type_alias(rvalue) and not pep_613:
25972604
return False
25982605

25992606
if existing and not isinstance(existing.node, (PlaceholderNode, TypeAlias)):

mypy/typeanal.py

+2
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,8 @@ def visit_unbound_type_nonoptional(self, t: UnboundType, defining_literal: bool)
258258
return res
259259
elif isinstance(node, TypeInfo):
260260
return self.analyze_type_with_type_info(node, t.args, t)
261+
elif node.fullname in ("typing_extensions.TypeAlias", "typing.TypeAlias"):
262+
return AnyType(TypeOfAny.special_form)
261263
else:
262264
return self.analyze_unbound_type_without_type_info(t, sym, defining_literal)
263265
else: # sym is None

test-data/unit/check-type-aliases.test

+61
Original file line numberDiff line numberDiff line change
@@ -659,3 +659,64 @@ reveal_type(w) # N: Revealed type is "__main__.Out.In"
659659
reveal_type(x) # N: Revealed type is "__main__.Out.In.Inner"
660660
reveal_type(y) # N: Revealed type is "__main__.Out.In.Inner"
661661
reveal_type(z) # N: Revealed type is "__main__.Out.In"
662+
663+
664+
[case testSimplePep613]
665+
from typing_extensions import TypeAlias
666+
x: TypeAlias = str
667+
a: x
668+
reveal_type(a) # N: Revealed type is "builtins.str"
669+
670+
y: TypeAlias = "str"
671+
b: y
672+
reveal_type(b) # N: Revealed type is "builtins.str"
673+
674+
z: TypeAlias = "int | str"
675+
c: z
676+
reveal_type(c) # N: Revealed type is "Union[builtins.int, builtins.str]"
677+
[builtins fixtures/tuple.pyi]
678+
679+
[case testForwardRefPep613]
680+
from typing_extensions import TypeAlias
681+
682+
x: TypeAlias = "MyClass"
683+
a: x
684+
reveal_type(a) # N: Revealed type is "__main__.MyClass"
685+
686+
class MyClass: ...
687+
[builtins fixtures/tuple.pyi]
688+
689+
[case testInvalidPep613]
690+
from typing_extensions import TypeAlias
691+
692+
x: TypeAlias = list(int) # E: Invalid type alias: expression is not a valid type \
693+
# E: Too many arguments for "list"
694+
a: x
695+
[builtins fixtures/tuple.pyi]
696+
697+
[case testFunctionScopePep613]
698+
from typing_extensions import TypeAlias
699+
700+
def f() -> None:
701+
x: TypeAlias = str
702+
a: x
703+
reveal_type(a) # N: Revealed type is "builtins.str"
704+
705+
y: TypeAlias = "str"
706+
b: y
707+
reveal_type(b) # N: Revealed type is "builtins.str"
708+
[builtins fixtures/tuple.pyi]
709+
710+
[case testImportCyclePep613]
711+
# cmd: mypy -m t t2
712+
[file t.py]
713+
MYPY = False
714+
if MYPY:
715+
from t2 import A
716+
x: A
717+
reveal_type(x) # N: Revealed type is "builtins.str"
718+
[file t2.py]
719+
from typing_extensions import TypeAlias
720+
A: TypeAlias = str
721+
[builtins fixtures/bool.pyi]
722+
[out]

test-data/unit/lib-stub/typing_extensions.pyi

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ Annotated: _SpecialForm = ...
2424
ParamSpec: _SpecialForm
2525
Concatenate: _SpecialForm
2626

27+
TypeAlias: _SpecialForm
28+
2729
TypeGuard: _SpecialForm
2830

2931
# Fallback type for all typed dicts (does not exist at runtime).

0 commit comments

Comments
 (0)