diff --git a/mypy/semanal.py b/mypy/semanal.py index 49c24cde0447..f0aceedb9c33 100644 --- 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 index 0aa90d49cfb5..6d5011273f92 100644 --- 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 diff --git a/test-data/unit/check-type-aliases.test b/test-data/unit/check-type-aliases.test index 2701858895d1..a697c32d7c49 100644 --- 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] diff --git a/test-data/unit/lib-stub/typing_extensions.pyi b/test-data/unit/lib-stub/typing_extensions.pyi index 478e5dc1b283..5b32449e71cf 100644 --- a/test-data/unit/lib-stub/typing_extensions.pyi +++ b/test-data/unit/lib-stub/typing_extensions.pyi @@ -24,6 +24,8 @@ Annotated: _SpecialForm = ... ParamSpec: _SpecialForm Concatenate: _SpecialForm +TypeAlias: _SpecialForm + TypeGuard: _SpecialForm # Fallback type for all typed dicts (does not exist at runtime).