Skip to content

Commit e2772a5

Browse files
committed
gh-148207: add additional keywords to typing.TypeVarTuple
1 parent 7e0a0be commit e2772a5

File tree

6 files changed

+211
-38
lines changed

6 files changed

+211
-38
lines changed

Doc/library/typing.rst

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1980,7 +1980,7 @@ without the dedicated syntax, as documented below.
19801980

19811981
.. _typevartuple:
19821982

1983-
.. class:: TypeVarTuple(name, *, default=typing.NoDefault)
1983+
.. class:: TypeVarTuple(name, *, bound=None, covariant=False, contravariant=False, infer_variance=False, default=typing.NoDefault)
19841984

19851985
Type variable tuple. A specialized form of :ref:`type variable <typevar>`
19861986
that enables *variadic* generics.
@@ -2090,6 +2090,22 @@ without the dedicated syntax, as documented below.
20902090

20912091
The name of the type variable tuple.
20922092

2093+
.. attribute:: __bound__
2094+
2095+
The upper bound of the type variable tuple, if any.
2096+
2097+
.. attribute:: __covariant__
2098+
2099+
Whether the type variable tuple has been explicitly marked as covariant.
2100+
2101+
.. attribute:: __contravariant__
2102+
2103+
Whether the type variable tuple has been explicitly marked as contravariant.
2104+
2105+
.. attribute:: __infer_variance__
2106+
2107+
Whether the type variable tuple's variance should be inferred by type checkers.
2108+
20932109
.. attribute:: __default__
20942110

20952111
The default value of the type variable tuple, or :data:`typing.NoDefault` if it
@@ -2116,6 +2132,12 @@ without the dedicated syntax, as documented below.
21162132

21172133
.. versionadded:: 3.13
21182134

2135+
Type variable tuples created with ``covariant=True`` or
2136+
``contravariant=True`` can be used to declare covariant or contravariant
2137+
generic types. The ``bound`` argument is also accepted, similar to
2138+
:class:`TypeVar`. However the actual semantics of these keywords are yet to
2139+
be decided.
2140+
21192141
.. versionadded:: 3.11
21202142

21212143
.. versionchanged:: 3.12
@@ -2127,6 +2149,11 @@ without the dedicated syntax, as documented below.
21272149

21282150
Support for default values was added.
21292151

2152+
.. versionchanged:: 3.15
2153+
2154+
Added support for the ``bound``, ``covariant``, ``contravariant``, and
2155+
``infer_variance`` parameters.
2156+
21302157
.. class:: ParamSpec(name, *, bound=None, covariant=False, contravariant=False, default=typing.NoDefault)
21312158

21322159
Parameter specification variable. A specialized version of

Doc/whatsnew/3.15.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1224,6 +1224,11 @@ typing
12241224
as it was incorrectly inferred in runtime before.
12251225
(Contributed by Nikita Sobolev in :gh:`137191`.)
12261226

1227+
* :class:`~typing.TypeVarTuple` now accepts ``bound``, ``covariant``,
1228+
``contravariant``, and ``infer_variance`` keyword arguments, matching the
1229+
interface of :class:`~typing.TypeVar` and :class:`~typing.ParamSpec`.
1230+
``bound`` semantics remain undefined in the specification.
1231+
12271232

12281233
unicodedata
12291234
-----------

Lib/test/test_typing.py

Lines changed: 73 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -780,7 +780,7 @@ def test_typevartuple_none(self):
780780
self.assertIs(U_None.__default__, None)
781781
self.assertIs(U_None.has_default(), True)
782782

783-
class X[**Ts]: ...
783+
class X[*Ts]: ...
784784
Ts, = X.__type_params__
785785
self.assertIs(Ts.__default__, NoDefault)
786786
self.assertIs(Ts.has_default(), False)
@@ -1288,6 +1288,57 @@ def test_cannot_call_instance(self):
12881288
with self.assertRaises(TypeError):
12891289
Ts()
12901290

1291+
def test_default_variance(self):
1292+
Ts = TypeVarTuple('Ts')
1293+
self.assertIs(Ts.__covariant__, False)
1294+
self.assertIs(Ts.__contravariant__, False)
1295+
self.assertIs(Ts.__infer_variance__, False)
1296+
self.assertIsNone(Ts.__bound__)
1297+
1298+
def test_covariant(self):
1299+
Ts_co = TypeVarTuple('Ts_co', covariant=True)
1300+
self.assertIs(Ts_co.__covariant__, True)
1301+
self.assertIs(Ts_co.__contravariant__, False)
1302+
self.assertIs(Ts_co.__infer_variance__, False)
1303+
1304+
def test_contravariant(self):
1305+
Ts_contra = TypeVarTuple('Ts_contra', contravariant=True)
1306+
self.assertIs(Ts_contra.__covariant__, False)
1307+
self.assertIs(Ts_contra.__contravariant__, True)
1308+
self.assertIs(Ts_contra.__infer_variance__, False)
1309+
1310+
def test_infer_variance(self):
1311+
Ts = TypeVarTuple('Ts', infer_variance=True)
1312+
self.assertIs(Ts.__covariant__, False)
1313+
self.assertIs(Ts.__contravariant__, False)
1314+
self.assertIs(Ts.__infer_variance__, True)
1315+
1316+
def test_bound(self):
1317+
Ts_bound = TypeVarTuple('Ts_bound', bound=int)
1318+
self.assertIs(Ts_bound.__bound__, int)
1319+
Ts_no_bound = TypeVarTuple('Ts_no_bound')
1320+
self.assertIsNone(Ts_no_bound.__bound__)
1321+
1322+
def test_no_bivariant(self):
1323+
with self.assertRaises(ValueError):
1324+
TypeVarTuple('Ts', covariant=True, contravariant=True)
1325+
1326+
def test_cannot_combine_explicit_and_infer(self):
1327+
with self.assertRaises(ValueError):
1328+
TypeVarTuple('Ts', covariant=True, infer_variance=True)
1329+
with self.assertRaises(ValueError):
1330+
TypeVarTuple('Ts', contravariant=True, infer_variance=True)
1331+
1332+
def test_repr_with_variance(self):
1333+
Ts = TypeVarTuple('Ts')
1334+
self.assertEqual(repr(Ts), '~Ts')
1335+
Ts_co = TypeVarTuple('Ts_co', covariant=True)
1336+
self.assertEqual(repr(Ts_co), '+Ts_co')
1337+
Ts_contra = TypeVarTuple('Ts_contra', contravariant=True)
1338+
self.assertEqual(repr(Ts_contra), '-Ts_contra')
1339+
Ts_infer = TypeVarTuple('Ts_infer', infer_variance=True)
1340+
self.assertEqual(repr(Ts_infer), 'Ts_infer')
1341+
12911342
def test_unpacked_typevartuple_is_equal_to_itself(self):
12921343
Ts = TypeVarTuple('Ts')
12931344
self.assertEqual((*Ts,)[0], (*Ts,)[0])
@@ -1427,16 +1478,16 @@ def test_repr_is_correct(self):
14271478
class G1(Generic[*Ts]): pass
14281479
class G2(Generic[Unpack[Ts]]): pass
14291480

1430-
self.assertEqual(repr(Ts), 'Ts')
1481+
self.assertEqual(repr(Ts), '~Ts')
14311482

1432-
self.assertEqual(repr((*Ts,)[0]), 'typing.Unpack[Ts]')
1433-
self.assertEqual(repr(Unpack[Ts]), 'typing.Unpack[Ts]')
1483+
self.assertEqual(repr((*Ts,)[0]), 'typing.Unpack[~Ts]')
1484+
self.assertEqual(repr(Unpack[Ts]), 'typing.Unpack[~Ts]')
14341485

1435-
self.assertEqual(repr(tuple[*Ts]), 'tuple[typing.Unpack[Ts]]')
1436-
self.assertEqual(repr(Tuple[Unpack[Ts]]), 'typing.Tuple[typing.Unpack[Ts]]')
1486+
self.assertEqual(repr(tuple[*Ts]), 'tuple[typing.Unpack[~Ts]]')
1487+
self.assertEqual(repr(Tuple[Unpack[Ts]]), 'typing.Tuple[typing.Unpack[~Ts]]')
14371488

1438-
self.assertEqual(repr(*tuple[*Ts]), '*tuple[typing.Unpack[Ts]]')
1439-
self.assertEqual(repr(Unpack[Tuple[Unpack[Ts]]]), 'typing.Unpack[typing.Tuple[typing.Unpack[Ts]]]')
1489+
self.assertEqual(repr(*tuple[*Ts]), '*tuple[typing.Unpack[~Ts]]')
1490+
self.assertEqual(repr(Unpack[Tuple[Unpack[Ts]]]), 'typing.Unpack[typing.Tuple[typing.Unpack[~Ts]]]')
14401491

14411492
def test_variadic_class_repr_is_correct(self):
14421493
Ts = TypeVarTuple('Ts')
@@ -1475,61 +1526,61 @@ def test_variadic_class_alias_repr_is_correct(self):
14751526
class A(Generic[Unpack[Ts]]): pass
14761527

14771528
B = A[*Ts]
1478-
self.assertEndsWith(repr(B), 'A[typing.Unpack[Ts]]')
1529+
self.assertEndsWith(repr(B), 'A[typing.Unpack[~Ts]]')
14791530
self.assertEndsWith(repr(B[()]), 'A[()]')
14801531
self.assertEndsWith(repr(B[float]), 'A[float]')
14811532
self.assertEndsWith(repr(B[float, str]), 'A[float, str]')
14821533

14831534
C = A[Unpack[Ts]]
1484-
self.assertEndsWith(repr(C), 'A[typing.Unpack[Ts]]')
1535+
self.assertEndsWith(repr(C), 'A[typing.Unpack[~Ts]]')
14851536
self.assertEndsWith(repr(C[()]), 'A[()]')
14861537
self.assertEndsWith(repr(C[float]), 'A[float]')
14871538
self.assertEndsWith(repr(C[float, str]), 'A[float, str]')
14881539

14891540
D = A[*Ts, int]
1490-
self.assertEndsWith(repr(D), 'A[typing.Unpack[Ts], int]')
1541+
self.assertEndsWith(repr(D), 'A[typing.Unpack[~Ts], int]')
14911542
self.assertEndsWith(repr(D[()]), 'A[int]')
14921543
self.assertEndsWith(repr(D[float]), 'A[float, int]')
14931544
self.assertEndsWith(repr(D[float, str]), 'A[float, str, int]')
14941545

14951546
E = A[Unpack[Ts], int]
1496-
self.assertEndsWith(repr(E), 'A[typing.Unpack[Ts], int]')
1547+
self.assertEndsWith(repr(E), 'A[typing.Unpack[~Ts], int]')
14971548
self.assertEndsWith(repr(E[()]), 'A[int]')
14981549
self.assertEndsWith(repr(E[float]), 'A[float, int]')
14991550
self.assertEndsWith(repr(E[float, str]), 'A[float, str, int]')
15001551

15011552
F = A[int, *Ts]
1502-
self.assertEndsWith(repr(F), 'A[int, typing.Unpack[Ts]]')
1553+
self.assertEndsWith(repr(F), 'A[int, typing.Unpack[~Ts]]')
15031554
self.assertEndsWith(repr(F[()]), 'A[int]')
15041555
self.assertEndsWith(repr(F[float]), 'A[int, float]')
15051556
self.assertEndsWith(repr(F[float, str]), 'A[int, float, str]')
15061557

15071558
G = A[int, Unpack[Ts]]
1508-
self.assertEndsWith(repr(G), 'A[int, typing.Unpack[Ts]]')
1559+
self.assertEndsWith(repr(G), 'A[int, typing.Unpack[~Ts]]')
15091560
self.assertEndsWith(repr(G[()]), 'A[int]')
15101561
self.assertEndsWith(repr(G[float]), 'A[int, float]')
15111562
self.assertEndsWith(repr(G[float, str]), 'A[int, float, str]')
15121563

15131564
H = A[int, *Ts, str]
1514-
self.assertEndsWith(repr(H), 'A[int, typing.Unpack[Ts], str]')
1565+
self.assertEndsWith(repr(H), 'A[int, typing.Unpack[~Ts], str]')
15151566
self.assertEndsWith(repr(H[()]), 'A[int, str]')
15161567
self.assertEndsWith(repr(H[float]), 'A[int, float, str]')
15171568
self.assertEndsWith(repr(H[float, str]), 'A[int, float, str, str]')
15181569

15191570
I = A[int, Unpack[Ts], str]
1520-
self.assertEndsWith(repr(I), 'A[int, typing.Unpack[Ts], str]')
1571+
self.assertEndsWith(repr(I), 'A[int, typing.Unpack[~Ts], str]')
15211572
self.assertEndsWith(repr(I[()]), 'A[int, str]')
15221573
self.assertEndsWith(repr(I[float]), 'A[int, float, str]')
15231574
self.assertEndsWith(repr(I[float, str]), 'A[int, float, str, str]')
15241575

15251576
J = A[*Ts, *tuple[str, ...]]
1526-
self.assertEndsWith(repr(J), 'A[typing.Unpack[Ts], *tuple[str, ...]]')
1577+
self.assertEndsWith(repr(J), 'A[typing.Unpack[~Ts], *tuple[str, ...]]')
15271578
self.assertEndsWith(repr(J[()]), 'A[*tuple[str, ...]]')
15281579
self.assertEndsWith(repr(J[float]), 'A[float, *tuple[str, ...]]')
15291580
self.assertEndsWith(repr(J[float, str]), 'A[float, str, *tuple[str, ...]]')
15301581

15311582
K = A[Unpack[Ts], Unpack[Tuple[str, ...]]]
1532-
self.assertEndsWith(repr(K), 'A[typing.Unpack[Ts], typing.Unpack[typing.Tuple[str, ...]]]')
1583+
self.assertEndsWith(repr(K), 'A[typing.Unpack[~Ts], typing.Unpack[typing.Tuple[str, ...]]]')
15331584
self.assertEndsWith(repr(K[()]), 'A[typing.Unpack[typing.Tuple[str, ...]]]')
15341585
self.assertEndsWith(repr(K[float]), 'A[float, typing.Unpack[typing.Tuple[str, ...]]]')
15351586
self.assertEndsWith(repr(K[float, str]), 'A[float, str, typing.Unpack[typing.Tuple[str, ...]]]')
@@ -1550,9 +1601,9 @@ class G(type(Unpack[Ts])): pass
15501601
with self.assertRaisesRegex(TypeError,
15511602
r'Cannot subclass typing\.Unpack'):
15521603
class H(Unpack): pass
1553-
with self.assertRaisesRegex(TypeError, r'Cannot subclass typing.Unpack\[Ts\]'):
1604+
with self.assertRaisesRegex(TypeError, r'Cannot subclass typing.Unpack\[~Ts\]'):
15541605
class I(*Ts): pass
1555-
with self.assertRaisesRegex(TypeError, r'Cannot subclass typing.Unpack\[Ts\]'):
1606+
with self.assertRaisesRegex(TypeError, r'Cannot subclass typing.Unpack\[~Ts\]'):
15561607
class J(Unpack[Ts]): pass
15571608

15581609
def test_variadic_class_args_are_correct(self):
@@ -5596,13 +5647,13 @@ class TsP(Generic[*Ts, P]):
55965647
MyCallable[[int], bool]: "MyCallable[[int], bool]",
55975648
MyCallable[[int, str], bool]: "MyCallable[[int, str], bool]",
55985649
MyCallable[[int, list[int]], bool]: "MyCallable[[int, list[int]], bool]",
5599-
MyCallable[Concatenate[*Ts, P], T]: "MyCallable[typing.Concatenate[typing.Unpack[Ts], ~P], ~T]",
5650+
MyCallable[Concatenate[*Ts, P], T]: "MyCallable[typing.Concatenate[typing.Unpack[~Ts], ~P], ~T]",
56005651

56015652
DoubleSpec[P2, P, T]: "DoubleSpec[~P2, ~P, ~T]",
56025653
DoubleSpec[[int], [str], bool]: "DoubleSpec[[int], [str], bool]",
56035654
DoubleSpec[[int, int], [str, str], bool]: "DoubleSpec[[int, int], [str, str], bool]",
56045655

5605-
TsP[*Ts, P]: "TsP[typing.Unpack[Ts], ~P]",
5656+
TsP[*Ts, P]: "TsP[typing.Unpack[~Ts], ~P]",
56065657
TsP[int, str, list[int], []]: "TsP[int, str, list[int], []]",
56075658
TsP[int, [str, list[int]]]: "TsP[int, [str, list[int]]]",
56085659

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:class:`typing.TypeVarTuple` now accepts ``bound``, ``covariant``,
2+
``contravariant``, and ``infer_variance`` parameters, matching the interface
3+
of :class:`typing.TypeVar` and :class:`typing.ParamSpec`.

Objects/clinic/typevarobject.c.h

Lines changed: 48 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)