diff options
| author | Miss Islington (bot) <31488909+miss-islington@users.noreply.github.com> | 2023-03-23 16:54:07 (GMT) |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-03-23 16:54:07 (GMT) |
| commit | 1645a40b5eb59e7021c93fd6593d1a316d3e26e8 (patch) | |
| tree | 70ff1882d9af12bb5c7eac6312be52a68f8cb8c8 /Lib/typing.py | |
| parent | 84ae50c9146e98be28cf3af4606f0ebad07807aa (diff) | |
| download | cpython-1645a40b5eb59e7021c93fd6593d1a316d3e26e8.zip cpython-1645a40b5eb59e7021c93fd6593d1a316d3e26e8.tar.gz cpython-1645a40b5eb59e7021c93fd6593d1a316d3e26e8.tar.bz2 | |
gh-88965: typing: fix type substitution of a list of types after initial `ParamSpec` substitution (GH-102808)
Previously, this used to fail:
```py
from typing import *
T = TypeVar("T")
P = ParamSpec("P")
class X(Generic[P]):
f: Callable[P, int]
Y = X[[int, T]]
Z = Y[str]
```
(cherry picked from commit adb0621652f489033b9db8d3949564c9fe545c1d)
Co-authored-by: Nikita Sobolev <mail@sobolevn.me>
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Diffstat (limited to 'Lib/typing.py')
| -rw-r--r-- | Lib/typing.py | 33 |
1 files changed, 26 insertions, 7 deletions
diff --git a/Lib/typing.py b/Lib/typing.py index 9f5db1a..8995564 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -250,10 +250,17 @@ def _collect_parameters(args): """ parameters = [] for t in args: - # We don't want __parameters__ descriptor of a bare Python class. if isinstance(t, type): - continue - if hasattr(t, '__typing_subst__'): + # We don't want __parameters__ descriptor of a bare Python class. + pass + elif isinstance(t, tuple): + # `t` might be a tuple, when `ParamSpec` is substituted with + # `[T, int]`, or `[int, *Ts]`, etc. + for x in t: + for collected in _collect_parameters([x]): + if collected not in parameters: + parameters.append(collected) + elif hasattr(t, '__typing_subst__'): if t not in parameters: parameters.append(t) else: @@ -1416,10 +1423,12 @@ class _GenericAlias(_BaseGenericAlias, _root=True): raise TypeError(f"Too {'many' if alen > plen else 'few'} arguments for {self};" f" actual {alen}, expected {plen}") new_arg_by_param = dict(zip(params, args)) + return tuple(self._make_substitution(self.__args__, new_arg_by_param)) + def _make_substitution(self, args, new_arg_by_param): + """Create a list of new type arguments.""" new_args = [] - for old_arg in self.__args__: - + for old_arg in args: if isinstance(old_arg, type): new_args.append(old_arg) continue @@ -1463,10 +1472,20 @@ class _GenericAlias(_BaseGenericAlias, _root=True): # should join all these types together in a flat list # `(float, int, str)` - so again, we should `extend`. new_args.extend(new_arg) + elif isinstance(old_arg, tuple): + # Corner case: + # P = ParamSpec('P') + # T = TypeVar('T') + # class Base(Generic[P]): ... + # Can be substituted like this: + # X = Base[[int, T]] + # In this case, `old_arg` will be a tuple: + new_args.append( + tuple(self._make_substitution(old_arg, new_arg_by_param)), + ) else: new_args.append(new_arg) - - return tuple(new_args) + return new_args def copy_with(self, args): return self.__class__(self.__origin__, args, name=self._name, inst=self._inst, |
