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

PEP 646: Add section on type substitution in generic aliases #2883

Merged
merged 4 commits into from Nov 27, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
186 changes: 186 additions & 0 deletions pep-0646.rst
Expand Up @@ -722,6 +722,187 @@ Normal ``TypeVar`` instances can also be used in such aliases:
# T bound to Any, Ts to an Tuple[Any, ...]
Foo


Substitution in Aliases
-----------------------

In the previous section, we only discussed simple usage of generic aliases
in which the type arguments were just simple types. However, a number of
more exotic constructions are also possible.


Type Arguments can be Variadic
''''''''''''''''''''''''''''''

First, type arguments to generic aliases can be variadic. For example, a
``TypeVarTuple`` can be used as a type argument:

::

Ts1 = TypeVar('Ts1')
mrahtz marked this conversation as resolved.
Show resolved Hide resolved
Ts2 = TypeVar('Ts2')

IntTuple = Tuple[int, *Ts1]
IntFloatTuple = IntTuple[float, *Ts2] # Valid

Here, ``*Ts1`` in the ``IntTuple`` alias is bound to ``Tuple[float, *Ts2]``,
resulting in an alias ``IntFloatTuple`` equivalent to
``Tuple[int, float, *Ts2]``.

Unpacked arbitrary-length tuples can also be used as type arguments, with
similar effects:

::

IntFloatsTuple = IntTuple[*Tuple[float, ...]] # Valid

Here, ``*Ts1`` is bound to ``*Tuple[float, ...]``, resulting in
``IntFloatsTuple`` being equivalent to ``Tuple[int, *Tuple[float, ...]]``: a tuple
consisting of an ``int`` then zero or more ``float``\s.


Variadic Arguments Require Variadic Aliases
'''''''''''''''''''''''''''''''''''''''''''

Variadic type arguments can only be used with generic aliases that are
themselves variadic. For example:

::

T = TypeVar('T')

IntTuple = Tuple[int, T]

IntTuple[str] # Valid
IntTuple[*Ts] # NOT valid
IntTuple[*Tuple[float, ...]] # NOT valid

Here, ``IntTuple`` is a *non*-variadic generic alias that takes exactly one
type argument. Hence, it cannot accept ``*Ts`` or ``*Tuple[float, ...]`` as type
arguments, because they represent an arbitrary number of types.


Aliases with Both TypeVars and TypeVarTuples
''''''''''''''''''''''''''''''''''''''''''''

In `Aliases`_, we briefly mentioned that aliases can be generic in both
``TypeVar``\s and ``TypeVarTuple``\s:

::

T = TypeVar('T')
Foo = Tuple[T, *Ts]

Foo[str, int] # T bound to str, Ts to Tuple[int]
Foo[str, int, float] # T bound to str, Ts to Tuple[int, float]

In accordance with `Multiple Type Variable Tuples: Not Allowed`_, at most one
``TypeVarTuple`` may appear in the type parameters to an alias. However, a
``TypeVarTuple`` can be combined with an arbitrary number of ``TypeVar``\s,
both before and after:

::

T1 = TypeVar('T1')
T2 = TypeVar('T2')
T3 = TypeVar('T3')

Tuple[*Ts, T1, T2] # Valid
Tuple[T1, T2, *Ts] # Valid
Tuple[T1, *Ts, T2, T3] # Valid

In order to substitute these type variables with supplied type arguments,
any type variables at the beginning or end of the type parameter list first
consume type arguments, and then any remaining type arguments are bound
to the ``TypeVarTuple``:

::

Shrubbery = Tuple[*Ts, T1, T2]

Shrubbery[str, bool] # T2=bool, T1=str, Ts=Tuple[()]
Shrubbery[str, bool, float] # T2=float, T1=bool, Ts=Tuple[str]
Shrubbery[str, bool, float, int] # T2=int, T1=float, Ts=Tuple[str, bool]

Ptang = Tuple[T1, *Ts, T2, T3]

Ptang[str, bool, float] # T1=str, T3=float, T2=bool, Ts=Tuple[()]
Ptang[str, bool, float, int] # T1=str, T3=int, T2=float, Ts=Tuple[bool]

Note that the minimum number of type arguments in such cases is set by
the number of ``TypeVar``\s:

::

Shrubbery[int] # Not valid; Shrubbery needs at least two type arguments


Splitting Arbitrary-Length Tuples
'''''''''''''''''''''''''''''''''

A final complication occurs when an unpacked arbitrary-length tuple is used
as a type argument to an alias consisting of both ``TypeVar``\s and a
``TypeVarTuple``:

::

Elderberries = Tuple[*Ts, T1]
Hamster = Elderberries[*Tuple[int, ...]] # valid

In such cases, the arbitrary-length tuple is split between the ``TypeVar``\s
and the ``TypeVarTuple``. We assume the arbitrary-length tuple contains
at least as many items as there are ``TypeVar``\s, such that individual
instances of the inner type - here ``int`` - are bound to any ``TypeVar``\s
present. The 'rest' of the arbitrary-length tuple - here ``*Tuple[int, ...]``,
since a tuple of arbitrary length minus two items is still arbitrary-length -
is bound to the ``TypeVarTuple``.

Here, therefore, ``Hamster`` is equivalent to ``Tuple[*Tuple[int, ...], int]``:
a tuple consisting of zero or more ``int``\s, then a final ``int``.

Of course, such splitting only occurs if necessary. For example, if we instead
did:

::

Elderberries[*Tuple[int, ...], str]

Then splitting would not occur; ``T1`` would be bound to ``str``, and
``Ts`` to ``*Tuple[int, ...]``.

In particularly awkward cases, a ``TypeVarTuple`` may consume both a type
*and* a part of an arbitrary-length tuple type:

::

Elderberries[str, *Tuple[int, ...]]

Here, ``T1`` is bound to ``int``, and ``Ts`` is bound to
``Tuple[str, *Tuple[int, ...]]``. This expression is therefore equivalent to
``Tuple[str, *Tuple[int, ...], int]``: a tuple consisting of a ``str``, then
zero or more ``int``\s, ending with an ``int``.


TypeVarTuples Cannot be Split
'''''''''''''''''''''''''''''

Finally, although any arbitrary-length tuples in the type argument list can be
split between the type variables and the type variable tuple, the same is not
true of ``TypeVarTuple``\s in the argument list:

::

Ts1 = TypeVarTuple('Ts1')
Ts2 = TypeVarTuple('Ts2')

Camelot = Tuple[T, *Ts1]
Camelot[*Ts2] # NOT valid

This is not possible because, unlike in the case of an unpacked arbitrary-length
tuple, there is no way to 'peer inside' the ``TypeVarTuple`` to see what its
individual types are.


Overloads for Accessing Individual Types
----------------------------------------

Expand Down Expand Up @@ -1459,6 +1640,9 @@ Expanding on these ideas, **Mark Mendoza** and **Vincent Siles** gave a presenta
'Variadic Type Variables for Decorators and Tensors' [#variadic-type-variables]_ at the 2019 Python
Typing Summit.

Discussion over how type substitution in generic aliases should behave
took place in `cpython#91162`_.


References
==========
Expand Down Expand Up @@ -1502,6 +1686,8 @@ References

.. [#dan-endorsement] https://mail.python.org/archives/list/python-dev@python.org/message/HTCARTYYCHETAMHB6OVRNR5EW5T2CP4J/

.. _cpython#91162: https://github.com/python/cpython/issues/91162

Copyright
=========

Expand Down