From cfb43b27befa05d17e465cd2bb7419cfe7ab20f5 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 29 May 2022 09:58:44 +0300 Subject: [PATCH 1/5] gh-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] --- Lib/test/test_typing.py | 11 ++++++++--- Lib/typing.py | 41 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 2afac235391552..1d53194e505a5a 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -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]'), @@ -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, ...]]'), diff --git a/Lib/typing.py b/Lib/typing.py index 40ab516f7c8ff7..f53f946306a148 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -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) @@ -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};" @@ -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. From c638e0ed0164843f8debb34b21d9b404f0073d03 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 29 May 2022 19:01:26 +0300 Subject: [PATCH 2/5] The C implementation. --- Lib/test/test_typing.py | 19 +++---- Lib/typing.py | 20 ++++---- Objects/genericaliasobject.c | 96 ++++++++++++++++++++++++++++++++---- 3 files changed, 104 insertions(+), 31 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 1d53194e505a5a..3dea7e55b45a59 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -758,11 +758,6 @@ class C(Generic[*Ts]): pass ('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]', '[*Ts]', 'generic[*Ts]'), ('generic[*Ts]', '[T, *Ts]', 'generic[T, *Ts]'), ('generic[*Ts]', '[*Ts, T]', 'generic[*Ts, T]'), @@ -770,16 +765,18 @@ 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, ...]] - ('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[T, *Ts]', '[*tuple_type[int, ...]]', 'generic[int, *tuple_type[int, ...]]'), + ('generic[*Ts, T]', '[*tuple_type[int, ...]]', 'generic[*tuple_type[int, ...], int]'), + ('generic[T1, *Ts, T2]', '[*tuple_type[int, ...]]', 'generic[int, *tuple_type[int, ...], int]'), + ('generic[T, str, *Ts]', '[*tuple_type[int, ...]]', 'generic[int, str, *tuple_type[int, ...]]'), + ('generic[*Ts, str, T]', '[*tuple_type[int, ...]]', 'generic[*tuple_type[int, ...], str, int]'), + ('generic[list[T], *Ts]', '[*tuple_type[int, ...]]', 'generic[list[int], *tuple_type[int, ...]]'), + ('generic[*Ts, list[T]]', '[*tuple_type[int, ...]]', 'generic[*tuple_type[int, ...], list[int]]'), + ('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, ...]]'), ('generic[T1, *tuple_type[int, ...], T2]', '[str, bool]', 'generic[str, *tuple_type[int, ...], bool]'), diff --git a/Lib/typing.py b/Lib/typing.py index f53f946306a148..94147ff972857a 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1429,8 +1429,8 @@ def _determine_new_args(self, args): alen = len(args) plen = len(params) if typevartuple_index is not None: - i = typevartuple_index - j = plen - typevartuple_index - 1 + left = typevartuple_index + right = plen - typevartuple_index - 1 var_tuple_index = None for k, arg in enumerate(args): if _is_unpacked_var_tuple(arg): @@ -1439,19 +1439,19 @@ def _determine_new_args(self, args): 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: + left = min(left, var_tuple_index) + right = min(right, alen - var_tuple_index - 1) + elif left + right > 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])) - for k in range(i, typevartuple_index): + new_arg_by_param.update(zip(params[:left], args[:left])) + for k in range(left, 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[typevartuple_index]] = tuple(args[left: alen - right]) + for k in range(typevartuple_index + 1, plen - right): new_arg_by_param[params[k]] = fillarg - new_arg_by_param.update(zip(params[plen - j:], args[alen - j:])) + new_arg_by_param.update(zip(params[plen - right:], args[alen - right:])) else: if alen != plen: raise TypeError(f"Too {'many' if alen > plen else 'few'} arguments for {self};" diff --git a/Objects/genericaliasobject.c b/Objects/genericaliasobject.c index 39fd70999ecbe5..e32e97cd1aa3ba 100644 --- a/Objects/genericaliasobject.c +++ b/Objects/genericaliasobject.c @@ -269,7 +269,9 @@ _Py_make_parameters(PyObject *args) a non-empty tuple, return a new reference to obj. */ static PyObject * subs_tvars(PyObject *obj, PyObject *params, - PyObject **argitems, Py_ssize_t nargs, Py_ssize_t varparam) + PyObject **argitems, Py_ssize_t nargs, + Py_ssize_t varparam, Py_ssize_t left, Py_ssize_t right, + PyObject *fillarg) { PyObject *subparams; if (_PyObject_LookupAttr(obj, &_Py_ID(__parameters__), &subparams) < 0) { @@ -283,28 +285,36 @@ subs_tvars(PyObject *obj, PyObject *params, Py_DECREF(subparams); return NULL; } - for (Py_ssize_t i = 0, j = 0; i < nsubargs; ++i) { + Py_ssize_t j = 0; + for (Py_ssize_t i = 0; i < nsubargs; ++i) { PyObject *arg = PyTuple_GET_ITEM(subparams, i); Py_ssize_t iparam = tuple_index(params, nparams, arg); if (iparam == varparam) { j = tuple_extend(&subargs, j, - argitems + iparam, nargs - nparams + 1); + argitems + left, nargs - left - right); if (j < 0) { return NULL; } } else { if (iparam >= 0) { - if (iparam > varparam) { - iparam += nargs - nsubargs; + if (iparam < left) { + arg = argitems[iparam]; + } + else if (iparam >= nparams - right) { + iparam += nargs - nparams; + arg = argitems[iparam]; + } + else { + arg = fillarg; } - arg = argitems[iparam]; } Py_INCREF(arg); PyTuple_SET_ITEM(subargs, j, arg); j++; } } + assert(j == PyTuple_GET_SIZE(subargs)); obj = PyObject_GetItem(obj, subargs); @@ -399,6 +409,27 @@ _unpack_args(PyObject *item) return newargs; } +static PyObject * +_get_unpacked_var_tuple_arg(PyObject *arg) +{ + if (PyType_Check(arg)) { + return NULL; + } + PyObject *subargs = _unpacked_tuple_args(arg); + if (subargs != NULL && + PyTuple_Check(subargs) && + PyTuple_GET_SIZE(subargs) == 2 && + PyTuple_GET_ITEM(subargs, 1) == Py_Ellipsis) + { + PyObject *subarg = PyTuple_GET_ITEM(subargs, 0); + Py_INCREF(subarg); + Py_DECREF(subargs); + return subarg; + } + Py_XDECREF(subargs); + return NULL; +} + PyObject * _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObject *item) { @@ -425,8 +456,37 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje varparam = i; } } + PyObject *fillarg = NULL; + Py_ssize_t vartuplearg = nitems; + Py_ssize_t left = varparam; + Py_ssize_t right = nparams - varparam - 1; if (varparam < nparams) { - if (nitems < nparams - 1) { + for (Py_ssize_t i = 0; i < nitems; i++) { + PyObject *arg = _get_unpacked_var_tuple_arg(argitems[i]); + if (arg) { + if (vartuplearg < nitems) { + Py_DECREF(arg); + Py_DECREF(fillarg); + Py_DECREF(item); + return PyErr_Format(PyExc_TypeError, + "More than one unpacked variable-size tuple argument", + self); + } + vartuplearg = i; + fillarg = arg; + } + else if (PyErr_Occurred()) { + Py_XDECREF(fillarg); + Py_DECREF(item); + return NULL; + } + } + if (vartuplearg < nitems) { + assert(fillarg); + left = Py_MIN(left, vartuplearg); + right = Py_MIN(right, nitems - vartuplearg - 1); + } + else if (left + right > nitems) { Py_DECREF(item); return PyErr_Format(PyExc_TypeError, "Too few arguments for %R", @@ -451,6 +511,7 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje Py_ssize_t nargs = PyTuple_GET_SIZE(args); PyObject *newargs = PyTuple_New(nargs); if (newargs == NULL) { + Py_XDECREF(fillarg); Py_DECREF(item); return NULL; } @@ -459,12 +520,14 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje int unpack = _is_unpacked_typevartuple(arg); if (unpack < 0) { Py_DECREF(newargs); + Py_XDECREF(fillarg); Py_DECREF(item); return NULL; } PyObject *subst; if (_PyObject_LookupAttr(arg, &_Py_ID(__typing_subst__), &subst) < 0) { Py_DECREF(newargs); + Py_XDECREF(fillarg); Py_DECREF(item); return NULL; } @@ -474,22 +537,33 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje if (iparam == varparam) { Py_DECREF(subst); Py_DECREF(newargs); + Py_XDECREF(fillarg); Py_DECREF(item); PyErr_SetString(PyExc_TypeError, "Substitution of bare TypeVarTuple is not supported"); return NULL; } - if (iparam > varparam) { + if (iparam < left) { + arg = argitems[iparam]; + } + else if (iparam >= nparams - right) { iparam += nitems - nparams; + arg = argitems[iparam]; + } + else { + assert(fillarg); + arg = fillarg; } - arg = PyObject_CallOneArg(subst, argitems[iparam]); + arg = PyObject_CallOneArg(subst, arg); Py_DECREF(subst); } else { - arg = subs_tvars(arg, parameters, argitems, nitems, varparam); + arg = subs_tvars(arg, parameters, argitems, nitems, + varparam, left, right, fillarg); } if (arg == NULL) { Py_DECREF(newargs); + Py_XDECREF(fillarg); Py_DECREF(item); return NULL; } @@ -498,6 +572,7 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje &PyTuple_GET_ITEM(arg, 0), PyTuple_GET_SIZE(arg)); Py_DECREF(arg); if (jarg < 0) { + Py_XDECREF(fillarg); Py_DECREF(item); return NULL; } @@ -508,6 +583,7 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje } } + Py_XDECREF(fillarg); Py_DECREF(item); return newargs; } From 731d5fb8802b61ada11395c324bb5531803a9f1d Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 29 May 2022 21:35:02 +0300 Subject: [PATCH 3/5] Remove unused code. --- Lib/typing.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/Lib/typing.py b/Lib/typing.py index 94147ff972857a..bb5274bcaa7530 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1783,16 +1783,6 @@ 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. From 03da7944672bf3bd3fece85df6bcd4d63056d5d5 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 30 May 2022 08:56:10 +0300 Subject: [PATCH 4/5] Refactoring. --- Lib/test/test_typing.py | 3 +- Lib/typing.py | 58 +++++------------------------------- Objects/genericaliasobject.c | 2 +- 3 files changed, 10 insertions(+), 53 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 3dea7e55b45a59..8da52f77054106 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -753,10 +753,11 @@ class C(Generic[*Ts]): pass ('generic[*Ts]', '[*tuple_type[int]]', 'generic[int]'), ('generic[*Ts]', '[*tuple_type[*Ts]]', 'generic[*Ts]'), ('generic[*Ts]', '[*tuple_type[int, str]]', 'generic[int, str]'), + ('generic[*Ts]', '[str, *tuple_type[int, ...], bool]', 'generic[str, *tuple_type[int, ...], bool]'), ('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]'), + ('generic[*Ts]', '[*tuple_type[int, ...], *tuple_type[str, ...]]', 'TypeError'), ('generic[*Ts]', '[*Ts]', 'generic[*Ts]'), ('generic[*Ts]', '[T, *Ts]', 'generic[T, *Ts]'), diff --git a/Lib/typing.py b/Lib/typing.py index bb5274bcaa7530..30e2cdae42ab29 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -906,14 +906,6 @@ 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) @@ -1263,44 +1255,6 @@ def __dir__(self): + [attr for attr in dir(self.__origin__) if not _is_dunder(attr)])) -def _is_unpacked_tuple(x: Any) -> bool: - # Is `x` something like `*tuple[int]` or `*tuple[int, ...]`? - if not isinstance(x, _UnpackGenericAlias): - return False - # Alright, `x` is `Unpack[something]`. - - # `x` will always have `__args__`, because Unpack[] and Unpack[()] - # aren't legal. - unpacked_type = x.__args__[0] - - return getattr(unpacked_type, '__origin__', None) is tuple - - -def _is_unpacked_arbitrary_length_tuple(x: Any) -> bool: - if not _is_unpacked_tuple(x): - return False - unpacked_tuple = x.__args__[0] - - if not hasattr(unpacked_tuple, '__args__'): - # It's `Unpack[tuple]`. We can't make any assumptions about the length - # of the tuple, so it's effectively an arbitrary-length tuple. - return True - - tuple_args = unpacked_tuple.__args__ - if not tuple_args: - # It's `Unpack[tuple[()]]`. - return False - - last_arg = tuple_args[-1] - if last_arg is Ellipsis: - # It's `Unpack[tuple[something, ...]]`, which is arbitrary-length. - return True - - # If the arguments didn't end with an ellipsis, then it's not an - # arbitrary-length tuple. - return False - - # Special typing constructs Union, Optional, Generic, Callable and Tuple # use three special attributes for internal bookkeeping of generic types: # * __parameters__ is a tuple of unique free type parameters of a generic @@ -1433,11 +1387,13 @@ def _determine_new_args(self, args): right = 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 not (isinstance(arg, type) and not isinstance(arg, GenericAlias)): + subargs = getattr(arg, '__typing_unpacked_tuple_args__', None) + if subargs and len(subargs) == 2 and subargs[-1] is ...: + if var_tuple_index is not None: + raise TypeError("More than one unpacked arbitrary-length tuple argument") + var_tuple_index = k + fillarg = subargs[0] if var_tuple_index is not None: left = min(left, var_tuple_index) right = min(right, alen - var_tuple_index - 1) diff --git a/Objects/genericaliasobject.c b/Objects/genericaliasobject.c index e32e97cd1aa3ba..a64bf960f9f353 100644 --- a/Objects/genericaliasobject.c +++ b/Objects/genericaliasobject.c @@ -469,7 +469,7 @@ _Py_subs_parameters(PyObject *self, PyObject *args, PyObject *parameters, PyObje Py_DECREF(fillarg); Py_DECREF(item); return PyErr_Format(PyExc_TypeError, - "More than one unpacked variable-size tuple argument", + "More than one unpacked arbitrary-length tuple argument", self); } vartuplearg = i; From 55c4efb30c2cda58df5b80cb1ea29428add8ef74 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 1 Jun 2022 11:24:27 +0300 Subject: [PATCH 5/5] Add a NEWS entry. --- .../Library/2022-06-01-11-24-13.gh-issue-91162.NxvU_u.rst | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2022-06-01-11-24-13.gh-issue-91162.NxvU_u.rst diff --git a/Misc/NEWS.d/next/Library/2022-06-01-11-24-13.gh-issue-91162.NxvU_u.rst b/Misc/NEWS.d/next/Library/2022-06-01-11-24-13.gh-issue-91162.NxvU_u.rst new file mode 100644 index 00000000000000..c8de0cf3e868f1 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-06-01-11-24-13.gh-issue-91162.NxvU_u.rst @@ -0,0 +1,5 @@ +Support splitting of unpacked arbitrary-length tuple over ``TypeVar`` and +``TypeVarTuple`` parameters. For example: + +* A[T, *Ts][*tuple[int, ...]] -> A[int, *tuple[int, ...]] +* A[*Ts, T][*tuple[int, ...]] -> A[*tuple[int, ...], int]