Skip to content

Commit 4fab260

Browse files
bpo-43224: Implement substitution of unpacked TypeVarTuple
1 parent 32bf359 commit 4fab260

File tree

2 files changed

+37
-62
lines changed

2 files changed

+37
-62
lines changed

Lib/test/test_typing.py

Lines changed: 35 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,10 @@ def test_cannot_be_called(self):
390390

391391
class TypeVarTupleTests(BaseTestCase):
392392

393+
def assertEndsWith(self, string, tail):
394+
if not string.endswith(tail):
395+
self.fail(f"String {string!r} does not end with {tail!r}")
396+
393397
def test_instance_is_equal_to_itself(self):
394398
Ts = TypeVarTuple('Ts')
395399
self.assertEqual(Ts, Ts)
@@ -449,78 +453,51 @@ def test_variadic_class_repr_is_correct(self):
449453
Ts = TypeVarTuple('Ts')
450454
class A(Generic[Unpack[Ts]]): pass
451455

452-
self.assertTrue(repr(A[()]).endswith('A[()]'))
453-
self.assertTrue(repr(A[float]).endswith('A[float]'))
454-
self.assertTrue(repr(A[float, str]).endswith('A[float, str]'))
455-
self.assertTrue(repr(
456-
A[Unpack[tuple[int, ...]]]
457-
).endswith(
458-
'A[*tuple[int, ...]]'
459-
))
460-
self.assertTrue(repr(
461-
A[float, Unpack[tuple[int, ...]]]
462-
).endswith(
463-
'A[float, *tuple[int, ...]]'
464-
))
465-
self.assertTrue(repr(
466-
A[Unpack[tuple[int, ...]], str]
467-
).endswith(
468-
'A[*tuple[int, ...], str]'
469-
))
470-
self.assertTrue(repr(
471-
A[float, Unpack[tuple[int, ...]], str]
472-
).endswith(
473-
'A[float, *tuple[int, ...], str]'
474-
))
456+
self.assertEndsWith(repr(A[()]), 'A[()]')
457+
self.assertEndsWith(repr(A[float]), 'A[float]')
458+
self.assertEndsWith(repr(A[float, str]), 'A[float, str]')
459+
self.assertEndsWith(repr(A[Unpack[tuple[int, ...]]]),
460+
'A[*tuple[int, ...]]')
461+
self.assertEndsWith(repr(A[float, Unpack[tuple[int, ...]]]),
462+
'A[float, *tuple[int, ...]]')
463+
self.assertEndsWith(repr(A[Unpack[tuple[int, ...]], str]),
464+
'A[*tuple[int, ...], str]')
465+
self.assertEndsWith(repr(A[float, Unpack[tuple[int, ...]], str]),
466+
'A[float, *tuple[int, ...], str]')
475467

476468
def test_variadic_class_alias_repr_is_correct(self):
477469
Ts = TypeVarTuple('Ts')
478470
class A(Generic[Unpack[Ts]]): pass
479471

480472
B = A[Unpack[Ts]]
481-
self.assertTrue(repr(B).endswith('A[*Ts]'))
482-
with self.assertRaises(NotImplementedError):
483-
B[()]
484-
with self.assertRaises(NotImplementedError):
485-
B[float]
486-
with self.assertRaises(NotImplementedError):
487-
B[float, str]
473+
self.assertEndsWith(repr(B), 'A[*Ts]')
474+
self.assertEndsWith(repr(B[()]), 'A[()]')
475+
self.assertEndsWith(repr(B[float]), 'A[float]')
476+
self.assertEndsWith(repr(B[float, str]), 'A[float, str]')
488477

489478
C = A[Unpack[Ts], int]
490-
self.assertTrue(repr(C).endswith('A[*Ts, int]'))
491-
with self.assertRaises(NotImplementedError):
492-
C[()]
493-
with self.assertRaises(NotImplementedError):
494-
C[float]
495-
with self.assertRaises(NotImplementedError):
496-
C[float, str]
479+
self.assertEndsWith(repr(C), 'A[*Ts, int]')
480+
self.assertEndsWith(repr(C[()]), 'A[int]')
481+
self.assertEndsWith(repr(C[float]), 'A[float, int]')
482+
self.assertEndsWith(repr(C[float, str]), 'A[float, str, int]')
497483

498484
D = A[int, Unpack[Ts]]
499-
self.assertTrue(repr(D).endswith('A[int, *Ts]'))
500-
with self.assertRaises(NotImplementedError):
501-
D[()]
502-
with self.assertRaises(NotImplementedError):
503-
D[float]
504-
with self.assertRaises(NotImplementedError):
505-
D[float, str]
485+
self.assertEndsWith(repr(D), 'A[int, *Ts]')
486+
self.assertEndsWith(repr(D[()]), 'A[int]')
487+
self.assertEndsWith(repr(D[float]), 'A[int, float]')
488+
self.assertEndsWith(repr(D[float, str]), 'A[int, float, str]')
506489

507490
E = A[int, Unpack[Ts], str]
508-
self.assertTrue(repr(E).endswith('A[int, *Ts, str]'))
509-
with self.assertRaises(NotImplementedError):
510-
E[()]
511-
with self.assertRaises(NotImplementedError):
512-
E[float]
513-
with self.assertRaises(NotImplementedError):
514-
E[float, bool]
491+
self.assertEndsWith(repr(E), 'A[int, *Ts, str]')
492+
self.assertEndsWith(repr(E[()]), 'A[int, str]')
493+
self.assertEndsWith(repr(E[float]), 'A[int, float, str]')
494+
self.assertEndsWith(repr(E[float, str]), 'A[int, float, str, str]')
515495

516496
F = A[Unpack[Ts], Unpack[tuple[str, ...]]]
517-
self.assertTrue(repr(F).endswith('A[*Ts, *tuple[str, ...]]'))
518-
with self.assertRaises(NotImplementedError):
519-
F[()]
520-
with self.assertRaises(NotImplementedError):
521-
F[float]
522-
with self.assertRaises(NotImplementedError):
523-
F[float, int]
497+
self.assertEndsWith(repr(F), 'A[*Ts, *tuple[str, ...]]')
498+
self.assertEndsWith(repr(F[()]), 'A[*tuple[str, ...]]')
499+
self.assertEndsWith(repr(F[float]), 'A[float, *tuple[str, ...]]')
500+
self.assertEndsWith(repr(F[float, str]), 'A[float, str, *tuple[str, ...]]')
524501

525502
def test_cannot_subclass_class(self):
526503
with self.assertRaises(TypeError):

Lib/typing.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1281,10 +1281,8 @@ def _determine_new_args(self, args):
12811281
# anything more exotic than a plain `TypeVar`, we need to consider
12821282
# edge cases.
12831283

1284-
if any(isinstance(p, TypeVarTuple) for p in self.__parameters__):
1285-
raise NotImplementedError(
1286-
"Type substitution for TypeVarTuples is not yet implemented"
1287-
)
1284+
if len(self.__parameters__) == 1 and isinstance(self.__parameters__[0], TypeVarTuple):
1285+
args = args,
12881286
# In the example above, this would be {T3: str}
12891287
new_arg_by_param = dict(zip(self.__parameters__, args))
12901288

0 commit comments

Comments
 (0)