Skip to content

Commit cfb43b2

Browse files
pythongh-91162: Support substitution of TypeVar with an unpacked variable-size tuple
For example: A[T, *Ts][*tuple[int, ...]] -> A[int, *tuple[int, ...]] A[*Ts, T][*tuple[int, ...]] -> A[*tuple[int, ...], int]
1 parent dec1e93 commit cfb43b2

File tree

2 files changed

+45
-7
lines changed

2 files changed

+45
-7
lines changed

Lib/test/test_typing.py

+8-3
Original file line numberDiff line numberDiff line change
@@ -756,11 +756,12 @@ class C(Generic[*Ts]): pass
756756
('generic[*Ts]', '[tuple_type[int, ...]]', 'generic[tuple_type[int, ...]]'),
757757
('generic[*Ts]', '[tuple_type[int, ...], tuple_type[str, ...]]', 'generic[tuple_type[int, ...], tuple_type[str, ...]]'),
758758
('generic[*Ts]', '[*tuple_type[int, ...]]', 'generic[*tuple_type[int, ...]]'),
759+
('generic[*Ts]', '[str, *tuple_type[int, ...], bool]', 'generic[str, *tuple_type[int, ...], bool]'),
759760

760761
# Technically, multiple unpackings are forbidden by PEP 646, but we
761762
# choose to be less restrictive at runtime, to allow folks room
762763
# to experiment. So all three of these should be valid.
763-
('generic[*Ts]', '[*tuple_type[int, ...], *tuple_type[str, ...]]', 'generic[*tuple_type[int, ...], *tuple_type[str, ...]]'),
764+
#('generic[*Ts]', '[*tuple_type[int, ...], *tuple_type[str, ...]]', 'generic[*tuple_type[int, ...], *tuple_type[str, ...]]'),
764765

765766
('generic[*Ts]', '[*Ts]', 'generic[*Ts]'),
766767
('generic[*Ts]', '[T, *Ts]', 'generic[T, *Ts]'),
@@ -769,11 +770,15 @@ class C(Generic[*Ts]): pass
769770
('generic[T, *Ts]', '[int, str]', 'generic[int, str]'),
770771
('generic[T, *Ts]', '[int, str, bool]', 'generic[int, str, bool]'),
771772

772-
('generic[T, *Ts]', '[*tuple[int, ...]]', 'TypeError'), # Should be generic[int, *tuple[int, ...]]
773+
#('generic[T, *Ts]', '[*tuple[int, ...]]', 'TypeError'), # Should be generic[int, *tuple[int, ...]]
774+
('C[T, *Ts]', '[*tuple_type[int, ...]]', 'C[int, *tuple_type[int, ...]]'),
775+
('C[*Ts, T]', '[*tuple_type[int, ...]]', 'C[*tuple_type[int, ...], int]'),
776+
('C[T1, *Ts, T2]', '[*tuple_type[int, ...]]', 'C[int, *tuple_type[int, ...], int]'),
777+
773778

774779
('generic[*Ts, T]', '[int]', 'generic[int]'),
775780
('generic[*Ts, T]', '[int, str]', 'generic[int, str]'),
776-
('generic[*Ts, T]', '[int, str, bool]', 'generic[int, str, bool]'),
781+
('generic[*Ts, T]', '[int, str, bool]', 'generic[int, str, bool]'),
777782

778783
('generic[T, *tuple_type[int, ...]]', '[str]', 'generic[str, *tuple_type[int, ...]]'),
779784
('generic[T1, T2, *tuple_type[int, ...]]', '[str, bool]', 'generic[str, bool, *tuple_type[int, ...]]'),

Lib/typing.py

+37-4
Original file line numberDiff line numberDiff line change
@@ -906,6 +906,14 @@ def _is_unpacked_typevartuple(x: Any) -> bool:
906906
return ((not isinstance(x, type)) and
907907
getattr(x, '__typing_is_unpacked_typevartuple__', False))
908908

909+
def _is_unpacked_var_tuple(x: Any) -> bool:
910+
if isinstance(x, type) and not isinstance(x, GenericAlias):
911+
return False
912+
args = getattr(x, '__typing_unpacked_tuple_args__', None)
913+
if args and args[-1] is ...:
914+
return True
915+
return False
916+
909917

910918
def _is_typevar_like(x: Any) -> bool:
911919
return isinstance(x, (TypeVar, ParamSpec)) or _is_unpacked_typevartuple(x)
@@ -1422,13 +1430,28 @@ def _determine_new_args(self, args):
14221430
plen = len(params)
14231431
if typevartuple_index is not None:
14241432
i = typevartuple_index
1425-
j = alen - (plen - i - 1)
1426-
if j < i:
1433+
j = plen - typevartuple_index - 1
1434+
var_tuple_index = None
1435+
for k, arg in enumerate(args):
1436+
if _is_unpacked_var_tuple(arg):
1437+
if var_tuple_index is not None:
1438+
raise TypeError("More than one unpacked variable-size tuple argument")
1439+
var_tuple_index = k
1440+
fillarg = args[var_tuple_index].__typing_unpacked_tuple_args__[0]
1441+
if var_tuple_index is not None:
1442+
i = min(i, var_tuple_index)
1443+
j = min(j, alen - var_tuple_index - 1)
1444+
elif i + j > alen:
14271445
raise TypeError(f"Too few arguments for {self};"
14281446
f" actual {alen}, expected at least {plen-1}")
1447+
14291448
new_arg_by_param.update(zip(params[:i], args[:i]))
1430-
new_arg_by_param[params[i]] = tuple(args[i: j])
1431-
new_arg_by_param.update(zip(params[i + 1:], args[j:]))
1449+
for k in range(i, typevartuple_index):
1450+
new_arg_by_param[params[k]] = fillarg
1451+
new_arg_by_param[params[typevartuple_index]] = tuple(args[i: alen - j])
1452+
for k in range(typevartuple_index + 1, plen - j):
1453+
new_arg_by_param[params[k]] = fillarg
1454+
new_arg_by_param.update(zip(params[plen - j:], args[alen - j:]))
14321455
else:
14331456
if alen != plen:
14341457
raise TypeError(f"Too {'many' if alen > plen else 'few'} arguments for {self};"
@@ -1760,6 +1783,16 @@ def __typing_is_unpacked_typevartuple__(self):
17601783
assert len(self.__args__) == 1
17611784
return isinstance(self.__args__[0], TypeVarTuple)
17621785

1786+
@property
1787+
def __typing_is_unpacked_var_tuple__(self):
1788+
assert self.__origin__ is Unpack
1789+
assert len(self.__args__) == 1
1790+
arg, = self.__args__
1791+
if isinstance(arg, _GenericAlias):
1792+
assert arg.__origin__ is tuple
1793+
return len(arg.__args__) >= 2 and arg.__args__[-1] is ...
1794+
return False
1795+
17631796

17641797
class Generic:
17651798
"""Abstract base class for generic types.

0 commit comments

Comments
 (0)