Skip to content
Merged
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
28 changes: 24 additions & 4 deletions pep_sphinx_extensions/lexers/pep824_lexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@
# If it breaks in the future, it can be safely replaced by plain
# Python or text lexers.

from pygments.lexer import inherit
from pygments.lexers.python import PythonLexer
from pygments.token import Operator
from pygments.lexer import DelegatingLexer, inherit
from pygments.lexers.python import (
PythonLexer,
PythonTracebackLexer,
_PythonConsoleLexerBase,
)
from pygments.token import Operator, Other


class Py824Lexer(PythonLexer):
Expand All @@ -25,5 +29,21 @@ class Py824Lexer(PythonLexer):
}


class Py824ConsoleLexer(DelegatingLexer):
name = "py824-console"

def __init__(self, **options):
pylexer = Py824Lexer
tblexer = PythonTracebackLexer

class _ReplaceInnerCode(DelegatingLexer):
def __init__(self, **options):
super().__init__(
pylexer, _PythonConsoleLexerBase, Other.Code, **options
)

super().__init__(tblexer, _ReplaceInnerCode, Other.Traceback, **options)


def register():
return [Py824Lexer]
return [Py824Lexer, Py824ConsoleLexer]
130 changes: 124 additions & 6 deletions peps/pep-0824.rst
Original file line number Diff line number Diff line change
Expand Up @@ -373,9 +373,9 @@ Operator precedence
-------------------

The ``??`` operator has a lower precedence than ``or`` and ``and``.
Thus, ``a or b ?? c`` means ``(a or b) ?? c``, and ``a ?? b and c``
means ``a ?? (b and c)``. To get the opposite grouping, use
``a or (b ?? c)`` or ``(a ?? b) and c``.
Thus, ``a ?? b or c`` means ``a ?? (b or c)``, and ``a and b ?? c``
means ``(a and b) ?? c``. To get the opposite grouping, use explicit
parentheses.

This precedence is a convention. For clarity, we recommend always adding
parentheses when mixing ``??`` with ``and`` or ``or``.
Expand Down Expand Up @@ -548,9 +548,110 @@ left-hand side is evaluated if the values is ``not None``. The
conditional operators ``or`` and ``and`` which also short-circuit the
expression for truthy and falsy values respectively.

Furthermore, setting the precedence between ``or`` and conditional
expressions matches other languages which have implemented the operator,
like JS [#js_precedence]_ and C# [#csharp_precedence]_.
Due to their similarities, it is even fair to describe ``??`` as just
a specialized variant of ``or``. While ``or`` checks for all falsy
values, ``??`` only checks for the narrower ``None``. For truthy values
and ``None``, they will output the same:

.. code-block:: py824-console

>>> a = None
>>> a or 2
2
>>> a ?? 2
2

Use different precedence for ``??``
-----------------------------------

It was discussed what the precedence for ``??`` should be, in particular
in relationship to ``or``.

Looking at the other modern programming languages which have "``null``
coalescing" operators, two options for the precedence emerge.

1. A precedence between binary and comparison operators as seen in
both Swift [#swift_precedence]_ and Kotlin [#kotlin_precedence]_
for their their "Nil-Coalescing" and "Elvis" operators respectively.

2. A precedence between the logical OR and conditional / ternary
operators, see C# [#csharp_precedence]_, Dart [#dart_precedence]_ and
PHP [#php_precedence]_. JS / TS [#js_precedence]_ is similar, though
parentheses are required when mixing ``??`` with either ``||`` or
``&&``. (See the next `section <Require parentheses when mixing ??
with and and or_>`_ for more details on that.)

The relationship of the ``None``-coalescing operator proposed in this
PEP to other programming languages is by design. Therefore, it does not
make sense to choose yet another precedence only for Python.

With the decision to add ``??`` as a boolean operator, see the rejected
idea `Add ?? as a binary operator`_ for the rational, only a precedence
close to ``and`` and ``or`` makes sense. As such, option (1) is
rejected.

The ``??`` operator only handles a subset of values compared to ``or``,
just ``None`` instead of all falsy values. By assigning a precedence
lower than ``or``, option (2), it is guaranteed that a ``not None``
but falsy result of the more specific check ``??`` is preserved and
does not get overwritten by the broader ``or`` check.

.. code-block:: py824

args = {"user_name": ""} # str or None (if not given as input)
config = {"default_name": "Alice"}

args["user_name"] ?? config["default_name"] or "Guest"

In the example above the user explicitly supplied an empty string,
a falsy value, which should be passed on. Assuming a precedence for
``??`` lower than ``or``, the expression would short-circuit and
return ``""`` directly after evaluating ``args["user_name"]``
because ``"" is not None``. Changing the precedence to be equal or
higher than ``or`` would mean that ``or "Guest"`` is evaluated even
though a valid value was already supplied.

The altered precedence would break the assumption that ``??`` can
short-circuit a boolean expression also containing ``or`` for
``not None`` but falsy values, without explicit parentheses. Thus,
a precedence lower than ``or`` is chosen, option (2), see
`Precedence`_ specification.

Require parentheses when mixing ``??`` with ``and`` and ``or``
--------------------------------------------------------------

As explained `before <Use different precedence for ??_>`_, JavaScript
requires parentheses when mixing ``??`` with ``&&`` or ``||``. It was
suggested that Python could do the same.

The PEP acknowledges that combining different boolean operators can
be confusing and explicitly recommends the use of parentheses for added
clarity in the `How to teach this <Operator precedence_>`_ section.
The question to answer therefore is not whether parentheses are useful
but rather if they should be enforced by the language grammar.

This is rejected. For one, the benefit from strict enforcement would
be minimal. Not only will both ``a ?? b or c`` and ``a or b ?? c``
expressions evaluate as a reader expects without having to think
about grouping or precedence -- they will short-circuit after each
operand if the condition is met -- but developers are likely to add
parentheses on their own anyway just to make it more obvious.
A larger ecosystem trend can also be seen in the increasing
prevalence of linter and auto-formatter rules requiring or outright
adding parentheses for boolean expressions automatically, e.g. for
Ruff RUF021 [#ruf021]_.

Furthermore, strict enforcement would add a new special case compared
to ``and`` and ``or`` which would need to be thought and remembered
every time instead of the more general recommendation to just always
use parentheses when mixing any boolean operators, which already exists.

Lastly, while JavaScript requires parentheses, the other languages
with similar precedence only do so when it is required by their type
system. Python itself does not require explicit parentheses for any
other operator either. Even for assignment expressions which
almost always require parentheses, it is not a strict enforcement but
rather a necessity created by the low precedence of ``:=``.

Add ``??=`` as ``AugAssign``
----------------------------
Expand Down Expand Up @@ -720,23 +821,40 @@ Footnotes
(https://dart.dev/language/operators#conditional-expressions)
.. [#dart_2] Dart: Assignment operators
(https://dart.dev/language/operators#assignment-operators)
.. [#dart_precedence] Dart: Operators
(https://dart.dev/language/operators)
.. [#swift] Swift: Nil-Coalescing Operator
(https://docs.swift.org/swift-book/documentation/the-swift-programming-language/basicoperators/#Nil-Coalescing-Operator)
.. [#swift_precedence] Swift Operator precedence
(https://www.programiz.com/swift-programming/operator-precedence-associativity)
.. [#kotlin] Kotlin: Elvis operator
(https://kotlinlang.org/docs/null-safety.html#elvis-operator)
.. [#kotlin_precedence] Kotlin: Precedence
(https://kotlinlang.org/grammar/#expressions)
.. [#php_1] PHP: Null Coalesce Operator
(https://wiki.php.net/rfc/isset_ternary)
.. [#php_2] PHP: Null Coalescing Assignment Operator
(https://wiki.php.net/rfc/null_coalesce_equal_operator)
.. [#php_precedence] PHP: Operator precedence
(https://www.php.net/manual/en/language.operators.precedence.php)
.. [#ruby] Ruby: Abbreviated Assignment
(https://ruby-doc.org/3.2/syntax/assignment_rdoc.html#label-Abbreviated+Assignment)
.. [#perl] Perl: Assignment Operators
(https://perldoc.perl.org/perlop#Assignment-Operators)
.. [#ruf021] Ruff: parenthesize-chained-operators (RUF021)
(https://docs.astral.sh/ruff/rules/parenthesize-chained-operators/)


Change History
==============

* 04-Nov-2026:

- Updated `Add ?? as a binary operator`_
and added `Use different precedence for ??`_
and `Require parentheses when mixing ?? with and and or`_
as rejected ideas.

* 31-Oct-2026:

- Added section on `Chaining <spec-chaining_>`_ to the specification.
Expand Down
Loading