Skip to content

Add formatting for MatchCase#6360

Merged
dhruvmanila merged 6 commits intomainfrom
dhruv/format-match-case
Aug 11, 2023
Merged

Add formatting for MatchCase#6360
dhruvmanila merged 6 commits intomainfrom
dhruv/format-match-case

Conversation

@dhruvmanila
Copy link
Member

@dhruvmanila dhruvmanila commented Aug 5, 2023

Summary

This PR adds formatting support for MatchCase node with subs for the Pattern
nodes.

Test Plan

Added test cases for case node handling with comments, newlines.

resolves: #6299

@dhruvmanila
Copy link
Member Author

Current dependencies on/for this PR:

This comment was auto-generated by Graphite.

@dhruvmanila dhruvmanila added the formatter Related to the formatter label Aug 5, 2023
@dhruvmanila dhruvmanila linked an issue Aug 5, 2023 that may be closed by this pull request
@github-actions
Copy link
Contributor

github-actions bot commented Aug 5, 2023

PR Check Results

Benchmark

Linux

group                                      main                                   pr
-----                                      ----                                   --
formatter/large/dataset.py                 1.00      8.5±0.34ms     4.8 MB/sec    1.00      8.5±0.40ms     4.8 MB/sec
formatter/numpy/ctypeslib.py               1.01  1719.0±60.66µs     9.7 MB/sec    1.00  1694.7±54.09µs     9.8 MB/sec
formatter/numpy/globals.py                 1.00    188.9±8.08µs    15.6 MB/sec    1.05   198.4±10.83µs    14.9 MB/sec
formatter/pydantic/types.py                1.00      3.6±0.12ms     7.1 MB/sec    1.01      3.6±0.13ms     7.0 MB/sec
linter/all-rules/large/dataset.py          1.00     10.8±0.30ms     3.8 MB/sec    1.01     11.0±0.34ms     3.7 MB/sec
linter/all-rules/numpy/ctypeslib.py        1.00      2.9±0.09ms     5.8 MB/sec    1.01      2.9±0.09ms     5.7 MB/sec
linter/all-rules/numpy/globals.py          1.00   408.9±14.64µs     7.2 MB/sec    1.03   419.6±19.06µs     7.0 MB/sec
linter/all-rules/pydantic/types.py         1.00      5.7±0.20ms     4.5 MB/sec    1.00      5.7±0.18ms     4.5 MB/sec
linter/default-rules/large/dataset.py      1.00      5.6±0.16ms     7.2 MB/sec    1.00      5.6±0.15ms     7.3 MB/sec
linter/default-rules/numpy/ctypeslib.py    1.05  1283.7±47.20µs    13.0 MB/sec    1.00  1219.3±61.22µs    13.7 MB/sec
linter/default-rules/numpy/globals.py      1.04    145.0±5.03µs    20.3 MB/sec    1.00    140.0±5.92µs    21.1 MB/sec
linter/default-rules/pydantic/types.py     1.00      2.5±0.10ms    10.0 MB/sec    1.00      2.5±0.09ms    10.1 MB/sec

Windows

group                                      main                                   pr
-----                                      ----                                   --
formatter/large/dataset.py                 1.04     13.2±0.50ms     3.1 MB/sec    1.00     12.7±1.23ms     3.2 MB/sec
formatter/numpy/ctypeslib.py               1.05      2.5±0.12ms     6.5 MB/sec    1.00      2.4±0.12ms     6.9 MB/sec
formatter/numpy/globals.py                 1.00   282.5±11.16µs    10.4 MB/sec    1.00   282.0±21.36µs    10.5 MB/sec
formatter/pydantic/types.py                1.01      5.6±0.24ms     4.6 MB/sec    1.00      5.5±0.29ms     4.6 MB/sec
linter/all-rules/large/dataset.py          1.18     16.9±0.37ms     2.4 MB/sec    1.00     14.4±0.63ms     2.8 MB/sec
linter/all-rules/numpy/ctypeslib.py        1.09      4.6±0.11ms     3.6 MB/sec    1.00      4.2±0.24ms     4.0 MB/sec
linter/all-rules/numpy/globals.py          1.14   580.8±21.12µs     5.1 MB/sec    1.00   508.5±36.04µs     5.8 MB/sec
linter/all-rules/pydantic/types.py         1.12      8.7±0.29ms     2.9 MB/sec    1.00      7.7±0.44ms     3.3 MB/sec
linter/default-rules/large/dataset.py      1.12      9.1±0.28ms     4.4 MB/sec    1.00      8.2±0.32ms     5.0 MB/sec
linter/default-rules/numpy/ctypeslib.py    1.17  1972.1±68.36µs     8.4 MB/sec    1.00  1683.1±70.88µs     9.9 MB/sec
linter/default-rules/numpy/globals.py      1.12    243.1±7.69µs    12.1 MB/sec    1.00    216.7±8.77µs    13.6 MB/sec
linter/default-rules/pydantic/types.py     1.13      4.3±0.21ms     6.0 MB/sec    1.00      3.8±0.22ms     6.8 MB/sec

@dhruvmanila dhruvmanila force-pushed the dhruv/format-match-stmt branch from e34d757 to 16fc898 Compare August 7, 2023 18:06
Base automatically changed from dhruv/format-match-stmt to main August 8, 2023 13:18
@dhruvmanila dhruvmanila force-pushed the dhruv/format-match-case branch from 65913e5 to 22e201d Compare August 9, 2023 17:23
@dhruvmanila dhruvmanila force-pushed the dhruv/format-match-case branch from ae9a0e7 to 51e0f67 Compare August 11, 2023 07:51
};

// The new level is for the `case` nodes.
let mut f = WithNodeLevel::new(NodeLevel::CompoundStatement, f);
Copy link
Member Author

Choose a reason for hiding this comment

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

Otherwise, the node level is NodeLevel::TopLevel for the case nodes which is coming from the match statement. This will be problematic as the empty lines between case nodes should be at max 1.

@dhruvmanila dhruvmanila marked this pull request as ready for review August 11, 2023 07:55
case NOT_YET_IMPLEMENTED_Pattern if foo == 2: # second
pass

case NOT_YET_IMPLEMENTED_Pattern if (foo := 1): # third
Copy link
Member Author

@dhruvmanila dhruvmanila Aug 11, 2023

Choose a reason for hiding this comment

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

These parentheses are preserved in black as well.

Black Playground

Copy link
Member Author

@dhruvmanila dhruvmanila Aug 11, 2023

Choose a reason for hiding this comment

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

Oh actually, even if the line length is more than 88, this adds the parentheses thus not breaking on multiple lines. I'll have to look into it as using Parenthesize::IfRequired isn't correct because otherwise the parentheses in this example won't be preserved.

Black Playground

Copy link
Member Author

Choose a reason for hiding this comment

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

This is solved. The heuristic is that if the expression already had parentheses, preserve that otherwise don't do anything even if it goes beyond the line length.

Copy link
Member

Choose a reason for hiding this comment

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

You may want to use Parenthesize::Optional which preserves Parentheses. Whether you want to use guard.format() or maybe_parenthesize depends on if parentheses should always be added if they don't fit on the line or only if they don't bring their own parentheses (e.g. [a, b, c, d] doesn't need addtional parentheses because it has its own).

I would expect it to be maybe_parenthesize because it is used in all positions where expressions are formatted inside a statement.

Copy link
Member Author

Choose a reason for hiding this comment

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

Parenthesize::Optional would break the expression into multiple lines which black doesn't do:

match long_lines:
    case "this is a long line for if condition" if aaaaaaaaahhhhhhhh == 1 and bbbbbbaaaaaaaaaaa == 2:  # comment
        pass

Running black on the above snippet wouldn't give any difference but if we use Optional, then we parenthesize the if expression:

match long_lines:
    case NOT_YET_IMPLEMENTED_Pattern if (
        aaaaaaaaahhhhhhhh == 1 and bbbbbbaaaaaaaaaaa == 2
    ):  # comment
        pass

Comment on lines 36 to 40
if is_expression_parenthesized(guard.as_ref().into(), f.context().source()) {
guard.format().with_options(Parentheses::Always).fmt(f)?;
} else {
guard.format().fmt(f)?;
}
Copy link
Member

Choose a reason for hiding this comment

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

Preserving parentheses should be the default behavior if you call expression.format().

Suggested change
if is_expression_parenthesized(guard.as_ref().into(), f.context().source()) {
guard.format().with_options(Parentheses::Always).fmt(f)?;
} else {
guard.format().fmt(f)?;
}
guard.format().fmt(f)?

Or you can use Parenthesize::Optional if it should use the same breaking logic as other expressions: e.g. that case a if a == [1, 2, 3] doesn't add parentheses and instead breaks after the [.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok, thanks! Regarding your example, black have the same behavior where it breaks after [ instead of parenthesizing the expression.

Black playground

case NOT_YET_IMPLEMENTED_Pattern if foo == 2: # second
pass

case NOT_YET_IMPLEMENTED_Pattern if (foo := 1): # third
Copy link
Member

Choose a reason for hiding this comment

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

You may want to use Parenthesize::Optional which preserves Parentheses. Whether you want to use guard.format() or maybe_parenthesize depends on if parentheses should always be added if they don't fit on the line or only if they don't bring their own parentheses (e.g. [a, b, c, d] doesn't need addtional parentheses because it has its own).

I would expect it to be maybe_parenthesize because it is used in all positions where expressions are formatted inside a statement.

@dhruvmanila dhruvmanila merged commit c434bdd into main Aug 11, 2023
@dhruvmanila dhruvmanila deleted the dhruv/format-match-case branch August 11, 2023 13:50
durumu pushed a commit to durumu/ruff that referenced this pull request Aug 12, 2023
## Summary

This PR adds formatting support for `MatchCase` node with subs for the
`Pattern`
nodes.

## Test Plan

Added test cases for case node handling with comments, newlines.

resolves: astral-sh#6299
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.

Formatter: MatchCase

3 participants