Skip to content

Commit 5b1b9ea

Browse files
bpo-43224: Implement substitution of unpacked TypeVarTuple (GH-31800)
1 parent c83fc9c commit 5b1b9ea

File tree

2 files changed

+117
-75
lines changed

2 files changed

+117
-75
lines changed

Lib/test/test_typing.py

Lines changed: 85 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,10 @@ def test_cannot_be_called(self):
411411

412412
class TypeVarTupleTests(BaseTestCase):
413413

414+
def assertEndsWith(self, string, tail):
415+
if not string.endswith(tail):
416+
self.fail(f"String {string!r} does not end with {tail!r}")
417+
414418
def test_instance_is_equal_to_itself(self):
415419
Ts = TypeVarTuple('Ts')
416420
self.assertEqual(Ts, Ts)
@@ -457,6 +461,56 @@ def test_tuple_args_and_parameters_are_correct(self):
457461
self.assertEqual(t2.__args__, (Unpack[Ts],))
458462
self.assertEqual(t2.__parameters__, (Ts,))
459463

464+
def test_var_substitution(self):
465+
Ts = TypeVarTuple('Ts')
466+
T = TypeVar('T')
467+
T2 = TypeVar('T2')
468+
class G(Generic[Unpack[Ts]]): pass
469+
470+
for A in G, Tuple:
471+
B = A[Unpack[Ts]]
472+
if A != Tuple:
473+
self.assertEqual(B[()], A[()])
474+
self.assertEqual(B[float], A[float])
475+
self.assertEqual(B[float, str], A[float, str])
476+
477+
C = List[A[Unpack[Ts]]]
478+
if A != Tuple:
479+
self.assertEqual(C[()], List[A[()]])
480+
self.assertEqual(C[float], List[A[float]])
481+
self.assertEqual(C[float, str], List[A[float, str]])
482+
483+
D = A[T, Unpack[Ts], T2]
484+
with self.assertRaises(TypeError):
485+
D[()]
486+
with self.assertRaises(TypeError):
487+
D[float]
488+
self.assertEqual(D[float, str], A[float, str])
489+
self.assertEqual(D[float, str, int], A[float, str, int])
490+
self.assertEqual(D[float, str, int, bytes], A[float, str, int, bytes])
491+
492+
E = Tuple[List[T], A[Unpack[Ts]], List[T2]]
493+
with self.assertRaises(TypeError):
494+
E[()]
495+
with self.assertRaises(TypeError):
496+
E[float]
497+
if A != Tuple:
498+
self.assertEqual(E[float, str],
499+
Tuple[List[float], A[()], List[str]])
500+
self.assertEqual(E[float, str, int],
501+
Tuple[List[float], A[str], List[int]])
502+
self.assertEqual(E[float, str, int, bytes],
503+
Tuple[List[float], A[str, int], List[bytes]])
504+
505+
def test_repr_is_correct(self):
506+
Ts = TypeVarTuple('Ts')
507+
self.assertEqual(repr(Ts), 'Ts')
508+
self.assertEqual(repr(Unpack[Ts]), '*Ts')
509+
self.assertEqual(repr(tuple[Unpack[Ts]]), 'tuple[*Ts]')
510+
self.assertEqual(repr(Tuple[Unpack[Ts]]), 'typing.Tuple[*Ts]')
511+
self.assertEqual(repr(Unpack[tuple[Unpack[Ts]]]), '*tuple[*Ts]')
512+
self.assertEqual(repr(Unpack[Tuple[Unpack[Ts]]]), '*typing.Tuple[*Ts]')
513+
460514
def test_repr_is_correct(self):
461515
Ts = TypeVarTuple('Ts')
462516
self.assertEqual(repr(Ts), 'Ts')
@@ -470,78 +524,51 @@ def test_variadic_class_repr_is_correct(self):
470524
Ts = TypeVarTuple('Ts')
471525
class A(Generic[Unpack[Ts]]): pass
472526

473-
self.assertTrue(repr(A[()]).endswith('A[()]'))
474-
self.assertTrue(repr(A[float]).endswith('A[float]'))
475-
self.assertTrue(repr(A[float, str]).endswith('A[float, str]'))
476-
self.assertTrue(repr(
477-
A[Unpack[tuple[int, ...]]]
478-
).endswith(
479-
'A[*tuple[int, ...]]'
480-
))
481-
self.assertTrue(repr(
482-
A[float, Unpack[tuple[int, ...]]]
483-
).endswith(
484-
'A[float, *tuple[int, ...]]'
485-
))
486-
self.assertTrue(repr(
487-
A[Unpack[tuple[int, ...]], str]
488-
).endswith(
489-
'A[*tuple[int, ...], str]'
490-
))
491-
self.assertTrue(repr(
492-
A[float, Unpack[tuple[int, ...]], str]
493-
).endswith(
494-
'A[float, *tuple[int, ...], str]'
495-
))
527+
self.assertEndsWith(repr(A[()]), 'A[()]')
528+
self.assertEndsWith(repr(A[float]), 'A[float]')
529+
self.assertEndsWith(repr(A[float, str]), 'A[float, str]')
530+
self.assertEndsWith(repr(A[Unpack[tuple[int, ...]]]),
531+
'A[*tuple[int, ...]]')
532+
self.assertEndsWith(repr(A[float, Unpack[tuple[int, ...]]]),
533+
'A[float, *tuple[int, ...]]')
534+
self.assertEndsWith(repr(A[Unpack[tuple[int, ...]], str]),
535+
'A[*tuple[int, ...], str]')
536+
self.assertEndsWith(repr(A[float, Unpack[tuple[int, ...]], str]),
537+
'A[float, *tuple[int, ...], str]')
496538

497539
def test_variadic_class_alias_repr_is_correct(self):
498540
Ts = TypeVarTuple('Ts')
499541
class A(Generic[Unpack[Ts]]): pass
500542

501543
B = A[Unpack[Ts]]
502-
self.assertTrue(repr(B).endswith('A[*Ts]'))
503-
with self.assertRaises(NotImplementedError):
504-
B[()]
505-
with self.assertRaises(NotImplementedError):
506-
B[float]
507-
with self.assertRaises(NotImplementedError):
508-
B[float, str]
544+
self.assertEndsWith(repr(B), 'A[*Ts]')
545+
self.assertEndsWith(repr(B[()]), 'A[()]')
546+
self.assertEndsWith(repr(B[float]), 'A[float]')
547+
self.assertEndsWith(repr(B[float, str]), 'A[float, str]')
509548

510549
C = A[Unpack[Ts], int]
511-
self.assertTrue(repr(C).endswith('A[*Ts, int]'))
512-
with self.assertRaises(NotImplementedError):
513-
C[()]
514-
with self.assertRaises(NotImplementedError):
515-
C[float]
516-
with self.assertRaises(NotImplementedError):
517-
C[float, str]
550+
self.assertEndsWith(repr(C), 'A[*Ts, int]')
551+
self.assertEndsWith(repr(C[()]), 'A[int]')
552+
self.assertEndsWith(repr(C[float]), 'A[float, int]')
553+
self.assertEndsWith(repr(C[float, str]), 'A[float, str, int]')
518554

519555
D = A[int, Unpack[Ts]]
520-
self.assertTrue(repr(D).endswith('A[int, *Ts]'))
521-
with self.assertRaises(NotImplementedError):
522-
D[()]
523-
with self.assertRaises(NotImplementedError):
524-
D[float]
525-
with self.assertRaises(NotImplementedError):
526-
D[float, str]
556+
self.assertEndsWith(repr(D), 'A[int, *Ts]')
557+
self.assertEndsWith(repr(D[()]), 'A[int]')
558+
self.assertEndsWith(repr(D[float]), 'A[int, float]')
559+
self.assertEndsWith(repr(D[float, str]), 'A[int, float, str]')
527560

528561
E = A[int, Unpack[Ts], str]
529-
self.assertTrue(repr(E).endswith('A[int, *Ts, str]'))
530-
with self.assertRaises(NotImplementedError):
531-
E[()]
532-
with self.assertRaises(NotImplementedError):
533-
E[float]
534-
with self.assertRaises(NotImplementedError):
535-
E[float, bool]
562+
self.assertEndsWith(repr(E), 'A[int, *Ts, str]')
563+
self.assertEndsWith(repr(E[()]), 'A[int, str]')
564+
self.assertEndsWith(repr(E[float]), 'A[int, float, str]')
565+
self.assertEndsWith(repr(E[float, str]), 'A[int, float, str, str]')
536566

537567
F = A[Unpack[Ts], Unpack[tuple[str, ...]]]
538-
self.assertTrue(repr(F).endswith('A[*Ts, *tuple[str, ...]]'))
539-
with self.assertRaises(NotImplementedError):
540-
F[()]
541-
with self.assertRaises(NotImplementedError):
542-
F[float]
543-
with self.assertRaises(NotImplementedError):
544-
F[float, int]
568+
self.assertEndsWith(repr(F), 'A[*Ts, *tuple[str, ...]]')
569+
self.assertEndsWith(repr(F[()]), 'A[*tuple[str, ...]]')
570+
self.assertEndsWith(repr(F[float]), 'A[float, *tuple[str, ...]]')
571+
self.assertEndsWith(repr(F[float, str]), 'A[float, str, *tuple[str, ...]]')
545572

546573
def test_cannot_subclass_class(self):
547574
with self.assertRaises(TypeError):

Lib/typing.py

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1297,30 +1297,39 @@ def _determine_new_args(self, args):
12971297
# anything more exotic than a plain `TypeVar`, we need to consider
12981298
# edge cases.
12991299

1300-
if any(isinstance(p, TypeVarTuple) for p in self.__parameters__):
1301-
raise NotImplementedError(
1302-
"Type substitution for TypeVarTuples is not yet implemented"
1303-
)
1300+
params = self.__parameters__
13041301
# In the example above, this would be {T3: str}
1305-
new_arg_by_param = dict(zip(self.__parameters__, args))
1302+
new_arg_by_param = {}
1303+
for i, param in enumerate(params):
1304+
if isinstance(param, TypeVarTuple):
1305+
j = len(args) - (len(params) - i - 1)
1306+
if j < i:
1307+
raise TypeError(f"Too few arguments for {self}")
1308+
new_arg_by_param.update(zip(params[:i], args[:i]))
1309+
new_arg_by_param[param] = args[i: j]
1310+
new_arg_by_param.update(zip(params[i + 1:], args[j:]))
1311+
break
1312+
else:
1313+
new_arg_by_param.update(zip(params, args))
13061314

13071315
new_args = []
13081316
for old_arg in self.__args__:
13091317

1310-
if _is_unpacked_typevartuple(old_arg):
1311-
original_typevartuple = old_arg.__parameters__[0]
1312-
new_arg = new_arg_by_param[original_typevartuple]
1318+
substfunc = getattr(old_arg, '__typing_subst__', None)
1319+
if substfunc:
1320+
new_arg = substfunc(new_arg_by_param[old_arg])
13131321
else:
1314-
substfunc = getattr(old_arg, '__typing_subst__', None)
1315-
if substfunc:
1316-
new_arg = substfunc(new_arg_by_param[old_arg])
1322+
subparams = getattr(old_arg, '__parameters__', ())
1323+
if not subparams:
1324+
new_arg = old_arg
13171325
else:
1318-
subparams = getattr(old_arg, '__parameters__', ())
1319-
if not subparams:
1320-
new_arg = old_arg
1321-
else:
1322-
subargs = tuple(new_arg_by_param[x] for x in subparams)
1323-
new_arg = old_arg[subargs]
1326+
subargs = []
1327+
for x in subparams:
1328+
if isinstance(x, TypeVarTuple):
1329+
subargs.extend(new_arg_by_param[x])
1330+
else:
1331+
subargs.append(new_arg_by_param[x])
1332+
new_arg = old_arg[tuple(subargs)]
13241333

13251334
if self.__origin__ == collections.abc.Callable and isinstance(new_arg, tuple):
13261335
# Consider the following `Callable`.
@@ -1612,6 +1621,12 @@ def __repr__(self):
16121621
# a single item.
16131622
return '*' + repr(self.__args__[0])
16141623

1624+
def __getitem__(self, args):
1625+
if (len(self.__parameters__) == 1 and
1626+
isinstance(self.__parameters__[0], TypeVarTuple)):
1627+
return args
1628+
return super().__getitem__(args)
1629+
16151630

16161631
class Generic:
16171632
"""Abstract base class for generic types.

0 commit comments

Comments
 (0)