Skip to content

Commit

Permalink
Support PEP 613
Browse files Browse the repository at this point in the history
Resolves python#9404
  • Loading branch information
hauntsaninja committed Oct 10, 2021
1 parent 82f767a commit 7fb8bd8
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 3 deletions.
13 changes: 10 additions & 3 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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:
#
Expand All @@ -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)):
Expand Down
2 changes: 2 additions & 0 deletions mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
61 changes: 61 additions & 0 deletions test-data/unit/check-type-aliases.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]
2 changes: 2 additions & 0 deletions test-data/unit/lib-stub/typing_extensions.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down

0 comments on commit 7fb8bd8

Please sign in to comment.