Skip to content

Commit f3bdf5c

Browse files
authored
Support fancy new syntax for variadic types (#16242)
This is the last significant thing I am aware of that is needed for PEP 646 support. After this and other currently open PRs are merged, I will make an additional pass grepping for usual suspects and verifying we didn't miss anything. Then we can flip the switch and announce this as supported.
1 parent 4a9e6e6 commit f3bdf5c

File tree

8 files changed

+111
-35
lines changed

8 files changed

+111
-35
lines changed

mypy/exprtotype.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,10 @@ def expr_to_unanalyzed_type(
103103
return expr_to_unanalyzed_type(args[0], options, allow_new_syntax, expr)
104104
else:
105105
base.args = tuple(
106-
expr_to_unanalyzed_type(arg, options, allow_new_syntax, expr) for arg in args
106+
expr_to_unanalyzed_type(
107+
arg, options, allow_new_syntax, expr, allow_unpack=True
108+
)
109+
for arg in args
107110
)
108111
if not base.args:
109112
base.empty_tuple_index = True

mypy/fastparse.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1762,7 +1762,6 @@ def __init__(
17621762
self.override_column = override_column
17631763
self.node_stack: list[AST] = []
17641764
self.is_evaluated = is_evaluated
1765-
self.allow_unpack = False
17661765

17671766
def convert_column(self, column: int) -> int:
17681767
"""Apply column override if defined; otherwise return column.
@@ -2039,19 +2038,14 @@ def visit_Attribute(self, n: Attribute) -> Type:
20392038
else:
20402039
return self.invalid_type(n)
20412040

2042-
# Used for Callable[[X *Ys, Z], R]
2041+
# Used for Callable[[X *Ys, Z], R] etc.
20432042
def visit_Starred(self, n: ast3.Starred) -> Type:
20442043
return UnpackType(self.visit(n.value), from_star_syntax=True)
20452044

20462045
# List(expr* elts, expr_context ctx)
20472046
def visit_List(self, n: ast3.List) -> Type:
20482047
assert isinstance(n.ctx, ast3.Load)
2049-
old_allow_unpack = self.allow_unpack
2050-
# We specifically only allow starred expressions in a list to avoid
2051-
# confusing errors for top-level unpacks (e.g. in base classes).
2052-
self.allow_unpack = True
20532048
result = self.translate_argument_list(n.elts)
2054-
self.allow_unpack = old_allow_unpack
20552049
return result
20562050

20572051

mypy/messages.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2516,6 +2516,8 @@ def format_literal_value(typ: LiteralType) -> str:
25162516
# There are type arguments. Convert the arguments to strings.
25172517
return f"{base_str}[{format_list(itype.args)}]"
25182518
elif isinstance(typ, UnpackType):
2519+
if options.use_star_unpack():
2520+
return f"*{format(typ.type)}"
25192521
return f"Unpack[{format(typ.type)}]"
25202522
elif isinstance(typ, TypeVarType):
25212523
# This is similar to non-generic instance types.

mypy/options.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,9 @@ def use_or_syntax(self) -> bool:
385385
return not self.force_union_syntax
386386
return False
387387

388+
def use_star_unpack(self) -> bool:
389+
return self.python_version >= (3, 11)
390+
388391
# To avoid breaking plugin compatibility, keep providing new_semantic_analyzer
389392
@property
390393
def new_semantic_analyzer(self) -> bool:

mypy/semanal.py

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1992,38 +1992,42 @@ def analyze_class_typevar_declaration(self, base: Type) -> tuple[TypeVarLikeList
19921992
return None
19931993

19941994
def analyze_unbound_tvar(self, t: Type) -> tuple[str, TypeVarLikeExpr] | None:
1995-
if not isinstance(t, UnboundType):
1996-
return None
1997-
unbound = t
1998-
sym = self.lookup_qualified(unbound.name, unbound)
1995+
if isinstance(t, UnpackType) and isinstance(t.type, UnboundType):
1996+
return self.analyze_unbound_tvar_impl(t.type, allow_tvt=True)
1997+
if isinstance(t, UnboundType):
1998+
sym = self.lookup_qualified(t.name, t)
1999+
if sym and sym.fullname in ("typing.Unpack", "typing_extensions.Unpack"):
2000+
inner_t = t.args[0]
2001+
if isinstance(inner_t, UnboundType):
2002+
return self.analyze_unbound_tvar_impl(inner_t, allow_tvt=True)
2003+
return None
2004+
return self.analyze_unbound_tvar_impl(t)
2005+
return None
2006+
2007+
def analyze_unbound_tvar_impl(
2008+
self, t: UnboundType, allow_tvt: bool = False
2009+
) -> tuple[str, TypeVarLikeExpr] | None:
2010+
sym = self.lookup_qualified(t.name, t)
19992011
if sym and isinstance(sym.node, PlaceholderNode):
20002012
self.record_incomplete_ref()
2001-
if sym and isinstance(sym.node, ParamSpecExpr):
2013+
if not allow_tvt and sym and isinstance(sym.node, ParamSpecExpr):
20022014
if sym.fullname and not self.tvar_scope.allow_binding(sym.fullname):
20032015
# It's bound by our type variable scope
20042016
return None
2005-
return unbound.name, sym.node
2006-
if sym and sym.fullname in ("typing.Unpack", "typing_extensions.Unpack"):
2007-
inner_t = unbound.args[0]
2008-
if not isinstance(inner_t, UnboundType):
2017+
return t.name, sym.node
2018+
if allow_tvt and sym and isinstance(sym.node, TypeVarTupleExpr):
2019+
if sym.fullname and not self.tvar_scope.allow_binding(sym.fullname):
2020+
# It's bound by our type variable scope
20092021
return None
2010-
inner_unbound = inner_t
2011-
inner_sym = self.lookup_qualified(inner_unbound.name, inner_unbound)
2012-
if inner_sym and isinstance(inner_sym.node, PlaceholderNode):
2013-
self.record_incomplete_ref()
2014-
if inner_sym and isinstance(inner_sym.node, TypeVarTupleExpr):
2015-
if inner_sym.fullname and not self.tvar_scope.allow_binding(inner_sym.fullname):
2016-
# It's bound by our type variable scope
2017-
return None
2018-
return inner_unbound.name, inner_sym.node
2019-
if sym is None or not isinstance(sym.node, TypeVarExpr):
2022+
return t.name, sym.node
2023+
if sym is None or not isinstance(sym.node, TypeVarExpr) or allow_tvt:
20202024
return None
20212025
elif sym.fullname and not self.tvar_scope.allow_binding(sym.fullname):
20222026
# It's bound by our type variable scope
20232027
return None
20242028
else:
20252029
assert isinstance(sym.node, TypeVarExpr)
2026-
return unbound.name, sym.node
2030+
return t.name, sym.node
20272031

20282032
def get_all_bases_tvars(
20292033
self, base_type_exprs: list[Expression], removed: list[int]
@@ -5333,7 +5337,9 @@ def analyze_type_application_args(self, expr: IndexExpr) -> list[Type] | None:
53335337
has_param_spec = False
53345338
num_args = -1
53355339
elif isinstance(base, RefExpr) and isinstance(base.node, TypeInfo):
5336-
allow_unpack = base.node.has_type_var_tuple_type
5340+
allow_unpack = (
5341+
base.node.has_type_var_tuple_type or base.node.fullname == "builtins.tuple"
5342+
)
53375343
has_param_spec = base.node.has_param_spec_type
53385344
num_args = len(base.node.type_vars)
53395345
else:
@@ -5343,7 +5349,7 @@ def analyze_type_application_args(self, expr: IndexExpr) -> list[Type] | None:
53435349

53445350
for item in items:
53455351
try:
5346-
typearg = self.expr_to_unanalyzed_type(item)
5352+
typearg = self.expr_to_unanalyzed_type(item, allow_unpack=True)
53475353
except TypeTranslationError:
53485354
self.fail("Type expected within [...]", expr)
53495355
return None
@@ -6608,8 +6614,10 @@ def type_analyzer(
66086614
tpan.global_scope = not self.type and not self.function_stack
66096615
return tpan
66106616

6611-
def expr_to_unanalyzed_type(self, node: Expression) -> ProperType:
6612-
return expr_to_unanalyzed_type(node, self.options, self.is_stub_file)
6617+
def expr_to_unanalyzed_type(self, node: Expression, allow_unpack: bool = False) -> ProperType:
6618+
return expr_to_unanalyzed_type(
6619+
node, self.options, self.is_stub_file, allow_unpack=allow_unpack
6620+
)
66136621

66146622
def anal_type(
66156623
self,

mypy/typeanal.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -964,7 +964,10 @@ def visit_unpack_type(self, t: UnpackType) -> Type:
964964
if not self.allow_unpack:
965965
self.fail(message_registry.INVALID_UNPACK_POSITION, t.type, code=codes.VALID_TYPE)
966966
return AnyType(TypeOfAny.from_error)
967-
return UnpackType(self.anal_type(t.type), from_star_syntax=t.from_star_syntax)
967+
self.allow_type_var_tuple = True
968+
result = UnpackType(self.anal_type(t.type), from_star_syntax=t.from_star_syntax)
969+
self.allow_type_var_tuple = False
970+
return result
968971

969972
def visit_parameters(self, t: Parameters) -> Type:
970973
raise NotImplementedError("ParamSpec literals cannot have unbound TypeVars")

test-data/unit/check-python311.test

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,68 @@ async def coro() -> Generator[List[Any], None, None]:
7777
reveal_type(coro) # N: Revealed type is "def () -> typing.Coroutine[Any, Any, typing.Generator[builtins.list[Any], None, None]]"
7878
[builtins fixtures/async_await.pyi]
7979
[typing fixtures/typing-async.pyi]
80+
81+
[case testTypeVarTupleNewSyntaxAnnotations]
82+
Ints = tuple[int, int, int]
83+
x: tuple[str, *Ints]
84+
reveal_type(x) # N: Revealed type is "Tuple[builtins.str, builtins.int, builtins.int, builtins.int]"
85+
y: tuple[int, *tuple[int, ...]]
86+
reveal_type(y) # N: Revealed type is "Tuple[builtins.int, Unpack[builtins.tuple[builtins.int, ...]]]"
87+
[builtins fixtures/tuple.pyi]
88+
89+
[case testTypeVarTupleNewSyntaxGenerics]
90+
from typing import Generic, TypeVar, TypeVarTuple
91+
92+
T = TypeVar("T")
93+
Ts = TypeVarTuple("Ts")
94+
class C(Generic[T, *Ts]):
95+
attr: tuple[int, *Ts, str]
96+
97+
def test(self) -> None:
98+
reveal_type(self.attr) # N: Revealed type is "Tuple[builtins.int, Unpack[Ts`2], builtins.str]"
99+
self.attr = ci # E: Incompatible types in assignment (expression has type "C[*Tuple[int, ...]]", variable has type "Tuple[int, *Ts, str]")
100+
def meth(self, *args: *Ts) -> T: ...
101+
102+
ci: C[*tuple[int, ...]]
103+
reveal_type(ci) # N: Revealed type is "__main__.C[Unpack[builtins.tuple[builtins.int, ...]]]"
104+
reveal_type(ci.meth) # N: Revealed type is "def (*args: builtins.int) -> builtins.int"
105+
c3: C[str, str, str]
106+
reveal_type(c3) # N: Revealed type is "__main__.C[builtins.str, builtins.str, builtins.str]"
107+
108+
A = C[int, *Ts]
109+
B = tuple[str, *tuple[str, str], str]
110+
z: A[*B]
111+
reveal_type(z) # N: Revealed type is "__main__.C[builtins.int, builtins.str, builtins.str, builtins.str, builtins.str]"
112+
[builtins fixtures/tuple.pyi]
113+
114+
[case testTypeVarTupleNewSyntaxCallables]
115+
from typing import Generic, overload, TypeVar
116+
117+
T1 = TypeVar("T1")
118+
T2 = TypeVar("T2")
119+
class MyClass(Generic[T1, T2]):
120+
@overload
121+
def __init__(self: MyClass[None, None]) -> None: ...
122+
123+
@overload
124+
def __init__(self: MyClass[T1, None], *types: *tuple[type[T1]]) -> None: ...
125+
126+
@overload
127+
def __init__(self: MyClass[T1, T2], *types: *tuple[type[T1], type[T2]]) -> None: ...
128+
129+
def __init__(self: MyClass[T1, T2], *types: *tuple[type, ...]) -> None:
130+
pass
131+
132+
myclass = MyClass()
133+
reveal_type(myclass) # N: Revealed type is "__main__.MyClass[None, None]"
134+
myclass1 = MyClass(float)
135+
reveal_type(myclass1) # N: Revealed type is "__main__.MyClass[builtins.float, None]"
136+
myclass2 = MyClass(float, float)
137+
reveal_type(myclass2) # N: Revealed type is "__main__.MyClass[builtins.float, builtins.float]"
138+
myclass3 = MyClass(float, float, float) # E: No overload variant of "MyClass" matches argument types "Type[float]", "Type[float]", "Type[float]" \
139+
# N: Possible overload variants: \
140+
# N: def [T1, T2] __init__(self) -> MyClass[None, None] \
141+
# N: def [T1, T2] __init__(self, Type[T1], /) -> MyClass[T1, None] \
142+
# N: def [T1, T2] __init__(Type[T1], Type[T2], /) -> MyClass[T1, T2]
143+
reveal_type(myclass3) # N: Revealed type is "Any"
144+
[builtins fixtures/tuple.pyi]

test-data/unit/check-python312.test

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,6 @@ type Alias2[**P] = Callable[P, int] # E: PEP 695 type aliases are not yet suppo
4141
# E: Value of type "int" is not indexable \
4242
# E: Name "P" is not defined
4343
type Alias3[*Ts] = tuple[*Ts] # E: PEP 695 type aliases are not yet supported \
44-
# E: Type expected within [...] \
45-
# E: The type "Type[Tuple[Any, ...]]" is not generic and not indexable \
4644
# E: Name "Ts" is not defined
4745

4846
class Cls1[T: int]: ... # E: PEP 695 generics are not yet supported

0 commit comments

Comments
 (0)