Skip to content
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

[PEP 695] Support Annotated[...] in new-style type aliases #17777

Merged
merged 2 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
39 changes: 27 additions & 12 deletions mypy/exprtotype.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@

from __future__ import annotations

from typing import Callable

from mypy.fastparse import parse_type_string
from mypy.nodes import (
MISSING_FALLBACK,
BytesExpr,
CallExpr,
ComplexExpr,
Context,
DictExpr,
EllipsisExpr,
Expression,
Expand All @@ -21,6 +24,7 @@
RefExpr,
StarExpr,
StrExpr,
SymbolTableNode,
TupleExpr,
UnaryExpr,
get_member_expr_fullname,
Expand Down Expand Up @@ -63,12 +67,16 @@ def expr_to_unanalyzed_type(
allow_new_syntax: bool = False,
_parent: Expression | None = None,
allow_unpack: bool = False,
lookup_qualified: Callable[[str, Context], SymbolTableNode | None] | None = None,
) -> ProperType:
"""Translate an expression to the corresponding type.
The result is not semantically analyzed. It can be UnboundType or TypeList.
Raise TypeTranslationError if the expression cannot represent a type.
If lookup_qualified is not provided, the expression is expected to be semantically
analyzed.
If allow_new_syntax is True, allow all type syntax independent of the target
Python version (used in stubs).
Expand Down Expand Up @@ -101,19 +109,26 @@ def expr_to_unanalyzed_type(
else:
args = [expr.index]

if isinstance(expr.base, RefExpr) and expr.base.fullname in ANNOTATED_TYPE_NAMES:
# TODO: this is not the optimal solution as we are basically getting rid
# of the Annotation definition and only returning the type information,
# losing all the annotations.
if isinstance(expr.base, RefExpr):
# Check if the type is Annotated[...]. For this we need the fullname,
# which must be looked up if the expression hasn't been semantically analyzed.
base_fullname = None
if lookup_qualified is not None:
sym = lookup_qualified(base.name, expr)
if sym and sym.node:
base_fullname = sym.node.fullname
else:
base_fullname = expr.base.fullname

return expr_to_unanalyzed_type(args[0], options, allow_new_syntax, expr)
else:
base.args = tuple(
expr_to_unanalyzed_type(
arg, options, allow_new_syntax, expr, allow_unpack=True
)
for arg in args
)
if base_fullname is not None and base_fullname in ANNOTATED_TYPE_NAMES:
# TODO: this is not the optimal solution as we are basically getting rid
# of the Annotation definition and only returning the type information,
# losing all the annotations.
return expr_to_unanalyzed_type(args[0], options, allow_new_syntax, expr)
base.args = tuple(
expr_to_unanalyzed_type(arg, options, allow_new_syntax, expr, allow_unpack=True)
for arg in args
)
if not base.args:
base.empty_tuple_index = True
return base
Expand Down
4 changes: 3 additions & 1 deletion mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -3744,7 +3744,9 @@ def analyze_alias(
dynamic = bool(self.function_stack and self.function_stack[-1].is_dynamic())
global_scope = not self.type and not self.function_stack
try:
typ = expr_to_unanalyzed_type(rvalue, self.options, self.is_stub_file)
typ = expr_to_unanalyzed_type(
rvalue, self.options, self.is_stub_file, lookup_qualified=self.lookup_qualified
)
except TypeTranslationError:
self.fail(
"Invalid type alias: expression is not a valid type", rvalue, code=codes.VALID_TYPE
Expand Down
22 changes: 22 additions & 0 deletions test-data/unit/check-python312.test
Original file line number Diff line number Diff line change
Expand Up @@ -1713,3 +1713,25 @@ type XNested = (1 + (yield 1)) # E: Yield expression cannot be used within a ty
type YNested = (1 + (yield from [])) # E: Yield expression cannot be used within a type alias
type ZNested = (1 + (a := 1)) # E: Named expression cannot be used within a type alias
type KNested = (1 + (await 1)) # E: Await expression cannot be used within a type alias

[case testPEP695TypeAliasAndAnnotated]
# flags: --enable-incomplete-feature=NewGenericSyntax
from typing_extensions import Annotated, Annotated as _Annotated
import typing_extensions as t

def ann(*args): ...

type A = Annotated[int, ann()]
type B = Annotated[int | str, ann((1, 2))]
type C = _Annotated[int, ann()]
type D = t.Annotated[str, ann()]

x: A
y: B
z: C
zz: D
reveal_type(x) # N: Revealed type is "builtins.int"
reveal_type(y) # N: Revealed type is "Union[builtins.int, builtins.str]"
reveal_type(z) # N: Revealed type is "builtins.int"
reveal_type(zz) # N: Revealed type is "builtins.str"
[builtins fixtures/tuple.pyi]
Loading