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
<https://bugs.python.org/issue35029>`_.

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
==========

.. [bpo-46167] https://bugs.python.org/issue46167
.. [GH-30247] https://github.com/python/cpython/pull/30247


Copyright
=========

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

..
    Local Variables:
    mode: indented-text
    indent-tabs-mode: nil
    sentence-end-double-space: t
    fill-column: 70
    coding: utf-8
    End:
