ruff/crates/ruff_python_formatter/src/expression
Brent Westbrook cbc6863b8c
Fix panic when formatting comments in unary expressions (#21501)
## Summary

This is another attempt at https://github.com/astral-sh/ruff/pull/21410
that fixes https://github.com/astral-sh/ruff/issues/19226.

@MichaReiser helped me get something working in a very helpful pairing
session. I pushed one additional commit moving the comments back from
leading comments to trailing comments, which I think retains more of the
input formatting.

I was inspired by Dylan's PR (#21185) to make one of these tables:

<table>
                <thead>
                    <tr>
                    <th scope="col">Input</th>
                    <th scope="col">Main</th>
                    <th scope="col">PR</th>
                    </tr>
                </thead>
                <tbody>
<tr>
<td><pre lang="python">
if (
    not
    # comment
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
    pass
</pre></td>
<td><pre lang="python">
if (
    # comment
    not aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
    pass

</pre></td>
<td><pre lang="python">
if (
    not
    # comment
    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
    pass

</pre></td>
</tr>
<tr>
<td><pre lang="python">
if (
    # unary comment
    not
    # operand comment
    (
        # comment
        aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
        + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
    )
):
    pass
</pre></td>
<td><pre lang="python">
if (
    # unary comment
    # operand comment
    not (
        # comment
        aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
        + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
    )
):
    pass

</pre></td>
<td><pre lang="python">
if (
    # unary comment
    not
    # operand comment
    (
        # comment
        aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
        + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
    )
):
    pass

</pre></td>
</tr>
<tr>
<td><pre lang="python">
if (
    not # comment
    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
    pass
</pre></td>
<td><pre lang="python">
if (  # comment
    not aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
    pass

</pre></td>
<td><pre lang="python">
if (
    not aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa  # comment
    + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
    pass

</pre></td>
</tr>
</tbody>
            </table>

hopefully it helps even though the snippets are much wider here.

The two main differences are (1) that we now retain own-line comments
between the unary operator and its operand instead of moving these to
leading comments on the operator itself, and (2) that we move
end-of-line comments between the operator and operand to dangling
end-of-line comments on the operand (the last example in the table).

## Test Plan

Existing tests, plus new ones based on the issue. As I noted below, I
also ran the output from main on the unary.py file back through this
branch to check that we don't reformat code from main. This made me feel
a bit better about not preview-gating the changes in this PR.

```shell
> git show main:crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/unary.py | ruff format - | ./target/debug/ruff format --diff -
> echo $?
0
```

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
Co-authored-by: Takayuki Maeda <takoyaki0316@gmail.com>
2025-11-18 10:48:14 -05:00
..
binary_like.rs Switch to Rust 2024 edition (#18129) 2025-05-16 13:25:28 +02:00
expr_attribute.rs Avoid syntax error when formatting attribute expressions with outer parentheses, parenthesized value, and trailing comment on value (#20418) 2025-11-17 09:11:36 -06:00
expr_await.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
expr_bin_op.rs Fix f-string formatting in assignment statement (#14454) 2024-11-26 15:07:18 +05:30
expr_bool_op.rs Move {AnyNodeRef, AstNode} to ruff_python_ast crate root (#8030) 2023-10-18 00:01:18 +00:00
expr_boolean_literal.rs Split `Constant` to individual literal nodes (#8064) 2023-10-30 12:13:23 +05:30
expr_bytes_literal.rs Switch to Rust 2024 edition (#18129) 2025-05-16 13:25:28 +02:00
expr_call.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
expr_compare.rs Fix f-string formatting in assignment statement (#14454) 2024-11-26 15:07:18 +05:30
expr_dict.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
expr_dict_comp.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
expr_ellipsis_literal.rs Split `Constant` to individual literal nodes (#8064) 2023-10-30 12:13:23 +05:30
expr_f_string.rs Implement template strings (#17851) 2025-05-30 15:00:56 -05:00
expr_generator.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
expr_if.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
expr_ipy_escape_command.rs Formatter parentheses support for `IpyEscapeCommand` (#8207) 2023-10-25 14:01:50 +00:00
expr_lambda.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
expr_list.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
expr_list_comp.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
expr_name.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
expr_named.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
expr_none_literal.rs Split `Constant` to individual literal nodes (#8064) 2023-10-30 12:13:23 +05:30
expr_number_literal.rs Update Rust toolchain to 1.89 (#19807) 2025-08-07 18:21:50 +02:00
expr_set.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
expr_set_comp.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
expr_slice.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
expr_starred.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
expr_string_literal.rs Switch to Rust 2024 edition (#18129) 2025-05-16 13:25:28 +02:00
expr_subscript.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
expr_t_string.rs Implement template strings (#17851) 2025-05-30 15:00:56 -05:00
expr_tuple.rs Remove parentheses around multiple exception types on Python 3.14+ (#20768) 2025-10-14 11:17:45 -04:00
expr_unary_op.rs Fix panic when formatting comments in unary expressions (#21501) 2025-11-18 10:48:14 -05:00
expr_yield.rs Switch to Rust 2024 edition (#18129) 2025-05-16 13:25:28 +02:00
expr_yield_from.rs Move {AnyNodeRef, AstNode} to ruff_python_ast crate root (#8030) 2023-10-18 00:01:18 +00:00
mod.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
operator.rs Split implicit concatenated strings before binary expressions (#7145) 2023-09-08 06:51:26 +00:00
parentheses.rs Switch to Rust 2024 edition (#18129) 2025-05-16 13:25:28 +02:00