Skip to content

Commit a47f301

Browse files
authored
[PEP 695] Support Annotated[...] in new-style type aliases (#17777)
The rvalue expression isn't semantically analyzed, so we can't rely on the `fullname` attribute to check if there is a reference to `Annotated`. Instead, use a lookup function provided by the caller to determine the fullname. Error reporting in the second argument to `Annotated` is still inconsistent, but this seems lower priority. I'll create a follow-up issue about (or update an existing issue if one exists). Fixes #17751.
1 parent f68f76d commit a47f301

File tree

3 files changed

+52
-13
lines changed

3 files changed

+52
-13
lines changed

mypy/exprtotype.py

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22

33
from __future__ import annotations
44

5+
from typing import Callable
6+
57
from mypy.fastparse import parse_type_string
68
from mypy.nodes import (
79
MISSING_FALLBACK,
810
BytesExpr,
911
CallExpr,
1012
ComplexExpr,
13+
Context,
1114
DictExpr,
1215
EllipsisExpr,
1316
Expression,
@@ -21,6 +24,7 @@
2124
RefExpr,
2225
StarExpr,
2326
StrExpr,
27+
SymbolTableNode,
2428
TupleExpr,
2529
UnaryExpr,
2630
get_member_expr_fullname,
@@ -63,12 +67,16 @@ def expr_to_unanalyzed_type(
6367
allow_new_syntax: bool = False,
6468
_parent: Expression | None = None,
6569
allow_unpack: bool = False,
70+
lookup_qualified: Callable[[str, Context], SymbolTableNode | None] | None = None,
6671
) -> ProperType:
6772
"""Translate an expression to the corresponding type.
6873
6974
The result is not semantically analyzed. It can be UnboundType or TypeList.
7075
Raise TypeTranslationError if the expression cannot represent a type.
7176
77+
If lookup_qualified is not provided, the expression is expected to be semantically
78+
analyzed.
79+
7280
If allow_new_syntax is True, allow all type syntax independent of the target
7381
Python version (used in stubs).
7482
@@ -101,19 +109,26 @@ def expr_to_unanalyzed_type(
101109
else:
102110
args = [expr.index]
103111

104-
if isinstance(expr.base, RefExpr) and expr.base.fullname in ANNOTATED_TYPE_NAMES:
105-
# TODO: this is not the optimal solution as we are basically getting rid
106-
# of the Annotation definition and only returning the type information,
107-
# losing all the annotations.
112+
if isinstance(expr.base, RefExpr):
113+
# Check if the type is Annotated[...]. For this we need the fullname,
114+
# which must be looked up if the expression hasn't been semantically analyzed.
115+
base_fullname = None
116+
if lookup_qualified is not None:
117+
sym = lookup_qualified(base.name, expr)
118+
if sym and sym.node:
119+
base_fullname = sym.node.fullname
120+
else:
121+
base_fullname = expr.base.fullname
108122

109-
return expr_to_unanalyzed_type(args[0], options, allow_new_syntax, expr)
110-
else:
111-
base.args = tuple(
112-
expr_to_unanalyzed_type(
113-
arg, options, allow_new_syntax, expr, allow_unpack=True
114-
)
115-
for arg in args
116-
)
123+
if base_fullname is not None and base_fullname in ANNOTATED_TYPE_NAMES:
124+
# TODO: this is not the optimal solution as we are basically getting rid
125+
# of the Annotation definition and only returning the type information,
126+
# losing all the annotations.
127+
return expr_to_unanalyzed_type(args[0], options, allow_new_syntax, expr)
128+
base.args = tuple(
129+
expr_to_unanalyzed_type(arg, options, allow_new_syntax, expr, allow_unpack=True)
130+
for arg in args
131+
)
117132
if not base.args:
118133
base.empty_tuple_index = True
119134
return base

mypy/semanal.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3749,7 +3749,9 @@ def analyze_alias(
37493749
dynamic = bool(self.function_stack and self.function_stack[-1].is_dynamic())
37503750
global_scope = not self.type and not self.function_stack
37513751
try:
3752-
typ = expr_to_unanalyzed_type(rvalue, self.options, self.is_stub_file)
3752+
typ = expr_to_unanalyzed_type(
3753+
rvalue, self.options, self.is_stub_file, lookup_qualified=self.lookup_qualified
3754+
)
37533755
except TypeTranslationError:
37543756
self.fail(
37553757
"Invalid type alias: expression is not a valid type", rvalue, code=codes.VALID_TYPE

test-data/unit/check-python312.test

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1714,6 +1714,28 @@ type YNested = (1 + (yield from [])) # E: Yield expression cannot be used within
17141714
type ZNested = (1 + (a := 1)) # E: Named expression cannot be used within a type alias
17151715
type KNested = (1 + (await 1)) # E: Await expression cannot be used within a type alias
17161716

1717+
[case testPEP695TypeAliasAndAnnotated]
1718+
# flags: --enable-incomplete-feature=NewGenericSyntax
1719+
from typing_extensions import Annotated, Annotated as _Annotated
1720+
import typing_extensions as t
1721+
1722+
def ann(*args): ...
1723+
1724+
type A = Annotated[int, ann()]
1725+
type B = Annotated[int | str, ann((1, 2))]
1726+
type C = _Annotated[int, ann()]
1727+
type D = t.Annotated[str, ann()]
1728+
1729+
x: A
1730+
y: B
1731+
z: C
1732+
zz: D
1733+
reveal_type(x) # N: Revealed type is "builtins.int"
1734+
reveal_type(y) # N: Revealed type is "Union[builtins.int, builtins.str]"
1735+
reveal_type(z) # N: Revealed type is "builtins.int"
1736+
reveal_type(zz) # N: Revealed type is "builtins.str"
1737+
[builtins fixtures/tuple.pyi]
1738+
17171739
[case testPEP695NestedGenericClass1]
17181740
# flags: --enable-incomplete-feature=NewGenericSyntax
17191741
class C[T]:

0 commit comments

Comments
 (0)