diff options
author | Serhiy Storchaka <storchaka@gmail.com> | 2022-11-29 07:46:52 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-11-29 07:46:52 (GMT) |
commit | 8f2fb7dfe72c882e97e524ef7ce40ceb663cc27e (patch) | |
tree | 0ab80aba81b2c351a33b32c599f1c55b5ed3b562 /Lib/typing.py | |
parent | 1d1bb95abdcafe92c771fb3dc4722351b032cc24 (diff) | |
download | cpython-8f2fb7dfe72c882e97e524ef7ce40ceb663cc27e.zip cpython-8f2fb7dfe72c882e97e524ef7ce40ceb663cc27e.tar.gz cpython-8f2fb7dfe72c882e97e524ef7ce40ceb663cc27e.tar.bz2 |
gh-99344, gh-99379, gh-99382: Fix issues in substitution of ParamSpec and TypeVarTuple (GH-99412)
* Fix substitution of TypeVarTuple and ParamSpec together in user generics.
* Fix substitution of ParamSpec followed by TypeVarTuple in generic aliases.
* Check the number of arguments in substitution in user generics containing a
TypeVarTuple and one or more TypeVar.
Diffstat (limited to 'Lib/typing.py')
-rw-r--r-- | Lib/typing.py | 71 |
1 files changed, 30 insertions, 41 deletions
diff --git a/Lib/typing.py b/Lib/typing.py index 2339415..127e3c9 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -284,25 +284,6 @@ def _unpack_args(args): newargs.append(arg) return newargs -def _prepare_paramspec_params(cls, params): - """Prepares the parameters for a Generic containing ParamSpec - variables (internal helper). - """ - # Special case where Z[[int, str, bool]] == Z[int, str, bool] in PEP 612. - if (len(cls.__parameters__) == 1 - and params and not _is_param_expr(params[0])): - assert isinstance(cls.__parameters__[0], ParamSpec) - return (params,) - else: - _check_generic(cls, params, len(cls.__parameters__)) - _params = [] - # Convert lists to tuples to help other libraries cache the results. - for p, tvar in zip(params, cls.__parameters__): - if isinstance(tvar, ParamSpec) and isinstance(p, list): - p = tuple(p) - _params.append(p) - return tuple(_params) - def _deduplicate(params): # Weed out strict duplicates, preserving the first of each occurrence. all_params = set(params) @@ -1238,7 +1219,18 @@ class ParamSpec(_Final, _Immutable, _BoundVarianceMixin, _PickleUsingNameMixin, return arg def __typing_prepare_subst__(self, alias, args): - return _prepare_paramspec_params(alias, args) + params = alias.__parameters__ + i = params.index(self) + if i >= len(args): + raise TypeError(f"Too few arguments for {alias}") + # Special case where Z[[int, str, bool]] == Z[int, str, bool] in PEP 612. + if len(params) == 1 and not _is_param_expr(args[0]): + assert i == 0 + args = (args,) + # Convert lists to tuples to help other libraries cache the results. + elif isinstance(args[i], list): + args = (*args[:i], tuple(args[i]), *args[i+1:]) + return args def _is_dunder(attr): return attr.startswith('__') and attr.endswith('__') @@ -1801,23 +1793,13 @@ class Generic: if not isinstance(params, tuple): params = (params,) - if not params: - # We're only ok with `params` being empty if the class's only type - # parameter is a `TypeVarTuple` (which can contain zero types). - class_params = getattr(cls, "__parameters__", None) - only_class_parameter_is_typevartuple = ( - class_params is not None - and len(class_params) == 1 - and isinstance(class_params[0], TypeVarTuple) - ) - if not only_class_parameter_is_typevartuple: - raise TypeError( - f"Parameter list to {cls.__qualname__}[...] cannot be empty" - ) - params = tuple(_type_convert(p) for p in params) if cls in (Generic, Protocol): # Generic and Protocol can only be subscripted with unique type variables. + if not params: + raise TypeError( + f"Parameter list to {cls.__qualname__}[...] cannot be empty" + ) if not all(_is_typevar_like(p) for p in params): raise TypeError( f"Parameters to {cls.__name__}[...] must all be type variables " @@ -1827,13 +1809,20 @@ class Generic: f"Parameters to {cls.__name__}[...] must all be unique") else: # Subscripting a regular Generic subclass. - if any(isinstance(t, ParamSpec) for t in cls.__parameters__): - params = _prepare_paramspec_params(cls, params) - elif not any(isinstance(p, TypeVarTuple) for p in cls.__parameters__): - # We only run this if there are no TypeVarTuples, because we - # don't check variadic generic arity at runtime (to reduce - # complexity of typing.py). - _check_generic(cls, params, len(cls.__parameters__)) + for param in cls.__parameters__: + prepare = getattr(param, '__typing_prepare_subst__', None) + if prepare is not None: + params = prepare(cls, params) + _check_generic(cls, params, len(cls.__parameters__)) + + new_args = [] + for param, new_arg in zip(cls.__parameters__, params): + if isinstance(param, TypeVarTuple): + new_args.extend(new_arg) + else: + new_args.append(new_arg) + params = tuple(new_args) + return _GenericAlias(cls, params, _paramspec_tvars=True) |