summaryrefslogtreecommitdiffstats
path: root/Lib/typing.py
diff options
context:
space:
mode:
authorSerhiy Storchaka <storchaka@gmail.com>2022-11-29 07:46:52 (GMT)
committerGitHub <noreply@github.com>2022-11-29 07:46:52 (GMT)
commit8f2fb7dfe72c882e97e524ef7ce40ceb663cc27e (patch)
tree0ab80aba81b2c351a33b32c599f1c55b5ed3b562 /Lib/typing.py
parent1d1bb95abdcafe92c771fb3dc4722351b032cc24 (diff)
downloadcpython-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.py71
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)