Skip to content
Draft
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
34 changes: 25 additions & 9 deletions Doc/library/ast.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1914,22 +1914,27 @@ aliases.
.. versionchanged:: 3.13
Added the *default_value* parameter.

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

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``.
``bound`` is the bound, if any; a parameter specification is bounded by a
parameter list, so the bound is usually a :class:`List`. ``default_value``
is the default value; if the :class:`!ParamSpec` has no bound or no default,
the corresponding attribute will be set to ``None``.

.. doctest::

>>> print(ast.dump(ast.parse("type Alias[**P = [int, str]] = Callable[P, int]"), indent=4))
>>> print(ast.dump(ast.parse("type Alias[**P: [int] = [int, str]] = Callable[P, int]"), indent=4))
Module(
body=[
TypeAlias(
name=Name(id='Alias', ctx=Store()),
type_params=[
ParamSpec(
name='P',
bound=List(
elts=[
Name(id='int')]),
default_value=List(
elts=[
Name(id='int'),
Expand All @@ -1946,21 +1951,29 @@ aliases.
.. versionchanged:: 3.13
Added the *default_value* parameter.

.. class:: TypeVarTuple(name, default_value)
.. versionchanged:: 3.16
Added the *bound* parameter.

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

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``.
``bound`` is the bound, if any, which applies to each type substituted for the
type variable tuple. ``default_value`` is the default value; if the
:class:`!TypeVarTuple` has no bound or no default, the corresponding 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: int = ()] = tuple[*Ts]"), indent=4))
Module(
body=[
TypeAlias(
name=Name(id='Alias', ctx=Store()),
type_params=[
TypeVarTuple(name='Ts', default_value=Tuple())],
TypeVarTuple(
name='Ts',
bound=Name(id='int'),
default_value=Tuple())],
value=Subscript(
value=Name(id='tuple'),
slice=Tuple(
Expand All @@ -1973,6 +1986,9 @@ aliases.
.. versionchanged:: 3.13
Added the *default_value* parameter.

.. versionchanged:: 3.16
Added the *bound* parameter.

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

Expand Down
72 changes: 65 additions & 7 deletions Doc/library/typing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2109,6 +2109,30 @@ without the dedicated syntax, as documented below.

.. versionadded:: 3.15

.. attribute:: __bound__

The upper bound of each of the types the type variable tuple stands for,
if any.

.. versionchanged:: 3.16

For type variable tuples created through
:ref:`type parameter syntax <type-params>`, the bound is evaluated only
when the attribute is accessed, not when the type variable tuple is
created (see :ref:`lazy-evaluation`).

.. method:: evaluate_bound

An :term:`evaluate function` corresponding to the
:attr:`~TypeVarTuple.__bound__` attribute.
When called directly, this method supports only the :attr:`~annotationlib.Format.VALUE`
format, which is equivalent to accessing the :attr:`~TypeVarTuple.__bound__` attribute
directly, but the method object can be passed to
:func:`annotationlib.call_evaluate_function` to evaluate the value in a
different format.

.. versionadded:: 3.16

.. attribute:: __default__

The default value of the type variable tuple, or :data:`typing.NoDefault` if it
Expand All @@ -2135,11 +2159,6 @@ 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`, but its actual semantics are yet to be decided.

.. versionadded:: 3.11

.. versionchanged:: 3.12
Expand All @@ -2156,6 +2175,12 @@ without the dedicated syntax, as documented below.
Added support for the ``bound``, ``covariant``, ``contravariant``, and
``infer_variance`` parameters.

.. versionchanged:: 3.16

Type variable tuple bounds can now be declared using the
:ref:`type parameter <type-params>` syntax, and are
:ref:`lazily evaluated <lazy-evaluation>`.

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

Parameter specification variable. A specialized version of
Expand Down Expand Up @@ -2239,6 +2264,34 @@ without the dedicated syntax, as documented below.

.. versionadded:: 3.12

.. attribute:: __bound__

The upper bound of the parameter specification, if any. Because a
parameter specification stands for the parameters of a callable, its
bound is a parameter list, such as ``[int, str]``.

.. versionchanged:: 3.16

For parameter specifications created through
:ref:`type parameter syntax <type-params>`, the bound is evaluated only
when the attribute is accessed, not when the parameter specification is
created (see :ref:`lazy-evaluation`).

Previously, :attr:`!__bound__` was :class:`types.NoneType` rather than
``None`` when no bound was given.

.. method:: evaluate_bound

An :term:`evaluate function` corresponding to the
:attr:`~ParamSpec.__bound__` attribute.
When called directly, this method supports only the :attr:`~annotationlib.Format.VALUE`
format, which is equivalent to accessing the :attr:`~ParamSpec.__bound__` attribute
directly, but the method object can be passed to
:func:`annotationlib.call_evaluate_function` to evaluate the value in a
different format.

.. versionadded:: 3.16

.. attribute:: __default__

The default value of the parameter specification, or :data:`typing.NoDefault` if it
Expand Down Expand Up @@ -2267,8 +2320,7 @@ without the dedicated syntax, as documented below.

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
:class:`TypeVar`. However the actual semantics of these keywords are yet to
generic types. However the actual semantics of these keywords are yet to
be decided.

.. versionadded:: 3.10
Expand All @@ -2282,6 +2334,12 @@ without the dedicated syntax, as documented below.

Support for default values was added.

.. versionchanged:: 3.16

Parameter specification bounds can now be declared using the
:ref:`type parameter <type-params>` syntax, and are
:ref:`lazily evaluated <lazy-evaluation>`.

.. note::
Only parameter specification variables defined in global scope can
be pickled.
Expand Down
21 changes: 17 additions & 4 deletions Doc/reference/compound_stmts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1768,8 +1768,8 @@ Type parameter lists
type_params: "[" `type_param` ("," `type_param`)* "]"
type_param: `typevar` | `typevartuple` | `paramspec`
typevar: `identifier` (":" `expression`)? ("=" `expression`)?
typevartuple: "*" `identifier` ("=" `expression`)?
paramspec: "**" `identifier` ("=" `expression`)?
typevartuple: "*" `identifier` (":" `starred_expression`)? ("=" `starred_expression`)?
paramspec: "**" `identifier` (":" `expression`)? ("=" `expression`)?

:ref:`Functions <def>` (including :ref:`coroutines <async def>`),
:ref:`classes <class>` and :ref:`type aliases <type>` may
Expand Down Expand Up @@ -1832,8 +1832,19 @@ but only when the value is explicitly accessed through the attributes ``__bound_
and ``__constraints__``. To accomplish this, the bounds or constraints are
evaluated in a separate :ref:`annotation scope <annotation-scopes>`.

:data:`typing.TypeVarTuple`\ s and :data:`typing.ParamSpec`\ s cannot have bounds
or constraints.
:data:`typing.TypeVarTuple`\ s and :data:`typing.ParamSpec`\ s can also declare a
bound with a colon (``:``) followed by an expression, but they cannot declare
constraints. For a :data:`!typing.TypeVarTuple`, the bound applies to each of the
types it stands for (e.g. in ``*Ts: int``, every type substituted for ``Ts`` must
be a subtype of :class:`int`). For a :data:`!typing.ParamSpec`, the bound is a
parameter list that the substituted parameters must be compatible with (e.g.
``**P: [int]``). As with :data:`!typing.TypeVar`, these bounds are lazily
evaluated in a separate :ref:`annotation scope <annotation-scopes>` and are not
enforced at runtime.

.. versionchanged:: 3.16
Added support for bounds on :data:`!typing.TypeVarTuple`\ s and
:data:`!typing.ParamSpec`\ s.

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
Expand All @@ -1853,7 +1864,9 @@ The following example indicates the full set of allowed type parameter declarati
TypeVarWithBound: int,
TypeVarWithConstraints: (str, bytes),
*SimpleTypeVarTuple = (int, float),
*TypeVarTupleWithBound: int,
**SimpleParamSpec = (str, bytearray),
**ParamSpecWithBound: [int],
](
a: SimpleTypeVar,
b: TypeVarWithDefault,
Expand Down
14 changes: 14 additions & 0 deletions Doc/whatsnew/3.16.rst
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,20 @@ New features
Other language changes
======================

* :ref:`Type parameter lists <type-params>` now accept bounds on type variable
tuples and parameter specifications, using the same syntax already available
for :class:`~typing.TypeVar`::

def call[*Ts: int, **P: [str]](*args: *Ts, f: Callable[P, int]) -> None: ...

A :class:`~typing.TypeVarTuple` bound applies to each type the type variable
tuple stands for, while a :class:`~typing.ParamSpec` bound is a parameter
list. Like other type parameter bounds, they are
:ref:`lazily evaluated <lazy-evaluation>` and are available through the
``__bound__`` attribute and the ``evaluate_bound``
:term:`evaluate function`.
(Contributed by KotlinIsland in :gh:`148945`.)

* :meth:`memoryview.cast` now allows casting a multidimensional
F-contiguous view to a one-dimensional view.
(Contributed by Jaemin Park in :gh:`91484`.)
Expand Down
21 changes: 6 additions & 15 deletions Grammar/python.gram
Original file line number Diff line number Diff line change
Expand Up @@ -694,11 +694,14 @@ type_param_seq[asdl_type_param_seq*]: a[asdl_type_param_seq*]=','.type_param+ ['

type_param[type_param_ty] (memo):
| a=NAME b=[type_param_bound] c=[type_param_default] { _PyAST_TypeVar(a->v.Name.id, b, c, EXTRA) }
| invalid_type_param
| '*' a=NAME b=[type_param_starred_default] { _PyAST_TypeVarTuple(a->v.Name.id, b, EXTRA) }
| '**' a=NAME b=[type_param_default] { _PyAST_ParamSpec(a->v.Name.id, b, EXTRA) }
| '*' a=NAME b=[type_param_starred_bound] c=[type_param_starred_default] { _PyAST_TypeVarTuple(a->v.Name.id, b, c, EXTRA) }
| '**' a=NAME b=[type_param_paramspec_bound] c=[type_param_default] { _PyAST_ParamSpec(a->v.Name.id, b, c, EXTRA) }

type_param_bound[expr_ty]: ':' e=expression { e }
type_param_starred_bound[expr_ty]: ':' e=star_expression {
CHECK_VERSION(expr_ty, 16, "Type variable tuple bounds are", e) }
type_param_paramspec_bound[expr_ty]: ':' e=expression {
CHECK_VERSION(expr_ty, 16, "Parameter specification bounds are", 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 {
Expand Down Expand Up @@ -1249,18 +1252,6 @@ invalid_legacy_expression:
_PyPegen_check_legacy_stmt(p, a) ? RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b,
"Missing parentheses in call to '%U'. Did you mean %U(...)?", a->v.Name.id, a->v.Name.id) : NULL}

invalid_type_param:
| '*' 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 colon=':' e=expression {
RAISE_SYNTAX_ERROR_STARTING_FROM(colon, e->kind == Tuple_kind
? "cannot use constraints with ParamSpec"
: "cannot use bound with ParamSpec")
}

invalid_expression:
| STRING a=(!STRING expression_without_invalid)+ STRING {
RAISE_SYNTAX_ERROR_KNOWN_RANGE( PyPegen_first_item(a, expr_ty), PyPegen_last_item(a, expr_ty),
Expand Down
15 changes: 9 additions & 6 deletions Include/internal/pycore_ast.h

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

4 changes: 3 additions & 1 deletion Include/internal/pycore_intrinsics.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@
#define INTRINSIC_SET_FUNCTION_TYPE_PARAMS 4
#define INTRINSIC_SET_TYPEPARAM_DEFAULT 5
#define INTRINSIC_ADD_CONDITIONAL_ANNOTATION 6
#define INTRINSIC_TYPEVARTUPLE_WITH_BOUND 7
#define INTRINSIC_PARAMSPEC_WITH_BOUND 8

#define MAX_INTRINSIC_2 6
#define MAX_INTRINSIC_2 8

typedef PyObject *(*intrinsic_func1)(PyThreadState* tstate, PyObject *value);
typedef PyObject *(*intrinsic_func2)(PyThreadState* tstate, PyObject *value1, PyObject *value2);
Expand Down
3 changes: 2 additions & 1 deletion Include/internal/pycore_magic_number.h
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ Known values:
Python 3.16a1 3703 (Replace DELETE_GLOBAL with PUSH_NULL; STORE_GLOBAL)
Python 3.16a1 3704 (Replace DELETE_ATTR with PUSH_NULL; STORE_ATTR)
Python 3.16a1 3705 (Add INTRINSIC_ADD_CONDITIONAL_ANNOTATION)
Python 3.16a1 3706 (Add INTRINSIC_TYPEVARTUPLE_WITH_BOUND and INTRINSIC_PARAMSPEC_WITH_BOUND)

Python 3.17 will start with 3750

Expand All @@ -312,7 +313,7 @@ Known values:

*/

#define PYC_MAGIC_NUMBER 3705
#define PYC_MAGIC_NUMBER 3706
/* This is equivalent to converting PYC_MAGIC_NUMBER to 2 bytes
(little-endian) and then appending b'\r\n'. */
#define PYC_MAGIC_NUMBER_TOKEN \
Expand Down
2 changes: 2 additions & 0 deletions Include/internal/pycore_typevarobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ extern "C" {

extern PyObject *_Py_make_typevar(PyObject *, PyObject *, PyObject *);
extern PyObject *_Py_make_paramspec(PyThreadState *, PyObject *);
extern PyObject *_Py_make_paramspec_with_bound(PyObject *, PyObject *);
extern PyObject *_Py_make_typevartuple(PyThreadState *, PyObject *);
extern PyObject *_Py_make_typevartuple_with_bound(PyObject *, PyObject *);
extern PyObject *_Py_make_typealias(PyThreadState *, PyObject *);
extern PyObject *_Py_subscript_generic(PyThreadState *, PyObject *);
extern PyObject *_Py_set_typeparam_default(PyThreadState *, PyObject *, PyObject *);
Expand Down
6 changes: 6 additions & 0 deletions Lib/_ast_unparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -453,12 +453,18 @@ def visit_TypeVar(self, node):

def visit_TypeVarTuple(self, node):
self.write("*" + node.name)
if node.bound:
self.write(": ")
self.traverse(node.bound)
if node.default_value:
self.write(" = ")
self.traverse(node.default_value)

def visit_ParamSpec(self, node):
self.write("**" + node.name)
if node.bound:
self.write(": ")
self.traverse(node.bound)
if node.default_value:
self.write(" = ")
self.traverse(node.default_value)
Expand Down
3 changes: 3 additions & 0 deletions Lib/test/.ruff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ extend-exclude = [
"test_lazy_import/data/**/*.py",
# Unary plus literal pattern is not yet supported by Ruff (GH-145239)
"test_patma.py",
# Bounds on type variable tuples and parameter specifications are not yet
# supported by Ruff (GH-148945)
"test_type_params.py",
]

[lint]
Expand Down
Loading
Loading