Skip to content

Commit 5594c7c

Browse files
PEP 835: Address Discourse feedback from encukou and tjreedy
1 parent 37914ba commit 5594c7c

1 file changed

Lines changed: 59 additions & 41 deletions

File tree

peps/pep-0835.rst

Lines changed: 59 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -228,46 +228,11 @@ origin:
228228
Supported Left-Hand Operands
229229
-----------------------------
230230

231-
The ``@`` operator is implemented by adding ``nb_matrix_multiply`` to the
232-
metatype (``type``) and to several typing-related types. The operator is
233-
supported for any left-hand operand that currently supports the ``|``
234-
operator for making a union.
231+
The ``@`` operator is implemented by adding ``nb_matrix_multiply`` to the metatype (``type``) and to several typing-related types. The operator is supported for instances of ``type`` and other typing constructs that currently support the ``|`` operator for unions. The specific handling of ``None`` is currently unsettled (see Open Issues).
235232

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

239-
Parsing and Grammar
240-
===================
241-
242-
This proposal requires no changes to the Python grammar. Because ``@`` is
243-
already a valid operator, it is natively parsed as a binary operation. The
244-
shorthand is resolved during semantic analysis, entirely bypassing the need
245-
to patch grammar files or update the parser.
246-
247-
How to Teach This
248-
=================
249-
250-
In Python, the ``@`` symbol already has an established association with
251-
metadata through decorators. The annotation shorthand extends this
252-
intuition to the type system: ``int @ Field(gt=0)`` reads as "``int``,
253-
decorated with ``Field(gt=0)``."
254-
255-
For beginners, the key rule is simple: **in a type annotation, ``@`` means
256-
"with this metadata."** The full ``Annotated[int, Field(gt=0)]`` syntax
257-
remains available and is entirely equivalent for those who find it clearer.
258-
259-
For experienced developers, the precedence rules follow standard Python
260-
operator precedence (``@`` binds tighter than ``|``), and chaining
261-
``T @ m1 @ m2`` flattens exactly as nested ``Annotated`` does.
262-
263-
Documentation and teaching materials should introduce the shorthand alongside
264-
``Annotated``, not as a replacement. The longhand form is still preferred in
265-
contexts where multiple metadata items are passed as a group and chaining
266-
would be unwieldy.
267-
268-
Backwards Compatibility
269-
=======================
270-
271236
Forward References and Deferred Evaluation
272237
-------------------------------------------
273238

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

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

295261
This proposal introduces a new format, ``Format.FORWARDREF_STRUCTURAL``.
296-
This format assumes typing semantics and evaluates compound type expressions
297-
**structurally**. It always returns an ``AnnotatedType`` for ``@``, a union
262+
This format assumes typing semantics and evaluates type expressions involving
263+
operators (like ``@`` and ``|``) **structurally**. It always returns an ``AnnotatedType`` for ``@``, a union
298264
for ``|``, and a ``GenericAlias`` for subscripting. When a name cannot be
299265
resolved, only that name is wrapped in ``ForwardRef``; the surrounding
300266
operators are still evaluated. The example above produces::
@@ -321,6 +287,38 @@ coarser than from :pep:`749` thunks. When a name is unresolvable, the
321287
rather than just a name. :pep:`749` provides a strictly better experience and
322288
is the recommended path forward.
323289

290+
Parsing and Grammar
291+
===================
292+
293+
This proposal requires no changes to the Python grammar. Because ``@`` is
294+
already a valid operator, it is natively parsed as a binary operation. The
295+
shorthand is resolved during semantic analysis, entirely bypassing the need
296+
to patch grammar files or update the parser.
297+
298+
How to Teach This
299+
=================
300+
301+
In Python, the ``@`` symbol already has an established association with
302+
metadata through decorators. The annotation shorthand extends this
303+
intuition to the type system: ``int @ Field(gt=0)`` reads as "``int``,
304+
decorated with ``Field(gt=0)``."
305+
306+
For beginners, the key rule is simple: **in a type annotation, ``@`` means
307+
"with this metadata."** The full ``Annotated[int, Field(gt=0)]`` syntax
308+
remains available and is entirely equivalent for those who find it clearer.
309+
310+
For experienced developers, the precedence rules follow standard Python
311+
operator precedence (``@`` binds tighter than ``|``), and chaining
312+
``T @ m1 @ m2`` flattens exactly as nested ``Annotated`` does.
313+
314+
Documentation and teaching materials should introduce the shorthand alongside
315+
``Annotated``, not as a replacement. The longhand form is still preferred in
316+
contexts where multiple metadata items are passed as a group and chaining
317+
would be unwieldy.
318+
319+
Backwards Compatibility
320+
=======================
321+
324322
Operator Overloading
325323
--------------------
326324

@@ -354,7 +352,7 @@ metaclass.
354352
The private ``typing._AnnotatedAlias`` class is retained as a deprecated
355353
compatibility shim. Code using ``isinstance(x, typing._AnnotatedAlias)``
356354
will continue to work but emit a ``DeprecationWarning``. The shim is
357-
scheduled for removal in Python 3.18.
355+
scheduled for removal in Python 3.21.
358356

359357
Code that should be updated:
360358

@@ -481,6 +479,26 @@ References
481479
- :pep:`727` -- Documentation metadata in typing (Withdrawn)
482480
- :pep:`749` -- Implementing PEP 649
483481

482+
Open Issues
483+
===========
484+
485+
Supporting ``None``
486+
-------------------
487+
488+
While the ``@`` operator naturally applies to instances of ``type``, it is currently unsettled how it should handle ``None``.
489+
490+
There are two primary options, both with notable downsides:
491+
492+
1. **Implement ``__matmul__`` on ``NoneType``:** This provides the best ergonomics, allowing ``None @ Metadata``. However, it introduces a runtime risk. If a user accidentally uses matrix multiplication on a ``None`` value (e.g., ``a @ b`` where ``a`` is ``None`` instead of a numpy array), it will silently return an ``Annotated`` object instead of raising a ``TypeError``.
493+
2. **Do not support ``None @ Metadata``:** This avoids the runtime risk, but creates an ergonomic cliff. Users would be forced to write ``type(None) @ Metadata`` or fall back to ``typing.Annotated[None, Metadata]``.
494+
495+
The authors of this PEP lean slightly in favor of implementing ``__matmul__`` on ``NoneType`` for its ergonomic benefits, but ultimately defer to the Typing Council and community discussion to decide the best path forward.
496+
497+
Deprecation Timeline
498+
--------------------
499+
500+
This PEP schedules the removal of the ``typing._AnnotatedAlias`` compatibility shim for Python 3.21, following the standard 5-year deprecation policy (:pep:`387`). However, given the widespread use of ``typing.Annotated``, the exact timeline for removal—or whether the shim should be retained indefinitely to prevent churn in legacy code—remains open for community discussion.
501+
484502
Copyright
485503
=========
486504

0 commit comments

Comments
 (0)