Skip to content
Open
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
29 changes: 28 additions & 1 deletion Doc/library/typing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1980,7 +1980,7 @@ without the dedicated syntax, as documented below.

.. _typevartuple:

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

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

The name of the type variable tuple.

.. attribute:: __bound__

The upper bound of the type variable tuple, if any.

.. attribute:: __covariant__

Whether the type variable tuple has been explicitly marked as covariant.

.. attribute:: __contravariant__

Whether the type variable tuple has been explicitly marked as contravariant.

.. attribute:: __infer_variance__

Whether the type variable tuple's variance should be inferred by type checkers.

.. attribute:: __default__

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

.. versionadded:: 3.13

Type variable tuples created with ``covariant=True`` or
``contravariant=True`` can be used to declare covariant or contravariant
generic types. The ``bound`` argument is also accepted, similar to
:class:`TypeVar`. However the actual semantics of these keywords are yet to
be decided.

.. versionadded:: 3.11

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

Support for default values was added.

.. versionchanged:: 3.15

Added support for the ``bound``, ``covariant``, ``contravariant``, and
``infer_variance`` parameters.

.. class:: ParamSpec(name, *, bound=None, covariant=False, contravariant=False, default=typing.NoDefault)

Parameter specification variable. A specialized version of
Expand Down
5 changes: 5 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1224,6 +1224,11 @@ typing
as it was incorrectly inferred in runtime before.
(Contributed by Nikita Sobolev in :gh:`137191`.)

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


unicodedata
-----------
Expand Down
95 changes: 73 additions & 22 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -780,7 +780,7 @@ def test_typevartuple_none(self):
self.assertIs(U_None.__default__, None)
self.assertIs(U_None.has_default(), True)

class X[**Ts]: ...
class X[*Ts]: ...
Ts, = X.__type_params__
self.assertIs(Ts.__default__, NoDefault)
self.assertIs(Ts.has_default(), False)
Expand Down Expand Up @@ -1288,6 +1288,57 @@ def test_cannot_call_instance(self):
with self.assertRaises(TypeError):
Ts()

def test_default_variance(self):
Ts = TypeVarTuple('Ts')
self.assertIs(Ts.__covariant__, False)
self.assertIs(Ts.__contravariant__, False)
self.assertIs(Ts.__infer_variance__, False)
self.assertIsNone(Ts.__bound__)

def test_covariant(self):
Ts_co = TypeVarTuple('Ts_co', covariant=True)
self.assertIs(Ts_co.__covariant__, True)
self.assertIs(Ts_co.__contravariant__, False)
self.assertIs(Ts_co.__infer_variance__, False)

def test_contravariant(self):
Ts_contra = TypeVarTuple('Ts_contra', contravariant=True)
self.assertIs(Ts_contra.__covariant__, False)
self.assertIs(Ts_contra.__contravariant__, True)
self.assertIs(Ts_contra.__infer_variance__, False)

def test_infer_variance(self):
Ts = TypeVarTuple('Ts', infer_variance=True)
self.assertIs(Ts.__covariant__, False)
self.assertIs(Ts.__contravariant__, False)
self.assertIs(Ts.__infer_variance__, True)

def test_bound(self):
Ts_bound = TypeVarTuple('Ts_bound', bound=int)
self.assertIs(Ts_bound.__bound__, int)
Ts_no_bound = TypeVarTuple('Ts_no_bound')
self.assertIsNone(Ts_no_bound.__bound__)

def test_no_bivariant(self):
with self.assertRaises(ValueError):
TypeVarTuple('Ts', covariant=True, contravariant=True)

def test_cannot_combine_explicit_and_infer(self):
with self.assertRaises(ValueError):
TypeVarTuple('Ts', covariant=True, infer_variance=True)
with self.assertRaises(ValueError):
TypeVarTuple('Ts', contravariant=True, infer_variance=True)

def test_repr_with_variance(self):
Ts = TypeVarTuple('Ts')
self.assertEqual(repr(Ts), '~Ts')
Ts_co = TypeVarTuple('Ts_co', covariant=True)
self.assertEqual(repr(Ts_co), '+Ts_co')
Ts_contra = TypeVarTuple('Ts_contra', contravariant=True)
self.assertEqual(repr(Ts_contra), '-Ts_contra')
Ts_infer = TypeVarTuple('Ts_infer', infer_variance=True)
self.assertEqual(repr(Ts_infer), 'Ts_infer')

def test_unpacked_typevartuple_is_equal_to_itself(self):
Ts = TypeVarTuple('Ts')
self.assertEqual((*Ts,)[0], (*Ts,)[0])
Expand Down Expand Up @@ -1427,16 +1478,16 @@ def test_repr_is_correct(self):
class G1(Generic[*Ts]): pass
class G2(Generic[Unpack[Ts]]): pass

self.assertEqual(repr(Ts), 'Ts')
self.assertEqual(repr(Ts), '~Ts')

self.assertEqual(repr((*Ts,)[0]), 'typing.Unpack[Ts]')
self.assertEqual(repr(Unpack[Ts]), 'typing.Unpack[Ts]')
self.assertEqual(repr((*Ts,)[0]), 'typing.Unpack[~Ts]')
self.assertEqual(repr(Unpack[Ts]), 'typing.Unpack[~Ts]')

self.assertEqual(repr(tuple[*Ts]), 'tuple[typing.Unpack[Ts]]')
self.assertEqual(repr(Tuple[Unpack[Ts]]), 'typing.Tuple[typing.Unpack[Ts]]')
self.assertEqual(repr(tuple[*Ts]), 'tuple[typing.Unpack[~Ts]]')
self.assertEqual(repr(Tuple[Unpack[Ts]]), 'typing.Tuple[typing.Unpack[~Ts]]')

self.assertEqual(repr(*tuple[*Ts]), '*tuple[typing.Unpack[Ts]]')
self.assertEqual(repr(Unpack[Tuple[Unpack[Ts]]]), 'typing.Unpack[typing.Tuple[typing.Unpack[Ts]]]')
self.assertEqual(repr(*tuple[*Ts]), '*tuple[typing.Unpack[~Ts]]')
self.assertEqual(repr(Unpack[Tuple[Unpack[Ts]]]), 'typing.Unpack[typing.Tuple[typing.Unpack[~Ts]]]')

def test_variadic_class_repr_is_correct(self):
Ts = TypeVarTuple('Ts')
Expand Down Expand Up @@ -1475,61 +1526,61 @@ def test_variadic_class_alias_repr_is_correct(self):
class A(Generic[Unpack[Ts]]): pass

B = A[*Ts]
self.assertEndsWith(repr(B), 'A[typing.Unpack[Ts]]')
self.assertEndsWith(repr(B), 'A[typing.Unpack[~Ts]]')
self.assertEndsWith(repr(B[()]), 'A[()]')
self.assertEndsWith(repr(B[float]), 'A[float]')
self.assertEndsWith(repr(B[float, str]), 'A[float, str]')

C = A[Unpack[Ts]]
self.assertEndsWith(repr(C), 'A[typing.Unpack[Ts]]')
self.assertEndsWith(repr(C), 'A[typing.Unpack[~Ts]]')
self.assertEndsWith(repr(C[()]), 'A[()]')
self.assertEndsWith(repr(C[float]), 'A[float]')
self.assertEndsWith(repr(C[float, str]), 'A[float, str]')

D = A[*Ts, int]
self.assertEndsWith(repr(D), 'A[typing.Unpack[Ts], int]')
self.assertEndsWith(repr(D), 'A[typing.Unpack[~Ts], int]')
self.assertEndsWith(repr(D[()]), 'A[int]')
self.assertEndsWith(repr(D[float]), 'A[float, int]')
self.assertEndsWith(repr(D[float, str]), 'A[float, str, int]')

E = A[Unpack[Ts], int]
self.assertEndsWith(repr(E), 'A[typing.Unpack[Ts], int]')
self.assertEndsWith(repr(E), 'A[typing.Unpack[~Ts], int]')
self.assertEndsWith(repr(E[()]), 'A[int]')
self.assertEndsWith(repr(E[float]), 'A[float, int]')
self.assertEndsWith(repr(E[float, str]), 'A[float, str, int]')

F = A[int, *Ts]
self.assertEndsWith(repr(F), 'A[int, typing.Unpack[Ts]]')
self.assertEndsWith(repr(F), 'A[int, typing.Unpack[~Ts]]')
self.assertEndsWith(repr(F[()]), 'A[int]')
self.assertEndsWith(repr(F[float]), 'A[int, float]')
self.assertEndsWith(repr(F[float, str]), 'A[int, float, str]')

G = A[int, Unpack[Ts]]
self.assertEndsWith(repr(G), 'A[int, typing.Unpack[Ts]]')
self.assertEndsWith(repr(G), 'A[int, typing.Unpack[~Ts]]')
self.assertEndsWith(repr(G[()]), 'A[int]')
self.assertEndsWith(repr(G[float]), 'A[int, float]')
self.assertEndsWith(repr(G[float, str]), 'A[int, float, str]')

H = A[int, *Ts, str]
self.assertEndsWith(repr(H), 'A[int, typing.Unpack[Ts], str]')
self.assertEndsWith(repr(H), 'A[int, typing.Unpack[~Ts], str]')
self.assertEndsWith(repr(H[()]), 'A[int, str]')
self.assertEndsWith(repr(H[float]), 'A[int, float, str]')
self.assertEndsWith(repr(H[float, str]), 'A[int, float, str, str]')

I = A[int, Unpack[Ts], str]
self.assertEndsWith(repr(I), 'A[int, typing.Unpack[Ts], str]')
self.assertEndsWith(repr(I), 'A[int, typing.Unpack[~Ts], str]')
self.assertEndsWith(repr(I[()]), 'A[int, str]')
self.assertEndsWith(repr(I[float]), 'A[int, float, str]')
self.assertEndsWith(repr(I[float, str]), 'A[int, float, str, str]')

J = A[*Ts, *tuple[str, ...]]
self.assertEndsWith(repr(J), 'A[typing.Unpack[Ts], *tuple[str, ...]]')
self.assertEndsWith(repr(J), 'A[typing.Unpack[~Ts], *tuple[str, ...]]')
self.assertEndsWith(repr(J[()]), 'A[*tuple[str, ...]]')
self.assertEndsWith(repr(J[float]), 'A[float, *tuple[str, ...]]')
self.assertEndsWith(repr(J[float, str]), 'A[float, str, *tuple[str, ...]]')

K = A[Unpack[Ts], Unpack[Tuple[str, ...]]]
self.assertEndsWith(repr(K), 'A[typing.Unpack[Ts], typing.Unpack[typing.Tuple[str, ...]]]')
self.assertEndsWith(repr(K), 'A[typing.Unpack[~Ts], typing.Unpack[typing.Tuple[str, ...]]]')
self.assertEndsWith(repr(K[()]), 'A[typing.Unpack[typing.Tuple[str, ...]]]')
self.assertEndsWith(repr(K[float]), 'A[float, typing.Unpack[typing.Tuple[str, ...]]]')
self.assertEndsWith(repr(K[float, str]), 'A[float, str, typing.Unpack[typing.Tuple[str, ...]]]')
Expand All @@ -1550,9 +1601,9 @@ class G(type(Unpack[Ts])): pass
with self.assertRaisesRegex(TypeError,
r'Cannot subclass typing\.Unpack'):
class H(Unpack): pass
with self.assertRaisesRegex(TypeError, r'Cannot subclass typing.Unpack\[Ts\]'):
with self.assertRaisesRegex(TypeError, r'Cannot subclass typing.Unpack\[~Ts\]'):
class I(*Ts): pass
with self.assertRaisesRegex(TypeError, r'Cannot subclass typing.Unpack\[Ts\]'):
with self.assertRaisesRegex(TypeError, r'Cannot subclass typing.Unpack\[~Ts\]'):
class J(Unpack[Ts]): pass

def test_variadic_class_args_are_correct(self):
Expand Down Expand Up @@ -5596,13 +5647,13 @@ class TsP(Generic[*Ts, P]):
MyCallable[[int], bool]: "MyCallable[[int], bool]",
MyCallable[[int, str], bool]: "MyCallable[[int, str], bool]",
MyCallable[[int, list[int]], bool]: "MyCallable[[int, list[int]], bool]",
MyCallable[Concatenate[*Ts, P], T]: "MyCallable[typing.Concatenate[typing.Unpack[Ts], ~P], ~T]",
MyCallable[Concatenate[*Ts, P], T]: "MyCallable[typing.Concatenate[typing.Unpack[~Ts], ~P], ~T]",

DoubleSpec[P2, P, T]: "DoubleSpec[~P2, ~P, ~T]",
DoubleSpec[[int], [str], bool]: "DoubleSpec[[int], [str], bool]",
DoubleSpec[[int, int], [str, str], bool]: "DoubleSpec[[int, int], [str, str], bool]",

TsP[*Ts, P]: "TsP[typing.Unpack[Ts], ~P]",
TsP[*Ts, P]: "TsP[typing.Unpack[~Ts], ~P]",
TsP[int, str, list[int], []]: "TsP[int, str, list[int], []]",
TsP[int, [str, list[int]]]: "TsP[int, [str, list[int]]]",

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
:class:`typing.TypeVarTuple` now accepts ``bound``, ``covariant``,
``contravariant``, and ``infer_variance`` parameters, matching the interface
of :class:`typing.TypeVar` and :class:`typing.ParamSpec`.
57 changes: 48 additions & 9 deletions Objects/clinic/typevarobject.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading