Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Special ExprTuple formatting option for for-loops #5175

Merged
merged 1 commit into from
Jun 21, 2023

Conversation

konstin
Copy link
Member

@konstin konstin commented Jun 19, 2023

Motivation

While black keeps parentheses nearly everywhere, the notable exception is in the body of for loops:

for (a, b) in x:
    pass

becomes

for a, b in x:
    pass

This currently blocks #5163, which this PR should unblock.

Solution

This changes the ExprTuple formatting option to include one additional option that removes the parentheses when not using magic trailing comma and not breaking. It is supposed to be used through

#[derive(Debug)]
struct ExprTupleWithoutParentheses<'a>(&'a Expr);

impl Format<PyFormatContext<'_>> for ExprTupleWithoutParentheses<'_> {
    fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> {
        match self.0 {
            Expr::Tuple(expr_tuple) => expr_tuple
                .format()
                .with_options(TupleParentheses::StripInsideForLoop)
                .fmt(f),
            other => other.format().with_options(Parenthesize::IfBreaks).fmt(f),
        }
    }
}

Testing

The for loop formatting isn't merged due to missing this (and i didn't want to create more git weirdness across two people), but I've confirmed that when applying this to while loops instead of for loops, then

        write!(
            f,
            [
                text("while"),
                space(),
                ExprTupleWithoutParentheses(test.as_ref()),
                text(":"),
                trailing_comments(trailing_condition_comments),
                block_indent(&body.format())
            ]
        )?;

makes

while (a, b):
    pass

while (
    ajssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssa,
    b,
):
    pass

while (a,b,):
    pass

formatted as

while a, b:
    pass

while (
    ajssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssa,
    b,
):
    pass

while (
    a,
    b,
):
    pass

@konstin konstin added the formatter Related to the formatter label Jun 19, 2023
@konstin konstin requested a review from MichaReiser June 19, 2023 07:31
@github-actions
Copy link
Contributor

github-actions bot commented Jun 19, 2023

PR Check Results

Ecosystem

✅ ecosystem check detected no changes.

Benchmark

Linux

group                                      main                                   pr
-----                                      ----                                   --
formatter/large/dataset.py                 1.00      6.6±0.02ms     6.2 MB/sec    1.00      6.6±0.03ms     6.2 MB/sec
formatter/numpy/ctypeslib.py               1.01   1355.4±2.55µs    12.3 MB/sec    1.00   1348.6±6.33µs    12.3 MB/sec
formatter/numpy/globals.py                 1.00    131.2±0.27µs    22.5 MB/sec    1.00    131.2±3.21µs    22.5 MB/sec
formatter/pydantic/types.py                1.00      2.8±0.01ms     9.3 MB/sec    1.00      2.7±0.00ms     9.3 MB/sec
linter/all-rules/large/dataset.py          1.00     13.3±0.08ms     3.1 MB/sec    1.02     13.5±0.13ms     3.0 MB/sec
linter/all-rules/numpy/ctypeslib.py        1.00      3.4±0.01ms     5.0 MB/sec    1.02      3.4±0.01ms     4.9 MB/sec
linter/all-rules/numpy/globals.py          1.00    429.3±1.22µs     6.9 MB/sec    1.02    436.3±1.68µs     6.8 MB/sec
linter/all-rules/pydantic/types.py         1.00      5.9±0.03ms     4.3 MB/sec    1.04      6.1±0.04ms     4.2 MB/sec
linter/default-rules/large/dataset.py      1.00      6.6±0.03ms     6.1 MB/sec    1.01      6.7±0.04ms     6.1 MB/sec
linter/default-rules/numpy/ctypeslib.py    1.00   1449.3±2.36µs    11.5 MB/sec    1.01   1459.3±1.85µs    11.4 MB/sec
linter/default-rules/numpy/globals.py      1.00    162.0±0.34µs    18.2 MB/sec    1.01    163.2±0.92µs    18.1 MB/sec
linter/default-rules/pydantic/types.py     1.00      3.0±0.01ms     8.5 MB/sec    1.01      3.0±0.01ms     8.4 MB/sec

Windows

group                                      main                                   pr
-----                                      ----                                   --
formatter/large/dataset.py                 1.00      9.5±0.26ms     4.3 MB/sec    1.05     10.0±0.35ms     4.1 MB/sec
formatter/numpy/ctypeslib.py               1.00  1990.8±77.98µs     8.4 MB/sec    1.01      2.0±0.10ms     8.3 MB/sec
formatter/numpy/globals.py                 1.02   202.4±12.91µs    14.6 MB/sec    1.00   199.1±11.15µs    14.8 MB/sec
formatter/pydantic/types.py                1.02      4.1±0.12ms     6.2 MB/sec    1.00      4.1±0.11ms     6.3 MB/sec
linter/all-rules/large/dataset.py          1.00     17.7±0.20ms     2.3 MB/sec    1.10     19.5±0.43ms     2.1 MB/sec
linter/all-rules/numpy/ctypeslib.py        1.00      4.8±0.07ms     3.5 MB/sec    1.09      5.2±0.16ms     3.2 MB/sec
linter/all-rules/numpy/globals.py          1.00   583.5±10.11µs     5.1 MB/sec    1.12   655.9±57.92µs     4.5 MB/sec
linter/all-rules/pydantic/types.py         1.00      8.0±0.14ms     3.2 MB/sec    1.11      8.9±0.24ms     2.9 MB/sec
linter/default-rules/large/dataset.py      1.00      9.3±0.12ms     4.4 MB/sec    1.07     10.0±0.33ms     4.1 MB/sec
linter/default-rules/numpy/ctypeslib.py    1.00      2.0±0.03ms     8.3 MB/sec    1.08      2.2±0.06ms     7.6 MB/sec
linter/default-rules/numpy/globals.py      1.00   239.9±10.33µs    12.3 MB/sec    1.06   253.1±12.22µs    11.7 MB/sec
linter/default-rules/pydantic/types.py     1.01      4.6±0.20ms     5.5 MB/sec    1.00      4.6±0.13ms     5.6 MB/sec

Copy link
Member

@MichaReiser MichaReiser left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed on discord. The best way is probably to bypass Expr and call the Tuple formatting directly from the for formatting (and pass a custom option).

@konstin konstin force-pushed the parentheses_unless_breaks branch from ad18250 to 61ef362 Compare June 21, 2023 18:32
@konstin konstin changed the title Add RemoveUnlessBreaks for removing ExprTuple parentheses Special ExprTuple formatting option for for-loops Jun 21, 2023
@konstin konstin mentioned this pull request Jun 21, 2023
@konstin konstin requested a review from MichaReiser June 21, 2023 18:34
@konstin
Copy link
Member Author

konstin commented Jun 21, 2023

i completely rewrote this

@davidszotten
Copy link
Contributor

if it makes testing easier, and this is the only blocker, should we consider merging the StmtFor pr first? seems ok to merge impls with known issues (not much different from merging and subsequently finding edge cases to fix)

@MichaReiser
Copy link
Member

if it makes testing easier, and this is the only blocker, should we consider merging the StmtFor pr first? seems ok to merge impls with known issues (not much different from merging and subsequently finding edge cases to fix)

That sounds reasonable to me. But we can do either

## Motivation

While black keeps parentheses nearly everywhere, with the notable exception of in the body of for loops:
```python
for (a, b) in x:
    pass
```
becomes
```python
for a, b in x:
    pass
```

This currently blocks #5163, which this PR should unblock.

## Solution

This changes the `ExprTuple` formatting option to include one additional option that removes the parentheses when not using magic trailing comma and not breaking. It is supposed to be used through
```
#[derive(Debug)]
struct ExprTupleWithoutParentheses<'a>(&'a Expr);

impl Format<PyFormatContext<'_>> for ExprTupleWithoutParentheses<'_> {
    fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> {
        match self.0 {
            Expr::Tuple(expr_tuple) => expr_tuple
                .format()
                .with_options(TupleParentheses::StripInsideForLoop)
                .fmt(f),
            other => other.format().with_options(Parenthesize::IfBreaks).fmt(f),
        }
    }
}
```

## Testing

The for loop formatting isn't merged due to missing this (and i didn't want to create more git weirdness across two people), but I've confirmed that when applying this to while loops instead of for loops, then
```rust
        write!(
            f,
            [
                text("while"),
                space(),
                ExprTupleWithoutParentheses(test.as_ref()),
                text(":"),
                trailing_comments(trailing_condition_comments),
                block_indent(&body.format())
            ]
        )?;
```
makes
```python
while (a, b):
    pass

while (
    ajssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssa,
    b,
):
    pass

while (a,b,):
    pass
```
formatted as
```python
while a, b:
    pass

while (
    ajssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssa,
    b,
):
    pass

while (
    a,
    b,
):
    pass
```
@konstin konstin force-pushed the parentheses_unless_breaks branch from 61ef362 to 8c036eb Compare June 21, 2023 19:02
@konstin konstin merged commit 9419d3f into main Jun 21, 2023
@konstin konstin deleted the parentheses_unless_breaks branch June 21, 2023 19:17
@konstin
Copy link
Member Author

konstin commented Jun 21, 2023

Either works fine, but i think it's easier to test if merge or rebase main into #5163

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
formatter Related to the formatter
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants