PEP: 679 Title: Allow parentheses in assert statements Author: Pablo
Galindo Salgado <pablogsal@python.org> Discussions-To:
https://discuss.python.org/t/pep-679-allow-parentheses-in-assert-statements/13003
Status: Draft Type: Standards Track Content-Type: text/x-rst Created:
07-Jan-2022 Python-Version: 3.12

Abstract

This PEP proposes to allow parentheses surrounding the two-argument form
of assert statements. This will cause the interpreter to reinterpret
what before would have been an assert with a two-element tuple that will
always be True (assert (expression, message)) to an assert statement
with a subject and a failure message, equivalent to the statement with
the parentheses removed (assert expression, message).

Motivation

It is a common user mistake when using the form of the assert statement
that includes the error message to surround it with parentheses.
Unfortunately, this mistake passes undetected as the assert will always
pass, because it is interpreted as an assert statement where the
expression is a two-tuple, which always has truth-y value.

The mistake most often happens when extending the test or description
beyond a single line, as parentheses are the natural way to do that.

This is so common that a SyntaxWarning is now emitted by the compiler.

Additionally, some other statements in the language allow parenthesized
forms in one way or another like import statements
(from x import (a,b,c)) and del statements (del (a,b,c)).

Allowing parentheses not only will remove the common mistake but also
will allow users and auto-formatters to format long assert statements
over multiple lines in what the authors of this document believe will be
a more natural way. Although is possible to currently format long assert
statements over multiple lines as:

    assert (
        very very long
        expression
    ), (
        "very very long "
        "message"
    )

the authors of this document believe the parenthesized form is more
clear and more consistent with the formatting of other grammar
constructs:

    assert (
        very very long
        expression,

        "very very long "
        "message",
    )

This change has been originally discussed and proposed in [bpo-46167].

Rationale

This change can be implemented in the parser or in the compiler. We have
selected implementing this change in the parser because doing it in the
compiler will require re-interpreting the AST of an assert statement
with a two-tuple:

    Module(
        body=[
            Assert(
                test=Tuple(
                    elts=[
                        Name(id='x', ctx=Load()),
                        Name(id='y', ctx=Load())],
                    ctx=Load()))],
        type_ignores=[])

as the AST of an assert statement with an expression and a message:

    Module(
        body=[
            Assert(
                test=Name(id='x', ctx=Load()),
                msg=Name(id='y', ctx=Load()))],
        type_ignores=[])

The problem with this approach is that the AST of the first form will
technically be "incorrect" as we already have a specialized form for the
AST of an assert statement with a test and a message (the second one).
This means that many tools that deal with ASTs will need to be aware of
this change in semantics, which will be confusing as there is already a
correct form that better expresses the new meaning.

Specification

This PEP proposes changing the grammar of the assert statement to: :

    | 'assert' '(' expression ',' expression [','] ')' &(NEWLINE | ';')
    | 'assert' a=expression [',' expression ]

Where the first line is the new form of the assert statement that allows
parentheses. The lookahead is needed so statements like
assert (a, b) <= c, "something" are still parsed correctly and to
prevent the parser to eagerly capture the tuple as the full statement.

Optionally, new "invalid" rule can be added to produce custom syntax
errors to cover tuples with 0, 1, 3 or more elements.

Backwards Compatibility

The change is not technically backwards compatible, as parsing
assert (x,y) is currently interpreted as an assert statement with a
2-tuple as the subject, while after this change it will be interpreted
as assert x,y.

On the other hand, assert statements of this kind always pass, so they
are effectively not doing anything in user code. The authors of this
document think that this backwards incompatibility nature is beneficial,
as it will highlight these cases in user code while before they will
have passed unnoticed (assuming that these cases still exist because
users are ignoring syntax warnings).

Security Implications

There are no security implications for this change.

How to Teach This

The new form of the assert statement will be documented as part of the
language standard.

When teaching the form with error message of the assert statement to
users, now it can be noted that adding parentheses also work as
expected, which allows to break the statement over multiple lines.

Reference Implementation

A proposed draft PR with the change exist in [GH-30247].

References

Copyright

This document is placed in the public domain or under the
CC0-1.0-Universal license, whichever is more permissive.

GH-30247

    https://github.com/python/cpython/pull/30247

bpo-46167

    https://bugs.python.org/issue46167