Skip to content

Support PEP 613 #11305

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second error is weird but seems unrelated to your changes.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I assumed it was just a fixtures thing

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