After merging #102808 I realized that I've missed several important cases.
Right now substitution algorithm has these lines:
|
if self.__origin__ == collections.abc.Callable and isinstance(new_arg, tuple): |
|
# Consider the following `Callable`. |
|
# C = Callable[[int], str] |
|
# Here, `C.__args__` should be (int, str) - NOT ([int], str). |
|
# That means that if we had something like... |
|
# P = ParamSpec('P') |
|
# T = TypeVar('T') |
|
# C = Callable[P, T] |
|
# D = C[[int, str], float] |
|
# ...we need to be careful; `new_args` should end up as |
|
# `(int, str, float)` rather than `([int, str], float)`. |
|
new_args.extend(new_arg) |
It is tested. But, it is never tested for the nested arguments:
|
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: |
I think that Callable is complex and important enought to be covered with as many cases as possible. Furthermore, Callable can be nested deeply in real types and we need to be sure that this use-case works as intended.
I will send a PR with more tests :)
Related #88965
Linked PRs
After merging #102808 I realized that I've missed several important cases.
Right now substitution algorithm has these lines:
cpython/Lib/typing.py
Lines 1477 to 1488 in 2cdc518
It is tested. But, it is never tested for the nested arguments:
cpython/Lib/typing.py
Lines 1500 to 1511 in 2cdc518
I think that
Callableis complex and important enought to be covered with as many cases as possible. Furthermore,Callablecan be nested deeply in real types and we need to be sure that this use-case works as intended.I will send a PR with more tests :)
Related #88965
Linked PRs
Callabletype subtitution tests #103055Callabletype substitution tests (GH-103055) #103105