Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-43224: Implement substitution of unpacked TypeVarTuple in C #31828

Merged
merged 5 commits into from
Apr 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Include/internal/pycore_global_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(__truediv__)
STRUCT_FOR_ID(__trunc__)
STRUCT_FOR_ID(__typing_subst__)
STRUCT_FOR_ID(__typing_unpacked__)
STRUCT_FOR_ID(__warningregistry__)
STRUCT_FOR_ID(__weakref__)
STRUCT_FOR_ID(__xor__)
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_runtime_init.h
Original file line number Diff line number Diff line change
Expand Up @@ -825,6 +825,7 @@ extern "C" {
INIT_ID(__truediv__), \
INIT_ID(__trunc__), \
INIT_ID(__typing_subst__), \
INIT_ID(__typing_unpacked__), \
INIT_ID(__warningregistry__), \
INIT_ID(__weakref__), \
INIT_ID(__xor__), \
Expand Down
63 changes: 39 additions & 24 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -754,89 +754,89 @@ class C(Generic[*Ts]): pass
tests = [
# Alias # Args # Expected result
('C[*Ts]', '[()]', 'C[()]'),
('tuple[*Ts]', '[()]', 'TypeError'), # Should be tuple[()]
('tuple[*Ts]', '[()]', 'tuple[()]'),
('Tuple[*Ts]', '[()]', 'Tuple[()]'),

('C[*Ts]', '[int]', 'C[int]'),
('tuple[*Ts]', '[int]', 'tuple[(int,),]'), # Should be tuple[int]
('tuple[*Ts]', '[int]', 'tuple[int]'),
('Tuple[*Ts]', '[int]', 'Tuple[int]'),

('C[*Ts]', '[int, str]', 'C[int, str]'),
('tuple[*Ts]', '[int, str]', 'TypeError'), # Should be tuple[int, str]
('tuple[*Ts]', '[int, str]', 'tuple[int, str]'),
('Tuple[*Ts]', '[int, str]', 'Tuple[int, str]'),

('C[*Ts]', '[*tuple_type[int]]', 'C[*tuple_type[int]]'), # Should be C[int]
('tuple[*Ts]', '[*tuple_type[int]]', 'tuple[(*tuple_type[int],),]'), # Should be tuple[int]
('tuple[*Ts]', '[*tuple_type[int]]', 'tuple[*tuple_type[int]]'), # Should be tuple[int]
('Tuple[*Ts]', '[*tuple_type[int]]', 'Tuple[*tuple_type[int]]'), # Should be Tuple[int]

('C[*Ts]', '[*tuple_type[*Ts]]', 'C[*tuple_type[*Ts]]'), # Should be C[*Ts]
('tuple[*Ts]', '[*tuple_type[*Ts]]', 'tuple[(*tuple_type[*Ts],),]'), # Should be tuple[*Ts]
('tuple[*Ts]', '[*tuple_type[*Ts]]', 'tuple[*tuple_type[*Ts]]'), # Should be tuple[*Ts]
('Tuple[*Ts]', '[*tuple_type[*Ts]]', 'Tuple[*tuple_type[*Ts]]'), # Should be Tuple[*Ts]

('C[*Ts]', '[*tuple_type[int, str]]', 'C[*tuple_type[int, str]]'), # Should be C[int, str]
('tuple[*Ts]', '[*tuple_type[int, str]]', 'tuple[(*tuple_type[int, str],),]'), # Should be tuple[int, str]
('tuple[*Ts]', '[*tuple_type[int, str]]', 'tuple[*tuple_type[int, str]]'), # Should be tuple[int, str]
('Tuple[*Ts]', '[*tuple_type[int, str]]', 'Tuple[*tuple_type[int, str]]'), # Should be Tuple[int, str]

('C[*Ts]', '[tuple_type[int, ...]]', 'C[tuple_type[int, ...]]'),
('tuple[*Ts]', '[tuple_type[int, ...]]', 'tuple[(tuple_type[int, ...],),]'), # Should be tuple[tuple_type[int, ...]]
('tuple[*Ts]', '[tuple_type[int, ...]]', 'tuple[tuple_type[int, ...]]'),
('Tuple[*Ts]', '[tuple_type[int, ...]]', 'Tuple[tuple_type[int, ...]]'),

('C[*Ts]', '[tuple_type[int, ...], tuple_type[str, ...]]', 'C[tuple_type[int, ...], tuple_type[str, ...]]'),
('tuple[*Ts]', '[tuple_type[int, ...], tuple_type[str, ...]]', 'TypeError'), # Should be tuple[tuple_type[int, ...], tuple_type[str, ...]]
('tuple[*Ts]', '[tuple_type[int, ...], tuple_type[str, ...]]', 'tuple[tuple_type[int, ...], tuple_type[str, ...]]'),
('Tuple[*Ts]', '[tuple_type[int, ...], tuple_type[str, ...]]', 'Tuple[tuple_type[int, ...], tuple_type[str, ...]]'),

('C[*Ts]', '[*tuple_type[int, ...]]', 'C[*tuple_type[int, ...]]'),
('tuple[*Ts]', '[*tuple_type[int, ...]]', 'tuple[(*tuple_type[int, ...],),]'), # Should be tuple[*tuple_type[int, ...]]
('tuple[*Ts]', '[*tuple_type[int, ...]]', 'tuple[*tuple_type[int, ...]]'),
('Tuple[*Ts]', '[*tuple_type[int, ...]]', 'Tuple[*tuple_type[int, ...]]'),

# Technically, multiple unpackings are forbidden by PEP 646, but we
# choose to be less restrictive at runtime, to allow folks room
# to experiment. So all three of these should be valid.
('C[*Ts]', '[*tuple_type[int, ...], *tuple_type[str, ...]]', 'C[*tuple_type[int, ...], *tuple_type[str, ...]]'),
# Should be tuple[*tuple_type[int, ...], *tuple_type[str, ...]], to match the other two.
('tuple[*Ts]', '[*tuple_type[int, ...], *tuple_type[str, ...]]', 'TypeError'),
('tuple[*Ts]', '[*tuple_type[int, ...], *tuple_type[str, ...]]', 'tuple[*tuple_type[int, ...], *tuple_type[str, ...]]'),
('Tuple[*Ts]', '[*tuple_type[int, ...], *tuple_type[str, ...]]', 'Tuple[*tuple_type[int, ...], *tuple_type[str, ...]]'),

('C[*Ts]', '[*Ts]', 'C[*Ts]'),
('tuple[*Ts]', '[*Ts]', 'tuple[(*Ts,),]'), # Should be tuple[*Ts]
('tuple[*Ts]', '[*Ts]', 'tuple[*Ts]'),
('Tuple[*Ts]', '[*Ts]', 'Tuple[*Ts]'),

('C[*Ts]', '[T, *Ts]', 'C[T, *Ts]'),
('tuple[*Ts]', '[T, *Ts]', 'TypeError'), # Should be tuple[T, *Ts]
('tuple[*Ts]', '[T, *Ts]', 'tuple[T, *Ts]'),
('Tuple[*Ts]', '[T, *Ts]', 'Tuple[T, *Ts]'),

('C[*Ts]', '[*Ts, T]', 'C[*Ts, T]'),
('tuple[*Ts]', '[*Ts, T]', 'TypeError'), # Should be tuple[*Ts, T]
('tuple[*Ts]', '[*Ts, T]', 'tuple[*Ts, T]'),
('Tuple[*Ts]', '[*Ts, T]', 'Tuple[*Ts, T]'),

('C[T, *Ts]', '[int]', 'C[int]'),
('tuple[T, *Ts]', '[int]', 'TypeError'), # Should be tuple[int]
('tuple[T, *Ts]', '[int]', 'tuple[int]'),
('Tuple[T, *Ts]', '[int]', 'Tuple[int]'),

('C[T, *Ts]', '[int, str]', 'C[int, str]'),
('tuple[T, *Ts]', '[int, str]', 'tuple[int, (str,)]'), # Should be tuple[int, str]
('tuple[T, *Ts]', '[int, str]', 'tuple[int, str]'),
('Tuple[T, *Ts]', '[int, str]', 'Tuple[int, str]'),

('C[T, *Ts]', '[int, str, bool]', 'C[int, str, bool]'),
('tuple[T, *Ts]', '[int, str, bool]', 'TypeError'), # Should be tuple[int, str, bool]
('tuple[T, *Ts]', '[int, str, bool]', 'tuple[int, str, bool]'),
('Tuple[T, *Ts]', '[int, str, bool]', 'Tuple[int, str, bool]'),

('C[T, *Ts]', '[*tuple[int, ...]]', 'C[*tuple[int, ...]]'), # Should be C[int, *tuple[int, ...]]
('C[T, *Ts]', '[*Tuple[int, ...]]', 'TypeError'), # Ditto
('tuple[T, *Ts]', '[*tuple_type[int, ...]]', 'TypeError'), # Should be tuple[int, *tuple[int, ...]]
('Tuple[T, *Ts]', '[*tuple[int, ...]]', 'Tuple[*tuple[int, ...]]'), # Ditto
('Tuple[T, *Ts]', '[*Tuple[int, ...]]', 'TypeError'), # Ditto
('tuple[T, *Ts]', '[*tuple[int, ...]]', 'tuple[*tuple[int, ...]]'), # Should be tuple[int, *tuple[int, ...]]
('tuple[T, *Ts]', '[*Tuple[int, ...]]', 'TypeError'), # Should be tuple[int, *Tuple[int, ...]]
('Tuple[T, *Ts]', '[*tuple[int, ...]]', 'Tuple[*tuple[int, ...]]'), # Should be Tuple[int, *tuple[int, ...]]
('Tuple[T, *Ts]', '[*Tuple[int, ...]]', 'TypeError'), # Should be Tuple[int, *Tuple[int, ...]]

('C[*Ts, T]', '[int]', 'C[int]'),
('tuple[*Ts, T]', '[int]', 'TypeError'), # Should be tuple[int]
('tuple[*Ts, T]', '[int]', 'tuple[int]'),
('Tuple[*Ts, T]', '[int]', 'Tuple[int]'),

('C[*Ts, T]', '[int, str]', 'C[int, str]'),
('tuple[*Ts, T]', '[int, str]', 'tuple[(int,), str]'), # Should be tuple[int, str]
('tuple[*Ts, T]', '[int, str]', 'tuple[int, str]'),
('Tuple[*Ts, T]', '[int, str]', 'Tuple[int, str]'),

('C[*Ts, T]', '[int, str, bool]', 'C[int, str, bool]'),
('tuple[*Ts, T]', '[int, str, bool]', 'TypeError'), # Should be tuple[int, str, bool]
('tuple[*Ts, T]', '[int, str, bool]', 'tuple[int, str, bool]'),
('Tuple[*Ts, T]', '[int, str, bool]', 'Tuple[int, str, bool]'),

('generic[T, *tuple_type[int, ...]]', '[str]', 'generic[str, *tuple_type[int, ...]]'),
Expand Down Expand Up @@ -945,7 +945,7 @@ def test_var_substitution(self):
T2 = TypeVar('T2')
class G(Generic[Unpack[Ts]]): pass

for A in G, Tuple:
for A in G, Tuple, tuple:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There need to be more tests for cases where we substitute an unpacked tuple into the TypeVarTuple. I found a crash on your branch:

 % ./python.exe 
Python 3.11.0a6+ (heads/serhiy-storchaka-typevartuple-subst-c:a35af462a3, Mar 11 2022, 19:02:45) [Clang 13.0.0 (clang-1300.0.29.3)] on darwin
Type "help", "copyright", "credits" or "license" for more information
>>> from typing import Unpack, TypeVarTuple
>>> Ts = TypeVarTuple("Ts")
>>> t = tuple[Ts]
>>> t[int, Unpack[tuple[int, str]], str]
Assertion failed: (iparam != varparam), function _Py_subs_parameters, file genericaliasobject.c, line 382.
zsh: abort      ./python.exe

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Is not tuple[Ts] illegal?

I have added a runtime error for this case. It is consistent for Tuple, tuple and user generics.

B = A[Unpack[Ts]]
self.assertEqual(B[()], A[()])
self.assertEqual(B[float], A[float])
Expand Down Expand Up @@ -984,6 +984,21 @@ def test_bad_var_substitution(self):
T2 = TypeVar('T2')
class G(Generic[Unpack[Ts]]): pass

for A in G, Tuple, tuple:
B = A[Ts]
with self.assertRaises(TypeError):
B[int, str]

C = A[T, T2]
with self.assertRaises(TypeError):
C[Unpack[Ts]]

def test_repr_is_correct(self):
Ts = TypeVarTuple('Ts')
T = TypeVar('T')
T2 = TypeVar('T2')
class G(Generic[Unpack[Ts]]): pass

for A in G, Tuple:
B = A[T, Unpack[Ts], str, T2]
with self.assertRaises(TypeError):
Expand Down
10 changes: 7 additions & 3 deletions Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1019,7 +1019,7 @@ def __repr__(self):
return self.__name__

def __typing_subst__(self, arg):
raise AssertionError
raise TypeError("Substitution of bare TypeVarTuple is not supported")


class ParamSpecArgs(_Final, _Immutable, _root=True):
Expand Down Expand Up @@ -1689,11 +1689,15 @@ def __repr__(self):
return '*' + repr(self.__args__[0])

def __getitem__(self, args):
if (len(self.__parameters__) == 1 and
isinstance(self.__parameters__[0], TypeVarTuple)):
if self.__typing_unpacked__():
return args
return super().__getitem__(args)

def __typing_unpacked__(self):
# If x is Unpack[tuple[...]], __parameters__ will be empty.
return bool(self.__parameters__ and
isinstance(self.__parameters__[0], TypeVarTuple))


class Generic:
"""Abstract base class for generic types.
Expand Down
Loading