gh-88965: typing: fix type substitution of a list of types after initial ParamSpec substitution (#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]
```

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
Nikita Sobolev 2023-03-23 19:26:11 +03:00 committed by GitHub
parent baf4eb083c
commit adb0621652
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 154 additions and 7 deletions

View file

@ -255,10 +255,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:
@ -1441,10 +1448,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
@ -1488,10 +1497,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,