Skip to content
Open
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
87 changes: 47 additions & 40 deletions peps/pep-0835.rst
Original file line number Diff line number Diff line change
Expand Up @@ -229,45 +229,15 @@ Supported Left-Hand Operands
-----------------------------

The ``@`` operator is implemented by adding ``nb_matrix_multiply`` to the
metatype (``type``) and to several typing-related types. The operator is
metatype (``type``) and to several types used in type hinting (including
``NoneType``). The operator is
supported for any left-hand operand that currently supports the ``|``
operator for making a union.
operator for making a union (including ``type(None)``, which acts as the
canonical equivalent to ``None`` in type hints).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a questionable claim; some parts of typing.py turn None into NoneType but not all typing APIs do.

Also the mention of NoneType is a bit confusing. NoneType is a type like any other, so it inherits __or__ (currently) and __matmul__ (if this PEP is accepted) from type. The more interesting question is what happens to None itself. Currently, it does not in fact support __or__; None | int works, but that's through the latter's __ror__. Therefore, unions of None with a stringified type (None | "int") fail at runtime. You are proposing to make matmul work for None.


For all other left-hand operands, the operator returns ``NotImplemented``,
allowing normal ``__matmul__`` dispatch to proceed.

Parsing and Grammar
===================

This proposal requires no changes to the Python grammar. Because ``@`` is
already a valid operator, it is natively parsed as a binary operation. The
shorthand is resolved during semantic analysis, entirely bypassing the need
to patch grammar files or update the parser.

How to Teach This
=================

In Python, the ``@`` symbol already has an established association with
metadata through decorators. The annotation shorthand extends this
intuition to the type system: ``int @ Field(gt=0)`` reads as "``int``,
decorated with ``Field(gt=0)``."

For beginners, the key rule is simple: **in a type annotation, ``@`` means
"with this metadata."** The full ``Annotated[int, Field(gt=0)]`` syntax
remains available and is entirely equivalent for those who find it clearer.

For experienced developers, the precedence rules follow standard Python
operator precedence (``@`` binds tighter than ``|``), and chaining
``T @ m1 @ m2`` flattens exactly as nested ``Annotated`` does.

Documentation and teaching materials should introduce the shorthand alongside
``Annotated``, not as a replacement. The longhand form is still preferred in
contexts where multiple metadata items are passed as a group and chaining
would be unwieldy.

Backwards Compatibility
=======================

Forward References and Deferred Evaluation
-------------------------------------------

Expand All @@ -277,8 +247,9 @@ module provides several formats for retrieving annotations:
- ``Format.VALUE``: Fully evaluates the annotation. Raises ``NameError``
if any name is unresolvable.
- ``Format.FORWARDREF``: Wraps unresolvable names in ``ForwardRef`` objects.
However, compound expressions using operators (``@``, ``|``) produce an
opaque ``ForwardRef`` string containing the entire expression.
However, when operators like ``@`` or ``|`` fail to evaluate because of an
unresolvable name, this format falls back to returning the entire expression
as an opaque string wrapped in a single ``ForwardRef`` object.
- ``Format.STRING``: Returns the raw source text with no evaluation.

For the ``@`` operator, ``Format.FORWARDREF`` is insufficient. Consider::
Expand All @@ -293,8 +264,8 @@ forward reference is resolved. This is a blocking issue for libraries like
Pydantic and FastAPI, which inspect metadata at class-definition time.

This proposal introduces a new format, ``Format.FORWARDREF_STRUCTURAL``.
This format assumes typing semantics and evaluates compound type expressions
**structurally**. It always returns an ``AnnotatedType`` for ``@``, a union
This format assumes typing semantics and evaluates type expressions involving
operators (like ``@`` and ``|``) **structurally**. It always returns an ``AnnotatedType`` for ``@``, a union
for ``|``, and a ``GenericAlias`` for subscripting. When a name cannot be
resolved, only that name is wrapped in ``ForwardRef``; the surrounding
operators are still evaluated. The example above produces::
Expand All @@ -304,7 +275,10 @@ operators are still evaluated. The example above produces::
The metadata is immediately accessible. This format also resolves the
pre-existing issue with ``|`` unions, where ``"Foo" | int`` under
``Format.FORWARDREF`` produces ``ForwardRef('Foo | int')`` instead of the
structural ``ForwardRef('Foo') | int``.
structural ``ForwardRef('Foo') | int``. Notably, if ``FORWARDREF_STRUCTURAL``

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems very speculative; there are lots of problems that would need to be solved before this can be entertained.

becomes the default evaluation strategy for type hints, it could eventually
reduce or eliminate the need for runtime type objects to implement
``__matmul__``.

Interaction with PEP 563
^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -321,6 +295,38 @@ coarser than from :pep:`749` thunks. When a name is unresolvable, the
rather than just a name. :pep:`749` provides a strictly better experience and
is the recommended path forward.

Parsing and Grammar
===================

This proposal requires no changes to the Python grammar. Because ``@`` is
already a valid operator, it is natively parsed as a binary operation. The
shorthand is resolved during semantic analysis, entirely bypassing the need
to patch grammar files or update the parser.

How to Teach This
=================

In Python, the ``@`` symbol already has an established association with
metadata through decorators. The annotation shorthand extends this
intuition to the type system: ``int @ Field(gt=0)`` reads as "``int``,
decorated with ``Field(gt=0)``."

For beginners, the key rule is simple: **in a type annotation, ``@`` means
"with this metadata."** The full ``Annotated[int, Field(gt=0)]`` syntax
remains available and is entirely equivalent for those who find it clearer.

For experienced developers, the precedence rules follow standard Python
operator precedence (``@`` binds tighter than ``|``), and chaining
``T @ m1 @ m2`` flattens exactly as nested ``Annotated`` does.

Documentation and teaching materials should introduce the shorthand alongside
``Annotated``, not as a replacement. The longhand form is still preferred in
contexts where multiple metadata items are passed as a group and chaining
would be unwieldy.

Backwards Compatibility
=======================

Operator Overloading
--------------------

Expand Down Expand Up @@ -354,7 +360,8 @@ metaclass.
The private ``typing._AnnotatedAlias`` class is retained as a deprecated
compatibility shim. Code using ``isinstance(x, typing._AnnotatedAlias)``
will continue to work but emit a ``DeprecationWarning``. The shim is
scheduled for removal in Python 3.18.
scheduled for removal in Python 3.21 (following the standard 5-year deprecation
policy outlined in :pep:`387`).

Code that should be updated:

Expand Down
Loading