Skip to content

Commit

Permalink
pythongh-91162: Support substitution of TypeVar with an unpacked vari…
Browse files Browse the repository at this point in the history
…able-size tuple

For example:

  A[T, *Ts][*tuple[int, ...]] -> A[int, *tuple[int, ...]]
  A[*Ts, T][*tuple[int, ...]] -> A[*tuple[int, ...], int]
  • Loading branch information
serhiy-storchaka committed May 29, 2022
1 parent dec1e93 commit cfb43b2
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 7 deletions.
11 changes: 8 additions & 3 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -756,11 +756,12 @@ class C(Generic[*Ts]): pass
('generic[*Ts]', '[tuple_type[int, ...]]', 'generic[tuple_type[int, ...]]'),
('generic[*Ts]', '[tuple_type[int, ...], tuple_type[str, ...]]', 'generic[tuple_type[int, ...], tuple_type[str, ...]]'),
('generic[*Ts]', '[*tuple_type[int, ...]]', 'generic[*tuple_type[int, ...]]'),
('generic[*Ts]', '[str, *tuple_type[int, ...], bool]', 'generic[str, *tuple_type[int, ...], bool]'),

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

('generic[*Ts]', '[*Ts]', 'generic[*Ts]'),
('generic[*Ts]', '[T, *Ts]', 'generic[T, *Ts]'),
Expand All @@ -769,11 +770,15 @@ class C(Generic[*Ts]): pass
('generic[T, *Ts]', '[int, str]', 'generic[int, str]'),
('generic[T, *Ts]', '[int, str, bool]', 'generic[int, str, bool]'),

('generic[T, *Ts]', '[*tuple[int, ...]]', 'TypeError'), # Should be generic[int, *tuple[int, ...]]
#('generic[T, *Ts]', '[*tuple[int, ...]]', 'TypeError'), # Should be generic[int, *tuple[int, ...]]
('C[T, *Ts]', '[*tuple_type[int, ...]]', 'C[int, *tuple_type[int, ...]]'),
('C[*Ts, T]', '[*tuple_type[int, ...]]', 'C[*tuple_type[int, ...], int]'),
('C[T1, *Ts, T2]', '[*tuple_type[int, ...]]', 'C[int, *tuple_type[int, ...], int]'),


('generic[*Ts, T]', '[int]', 'generic[int]'),
('generic[*Ts, T]', '[int, str]', 'generic[int, str]'),
('generic[*Ts, T]', '[int, str, bool]', 'generic[int, str, bool]'),
('generic[*Ts, T]', '[int, str, bool]', 'generic[int, str, bool]'),

('generic[T, *tuple_type[int, ...]]', '[str]', 'generic[str, *tuple_type[int, ...]]'),
('generic[T1, T2, *tuple_type[int, ...]]', '[str, bool]', 'generic[str, bool, *tuple_type[int, ...]]'),
Expand Down
41 changes: 37 additions & 4 deletions Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -906,6 +906,14 @@ def _is_unpacked_typevartuple(x: Any) -> bool:
return ((not isinstance(x, type)) and
getattr(x, '__typing_is_unpacked_typevartuple__', False))

def _is_unpacked_var_tuple(x: Any) -> bool:
if isinstance(x, type) and not isinstance(x, GenericAlias):
return False
args = getattr(x, '__typing_unpacked_tuple_args__', None)
if args and args[-1] is ...:
return True
return False


def _is_typevar_like(x: Any) -> bool:
return isinstance(x, (TypeVar, ParamSpec)) or _is_unpacked_typevartuple(x)
Expand Down Expand Up @@ -1422,13 +1430,28 @@ def _determine_new_args(self, args):
plen = len(params)
if typevartuple_index is not None:
i = typevartuple_index
j = alen - (plen - i - 1)
if j < i:
j = plen - typevartuple_index - 1
var_tuple_index = None
for k, arg in enumerate(args):
if _is_unpacked_var_tuple(arg):
if var_tuple_index is not None:
raise TypeError("More than one unpacked variable-size tuple argument")
var_tuple_index = k
fillarg = args[var_tuple_index].__typing_unpacked_tuple_args__[0]
if var_tuple_index is not None:
i = min(i, var_tuple_index)
j = min(j, alen - var_tuple_index - 1)
elif i + j > alen:
raise TypeError(f"Too few arguments for {self};"
f" actual {alen}, expected at least {plen-1}")

new_arg_by_param.update(zip(params[:i], args[:i]))
new_arg_by_param[params[i]] = tuple(args[i: j])
new_arg_by_param.update(zip(params[i + 1:], args[j:]))
for k in range(i, typevartuple_index):
new_arg_by_param[params[k]] = fillarg
new_arg_by_param[params[typevartuple_index]] = tuple(args[i: alen - j])
for k in range(typevartuple_index + 1, plen - j):
new_arg_by_param[params[k]] = fillarg
new_arg_by_param.update(zip(params[plen - j:], args[alen - j:]))
else:
if alen != plen:
raise TypeError(f"Too {'many' if alen > plen else 'few'} arguments for {self};"
Expand Down Expand Up @@ -1760,6 +1783,16 @@ def __typing_is_unpacked_typevartuple__(self):
assert len(self.__args__) == 1
return isinstance(self.__args__[0], TypeVarTuple)

@property
def __typing_is_unpacked_var_tuple__(self):
assert self.__origin__ is Unpack
assert len(self.__args__) == 1
arg, = self.__args__
if isinstance(arg, _GenericAlias):
assert arg.__origin__ is tuple
return len(arg.__args__) >= 2 and arg.__args__[-1] is ...
return False


class Generic:
"""Abstract base class for generic types.
Expand Down

0 comments on commit cfb43b2

Please sign in to comment.