ruff/crates
Charlie Marsh 11287f944f
Avoid re-parenthesizing call chains whose inner values are parenthesized (#7373)
## Summary

Given a statement like:

```python
result = (
    f(111111111111111111111111111111111111111111111111111111111111111111111111111111111)
    + 1
)()
```

When we go to parenthesize the target of the assignment, we use
`maybe_parenthesize_expression` with `Parenthesize::IfBreaks`. This then
checks if the call on the right-hand side needs to be parenthesized, the
implementation of which looks like:

```rust
impl NeedsParentheses for ExprCall {
    fn needs_parentheses(
        &self,
        _parent: AnyNodeRef,
        context: &PyFormatContext,
    ) -> OptionalParentheses {
        if CallChainLayout::from_expression(self.into(), context.source())
            == CallChainLayout::Fluent
        {
            OptionalParentheses::Multiline
        } else if context.comments().has_dangling(self) {
            OptionalParentheses::Always
        } else {
            self.func.needs_parentheses(self.into(), context)
        }
    }
}
```

Checking for `self.func.needs_parentheses(self.into(), context)` is
problematic, since, as in the example above, `self.func` may _already_
be parenthesized -- in which case, we _don't_ want to parenthesize the
entire expression. If we do, we end up with this non-ideal formatting:

```python
result = (
    (
        f(
            111111111111111111111111111111111111111111111111111111111111111111111111111111111
        )
        + 1
    )()
)
```

This PR modifies the `NeedsParentheses` implementations for call chain
expressions to return `Never` if the inner expression has its own
parentheses, in which case, the formatting implementations for those
expressions will preserve them anyway.

Closes https://github.com/astral-sh/ruff/issues/7370.

## Test Plan

Zulip improves a bit, everything else is unchanged.

Before:

| project | similarity index | total files | changed files |

|--------------|------------------:|------------------:|------------------:|
| cpython | 0.76083 | 1789 | 1632 |
| django | 0.99981 | 2760 | 40 |
| transformers | 0.99944 | 2587 | 413 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99983 | 3496 | 18 |
| warehouse | 0.99834 | 648 | 20 |
| zulip | 0.99956 | 1437 | 23 |

After:

| project | similarity index | total files | changed files |

|--------------|------------------:|------------------:|------------------:|
| cpython | 0.76083 | 1789 | 1632 |
| django | 0.99981 | 2760 | 40 |
| transformers | 0.99944 | 2587 | 413 |
| twine | 1.00000 | 33 | 0 |
| typeshed | 0.99983 | 3496 | 18 |
| warehouse | 0.99834 | 648 | 20 |
| **zulip** | **0.99962** | **1437** | **22** |
2023-09-14 05:05:37 -04:00
..
flake8_to_ruff Bump version to v0.0.289 (#7308) 2023-09-12 12:00:11 -04:00
ruff [pylint] Implement too-many-public-methods rule (PLR0904) (#6179) 2023-09-14 00:52:26 +00:00
ruff_benchmark Enable preview mode during benchmarks (#7208) 2023-09-11 14:09:33 -05:00
ruff_cache Error on zero tab width (#6429) 2023-08-08 16:51:37 -04:00
ruff_cli Add warnings for nursery and preview rule selection (#7210) 2023-09-13 15:29:58 -05:00
ruff_dev Display nursery rules as preview in documentation (#7341) 2023-09-13 10:46:43 -05:00
ruff_diagnostics Create ruff_notebook crate (#7039) 2023-09-01 13:56:44 +00:00
ruff_formatter Introduce IndentWidth (#7301) 2023-09-13 14:52:24 +02:00
ruff_index
ruff_macros Update rule selection to respect preview mode (#7195) 2023-09-11 12:28:39 -05:00
ruff_notebook chore: Upgrade pyproject-toml crate (#7335) 2023-09-13 17:55:03 +02:00
ruff_python_ast Don't reorder parameters in function calls (#7268) 2023-09-13 09:01:49 +00:00
ruff_python_codegen Introduce ArgOrKeyword to keep call parameter order (#7302) 2023-09-13 08:45:46 +00:00
ruff_python_formatter Avoid re-parenthesizing call chains whose inner values are parenthesized (#7373) 2023-09-14 05:05:37 -04:00
ruff_python_index Move Ranged into ruff_text_size (#6919) 2023-08-27 14:12:51 -04:00
ruff_python_literal Avoid parsing other parts of a format specification if replacements are present (#6858) 2023-08-25 17:42:57 +00:00
ruff_python_parser Add range to lexer test snapshots (#7265) 2023-09-11 19:12:46 +00:00
ruff_python_resolver Replace .map_or(false, $closure) with .is_some_and(closure) (#6244) 2023-08-01 19:29:42 +02:00
ruff_python_semantic [pylint] Implement too-many-public-methods rule (PLR0904) (#6179) 2023-09-14 00:52:26 +00:00
ruff_python_stdlib Use the unicode-ident crate (#7212) 2023-09-07 08:19:25 +00:00
ruff_python_trivia Use the unicode-ident crate (#7212) 2023-09-07 08:19:25 +00:00
ruff_shrinking Move Ranged into ruff_text_size (#6919) 2023-08-27 14:12:51 -04:00
ruff_source_file Fix D204 when there's a semicolon after a docstring (#7174) 2023-09-11 15:09:47 +02:00
ruff_text_size Unify line size settings between ruff and the formatter (#6873) 2023-08-28 06:44:56 +00:00
ruff_wasm playground: Respect line-length and preview configuration (#7330) 2023-09-13 12:14:25 +00:00
ruff_workspace [pylint] Implement too-many-public-methods rule (PLR0904) (#6179) 2023-09-14 00:52:26 +00:00