diff --git a/pep_sphinx_extensions/lexers/pep824_lexer.py b/pep_sphinx_extensions/lexers/pep824_lexer.py index 0f6b5791943..97cec804e02 100644 --- a/pep_sphinx_extensions/lexers/pep824_lexer.py +++ b/pep_sphinx_extensions/lexers/pep824_lexer.py @@ -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): @@ -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] diff --git a/peps/pep-0824.rst b/peps/pep-0824.rst index c9989050a19..dcf16321fda 100644 --- a/peps/pep-0824.rst +++ b/peps/pep-0824.rst @@ -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``. @@ -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 `_ 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 `_, 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 `_ 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`` ---------------------------- @@ -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 `_ to the specification.