Skip to content

Commit

Permalink
pythongh-116126: Implement PEP 696 (python#116129)
Browse files Browse the repository at this point in the history
Co-authored-by: Alex Waygood <[email protected]>
Co-authored-by: Bénédikt Tran <[email protected]>
Co-authored-by: Shantanu <[email protected]>
  • Loading branch information
4 people authored May 3, 2024
1 parent 852263e commit ca269e5
Show file tree
Hide file tree
Showing 28 changed files with 1,924 additions and 623 deletions.
52 changes: 38 additions & 14 deletions Doc/library/ast.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1748,43 +1748,57 @@ Type parameters
:ref:`Type parameters <type-params>` can exist on classes, functions, and type
aliases.

.. class:: TypeVar(name, bound)
.. class:: TypeVar(name, bound, default_value)

A :class:`typing.TypeVar`. ``name`` is the name of the type variable.
``bound`` is the bound or constraints, if any. If ``bound`` is a :class:`Tuple`,
it represents constraints; otherwise it represents the bound.
A :class:`typing.TypeVar`. *name* is the name of the type variable.
*bound* is the bound or constraints, if any. If *bound* is a :class:`Tuple`,
it represents constraints; otherwise it represents the bound. *default_value*
is the default value; if the :class:`!TypeVar` has no default, this
attribute will be set to ``None``.

.. doctest::

>>> print(ast.dump(ast.parse("type Alias[T: int] = list[T]"), indent=4))
>>> print(ast.dump(ast.parse("type Alias[T: int = bool] = list[T]"), indent=4))
Module(
body=[
TypeAlias(
name=Name(id='Alias', ctx=Store()),
type_params=[
TypeVar(
name='T',
bound=Name(id='int', ctx=Load()))],
bound=Name(id='int', ctx=Load()),
default_value=Name(id='bool', ctx=Load()))],
value=Subscript(
value=Name(id='list', ctx=Load()),
slice=Name(id='T', ctx=Load()),
ctx=Load()))])

.. versionadded:: 3.12

.. class:: ParamSpec(name)
.. versionchanged:: 3.13
Added the *default_value* parameter.

.. class:: ParamSpec(name, default_value)

A :class:`typing.ParamSpec`. ``name`` is the name of the parameter specification.
A :class:`typing.ParamSpec`. *name* is the name of the parameter specification.
*default_value* is the default value; if the :class:`!ParamSpec` has no default,
this attribute will be set to ``None``.

.. doctest::

>>> print(ast.dump(ast.parse("type Alias[**P] = Callable[P, int]"), indent=4))
>>> print(ast.dump(ast.parse("type Alias[**P = (int, str)] = Callable[P, int]"), indent=4))
Module(
body=[
TypeAlias(
name=Name(id='Alias', ctx=Store()),
type_params=[
ParamSpec(name='P')],
ParamSpec(
name='P',
default_value=Tuple(
elts=[
Name(id='int', ctx=Load()),
Name(id='str', ctx=Load())],
ctx=Load()))],
value=Subscript(
value=Name(id='Callable', ctx=Load()),
slice=Tuple(
Expand All @@ -1796,19 +1810,26 @@ aliases.

.. versionadded:: 3.12

.. class:: TypeVarTuple(name)
.. versionchanged:: 3.13
Added the *default_value* parameter.

.. class:: TypeVarTuple(name, default_value)

A :class:`typing.TypeVarTuple`. ``name`` is the name of the type variable tuple.
A :class:`typing.TypeVarTuple`. *name* is the name of the type variable tuple.
*default_value* is the default value; if the :class:`!TypeVarTuple` has no
default, this attribute will be set to ``None``.

.. doctest::

>>> print(ast.dump(ast.parse("type Alias[*Ts] = tuple[*Ts]"), indent=4))
>>> print(ast.dump(ast.parse("type Alias[*Ts = ()] = tuple[*Ts]"), indent=4))
Module(
body=[
TypeAlias(
name=Name(id='Alias', ctx=Store()),
type_params=[
TypeVarTuple(name='Ts')],
TypeVarTuple(
name='Ts',
default_value=Tuple(ctx=Load()))],
value=Subscript(
value=Name(id='tuple', ctx=Load()),
slice=Tuple(
Expand All @@ -1821,6 +1842,9 @@ aliases.

.. versionadded:: 3.12

.. versionchanged:: 3.13
Added the *default_value* parameter.

Function and class definitions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down
82 changes: 79 additions & 3 deletions Doc/library/typing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1614,7 +1614,7 @@ without the dedicated syntax, as documented below.

.. _typevar:

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

Type variable.

Expand Down Expand Up @@ -1752,15 +1752,35 @@ without the dedicated syntax, as documented below.
the constraints are evaluated only when the attribute is accessed, not when
the type variable is created (see :ref:`lazy-evaluation`).

.. attribute:: __default__

The default value of the type variable, or :data:`typing.NoDefault` if it
has no default.

.. versionadded:: 3.13

.. method:: has_default()

Return whether or not the type variable has a default value. This is equivalent
to checking whether :attr:`__default__` is not the :data:`typing.NoDefault`
singleton, except that it does not force evaluation of the
:ref:`lazily evaluated <lazy-evaluation>` default value.

.. versionadded:: 3.13

.. versionchanged:: 3.12

Type variables can now be declared using the
:ref:`type parameter <type-params>` syntax introduced by :pep:`695`.
The ``infer_variance`` parameter was added.

.. versionchanged:: 3.13

Support for default values was added.

.. _typevartuple:

.. class:: TypeVarTuple(name)
.. class:: TypeVarTuple(name, default=typing.NoDefault)

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

The name of the type variable tuple.

.. attribute:: __default__

The default value of the type variable tuple, or :data:`typing.NoDefault` if it
has no default.

.. versionadded:: 3.13

.. method:: has_default()

Return whether or not the type variable tuple has a default value. This is equivalent
to checking whether :attr:`__default__` is not the :data:`typing.NoDefault`
singleton, except that it does not force evaluation of the
:ref:`lazily evaluated <lazy-evaluation>` default value.

.. versionadded:: 3.13

.. versionadded:: 3.11

.. versionchanged:: 3.12

Type variable tuples can now be declared using the
:ref:`type parameter <type-params>` syntax introduced by :pep:`695`.

.. class:: ParamSpec(name, *, bound=None, covariant=False, contravariant=False)
.. versionchanged:: 3.13

Support for default values was added.

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

Parameter specification variable. A specialized version of
:ref:`type variables <typevar>`.
Expand Down Expand Up @@ -1946,6 +1986,22 @@ without the dedicated syntax, as documented below.

The name of the parameter specification.

.. attribute:: __default__

The default value of the parameter specification, or :data:`typing.NoDefault` if it
has no default.

.. versionadded:: 3.13

.. method:: has_default()

Return whether or not the parameter specification has a default value. This is equivalent
to checking whether :attr:`__default__` is not the :data:`typing.NoDefault`
singleton, except that it does not force evaluation of the
:ref:`lazily evaluated <lazy-evaluation>` default value.

.. versionadded:: 3.13

Parameter specification variables 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
Expand All @@ -1959,6 +2015,10 @@ without the dedicated syntax, as documented below.
Parameter specifications can now be declared using the
:ref:`type parameter <type-params>` syntax introduced by :pep:`695`.

.. versionchanged:: 3.13

Support for default values was added.

.. note::
Only parameter specification variables defined in global scope can
be pickled.
Expand Down Expand Up @@ -3171,6 +3231,22 @@ Introspection helpers

.. versionadded:: 3.7.4

.. data:: NoDefault

A sentinel object used to indicate that a type parameter has no default
value. For example:

.. doctest::

>>> T = TypeVar("T")
>>> T.__default__ is typing.NoDefault
True
>>> S = TypeVar("S", default=None)
>>> S.__default__ is None
True

.. versionadded:: 3.13

Constant
--------

Expand Down
31 changes: 23 additions & 8 deletions Doc/reference/compound_stmts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1620,15 +1620,18 @@ Type parameter lists

.. versionadded:: 3.12

.. versionchanged:: 3.13
Support for default values was added (see :pep:`696`).

.. index::
single: type parameters

.. productionlist:: python-grammar
type_params: "[" `type_param` ("," `type_param`)* "]"
type_param: `typevar` | `typevartuple` | `paramspec`
typevar: `identifier` (":" `expression`)?
typevartuple: "*" `identifier`
paramspec: "**" `identifier`
typevar: `identifier` (":" `expression`)? ("=" `expression`)?
typevartuple: "*" `identifier` ("=" `expression`)?
paramspec: "**" `identifier` ("=" `expression`)?

:ref:`Functions <def>` (including :ref:`coroutines <async def>`),
:ref:`classes <class>` and :ref:`type aliases <type>` may
Expand Down Expand Up @@ -1694,19 +1697,31 @@ evaluated in a separate :ref:`annotation scope <annotation-scopes>`.
:data:`typing.TypeVarTuple`\ s and :data:`typing.ParamSpec`\ s cannot have bounds
or constraints.

All three flavors of type parameters can also have a *default value*, which is used
when the type parameter is not explicitly provided. This is added by appending
a single equals sign (``=``) followed by an expression. Like the bounds and
constraints of type variables, the default value is not evaluated when the
object is created, but only when the type parameter's ``__default__`` attribute
is accessed. To this end, the default value is evaluated in a separate
:ref:`annotation scope <annotation-scopes>`. If no default value is specified
for a type parameter, the ``__default__`` attribute is set to the special
sentinel object :data:`typing.NoDefault`.

The following example indicates the full set of allowed type parameter declarations::

def overly_generic[
SimpleTypeVar,
TypeVarWithDefault = int,
TypeVarWithBound: int,
TypeVarWithConstraints: (str, bytes),
*SimpleTypeVarTuple,
**SimpleParamSpec,
*SimpleTypeVarTuple = (int, float),
**SimpleParamSpec = (str, bytearray),
](
a: SimpleTypeVar,
b: TypeVarWithBound,
c: Callable[SimpleParamSpec, TypeVarWithConstraints],
*d: SimpleTypeVarTuple,
b: TypeVarWithDefault,
c: TypeVarWithBound,
d: Callable[SimpleParamSpec, TypeVarWithConstraints],
*e: SimpleTypeVarTuple,
): ...

.. _generic-functions:
Expand Down
8 changes: 6 additions & 2 deletions Doc/reference/executionmodel.rst
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ Annotation scopes are used in the following contexts:
* Type parameter lists for :ref:`generic classes <generic-classes>`.
A generic class's base classes and
keyword arguments are executed within the annotation scope, but its decorators are not.
* The bounds and constraints for type variables
* The bounds, constraints, and default values for type parameters
(:ref:`lazily evaluated <lazy-evaluation>`).
* The value of type aliases (:ref:`lazily evaluated <lazy-evaluation>`).

Expand All @@ -232,13 +232,17 @@ Annotation scopes differ from function scopes in the following ways:
.. versionadded:: 3.12
Annotation scopes were introduced in Python 3.12 as part of :pep:`695`.

.. versionchanged:: 3.13
Annotation scopes are also used for type parameter defaults, as
introduced by :pep:`696`.

.. _lazy-evaluation:

Lazy evaluation
---------------

The values of type aliases created through the :keyword:`type` statement are
*lazily evaluated*. The same applies to the bounds and constraints of type
*lazily evaluated*. The same applies to the bounds, constraints, and default values of type
variables created through the :ref:`type parameter syntax <type-params>`.
This means that they are not evaluated when the type alias or type variable is
created. Instead, they are only evaluated when doing so is necessary to resolve
Expand Down
6 changes: 6 additions & 0 deletions Doc/whatsnew/3.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ Interpreter improvements:

New typing features:

* :pep:`696`: Type parameters (:data:`typing.TypeVar`, :data:`typing.ParamSpec`,
and :data:`typing.TypeVarTuple`) now support defaults.
* :pep:`742`: :data:`typing.TypeIs` was added, providing more intuitive
type narrowing behavior.

Expand Down Expand Up @@ -850,6 +852,10 @@ typing
an item of a :class:`typing.TypedDict` as read-only for type checkers.
See :pep:`705` for more details.

* Add :data:`typing.NoDefault`, a sentinel object used to represent the defaults
of some parameters in the :mod:`typing` module. (Contributed by Jelle Zijlstra in
:gh:`116126`.)

unicodedata
-----------

Expand Down
10 changes: 7 additions & 3 deletions Grammar/python.gram
Original file line number Diff line number Diff line change
Expand Up @@ -647,21 +647,25 @@ type_params[asdl_type_param_seq*]: '[' t=type_param_seq ']' {
type_param_seq[asdl_type_param_seq*]: a[asdl_type_param_seq*]=','.type_param+ [','] { a }

type_param[type_param_ty] (memo):
| a=NAME b=[type_param_bound] { _PyAST_TypeVar(a->v.Name.id, b, EXTRA) }
| a=NAME b=[type_param_bound] c=[type_param_default] { _PyAST_TypeVar(a->v.Name.id, b, c, EXTRA) }
| '*' a=NAME colon=':' e=expression {
RAISE_SYNTAX_ERROR_STARTING_FROM(colon, e->kind == Tuple_kind
? "cannot use constraints with TypeVarTuple"
: "cannot use bound with TypeVarTuple")
}
| '*' a=NAME { _PyAST_TypeVarTuple(a->v.Name.id, EXTRA) }
| '*' a=NAME b=[type_param_starred_default] { _PyAST_TypeVarTuple(a->v.Name.id, b, EXTRA) }
| '**' a=NAME colon=':' e=expression {
RAISE_SYNTAX_ERROR_STARTING_FROM(colon, e->kind == Tuple_kind
? "cannot use constraints with ParamSpec"
: "cannot use bound with ParamSpec")
}
| '**' a=NAME { _PyAST_ParamSpec(a->v.Name.id, EXTRA) }
| '**' a=NAME b=[type_param_default] { _PyAST_ParamSpec(a->v.Name.id, b, EXTRA) }

type_param_bound[expr_ty]: ':' e=expression { e }
type_param_default[expr_ty]: '=' e=expression {
CHECK_VERSION(expr_ty, 13, "Type parameter defaults are", e) }
type_param_starred_default[expr_ty]: '=' e=star_expression {
CHECK_VERSION(expr_ty, 13, "Type parameter defaults are", e) }

# EXPRESSIONS
# -----------
Expand Down
Loading

0 comments on commit ca269e5

Please sign in to comment.