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

Format PatternMatchStar #6653

Merged
merged 10 commits into from
Aug 24, 2023
Merged

Conversation

harupy
Copy link
Contributor

@harupy harupy commented Aug 17, 2023

Summary

Close #6558

Test Plan

@harupy
Copy link
Contributor Author

harupy commented Aug 17, 2023

As a test, I formatted this code:

x = [1, 2, 3, 4]

match x:
    case [
        1,
        *
        rest,
    ]:
        print(rest)
    case [
        1,
        2,
        *
        # hello
        rest,
    ]:
        print(rest)
    case [*_]:
        print("no match")

and got:

x = [1, 2, 3, 4]

match x:
    case [NOT_YET_IMPLEMENTED_PatternMatchSequence, 2]:
        print(rest)
    case [NOT_YET_IMPLEMENTED_PatternMatchSequence, 2]:
        print(rest)
    case [NOT_YET_IMPLEMENTED_PatternMatchSequence, 2]:
        print("no match")

How can I test this?

@github-actions
Copy link
Contributor

github-actions bot commented Aug 17, 2023

PR Check Results

Benchmark

Linux

group                                      main                                   pr
-----                                      ----                                   --
formatter/large/dataset.py                 1.00      3.6±0.04ms    11.3 MB/sec    1.01      3.7±0.03ms    11.1 MB/sec
formatter/numpy/ctypeslib.py               1.00    732.8±2.47µs    22.7 MB/sec    1.03    755.2±2.78µs    22.0 MB/sec
formatter/numpy/globals.py                 1.00     74.6±0.49µs    39.6 MB/sec    1.08     80.2±0.34µs    36.8 MB/sec
formatter/pydantic/types.py                1.00   1452.6±6.26µs    17.6 MB/sec    1.03  1501.2±11.63µs    17.0 MB/sec
linter/all-rules/large/dataset.py          1.00     10.2±0.05ms     4.0 MB/sec    1.00     10.2±0.04ms     4.0 MB/sec
linter/all-rules/numpy/ctypeslib.py        1.00      2.7±0.00ms     6.1 MB/sec    1.00      2.7±0.01ms     6.1 MB/sec
linter/all-rules/numpy/globals.py          1.00    386.5±0.71µs     7.6 MB/sec    1.00    386.0±0.54µs     7.6 MB/sec
linter/all-rules/pydantic/types.py         1.00      5.3±0.03ms     4.8 MB/sec    1.00      5.3±0.02ms     4.8 MB/sec
linter/default-rules/large/dataset.py      1.00      5.4±0.02ms     7.5 MB/sec    1.00      5.4±0.02ms     7.5 MB/sec
linter/default-rules/numpy/ctypeslib.py    1.00  1202.9±18.95µs    13.8 MB/sec    1.00   1207.8±1.23µs    13.8 MB/sec
linter/default-rules/numpy/globals.py      1.00    140.8±1.14µs    21.0 MB/sec    1.00    140.2±1.03µs    21.0 MB/sec
linter/default-rules/pydantic/types.py     1.00      2.5±0.01ms    10.4 MB/sec    1.01      2.5±0.01ms    10.3 MB/sec

Windows

group                                      main                                   pr
-----                                      ----                                   --
formatter/large/dataset.py                 1.00      5.0±0.13ms     8.1 MB/sec    1.03      5.1±0.26ms     7.9 MB/sec
formatter/numpy/ctypeslib.py               1.00  1011.0±60.02µs    16.5 MB/sec    1.00  1011.4±46.37µs    16.5 MB/sec
formatter/numpy/globals.py                 1.00    102.3±7.89µs    28.9 MB/sec    1.03    105.3±6.89µs    28.0 MB/sec
formatter/pydantic/types.py                1.00      2.0±0.14ms    12.5 MB/sec    1.01      2.1±0.08ms    12.4 MB/sec
linter/all-rules/large/dataset.py          1.00     15.4±0.31ms     2.6 MB/sec    1.01     15.6±0.34ms     2.6 MB/sec
linter/all-rules/numpy/ctypeslib.py        1.01      4.3±0.33ms     3.9 MB/sec    1.00      4.2±0.12ms     3.9 MB/sec
linter/all-rules/numpy/globals.py          1.00   522.6±20.53µs     5.6 MB/sec    1.02   531.2±22.75µs     5.6 MB/sec
linter/all-rules/pydantic/types.py         1.00      8.0±0.20ms     3.2 MB/sec    1.03      8.3±0.27ms     3.1 MB/sec
linter/default-rules/large/dataset.py      1.00      8.6±0.21ms     4.7 MB/sec    1.02      8.8±0.22ms     4.6 MB/sec
linter/default-rules/numpy/ctypeslib.py    1.00  1828.4±50.56µs     9.1 MB/sec    1.02  1866.5±52.73µs     8.9 MB/sec
linter/default-rules/numpy/globals.py      1.00   213.1±12.28µs    13.8 MB/sec    1.04   221.7±18.09µs    13.3 MB/sec
linter/default-rules/pydantic/types.py     1.00      3.9±0.13ms     6.6 MB/sec    1.03      4.0±0.31ms     6.4 MB/sec

@harupy
Copy link
Contributor Author

harupy commented Aug 17, 2023

Tweaked the PatternMatchSequence (should #6645 be resolved first?) implementation and got this:

x = [1, 2, 3, 4]

match x:
    case [
        "NOT_YET_IMPLEMENTED_PatternMatchValue",
        *rest,
    ]:
        print("a")
        print(rest)
    case [
        "NOT_YET_IMPLEMENTED_PatternMatchValue",
        "NOT_YET_IMPLEMENTED_PatternMatchValue",
        *rest# hello
        ,
    ]:
        print(rest)
    case [*_]:
        print("no match")

The comment location seems incorrect.

Comment on lines 13 to 14
let comments = f.context().comments().clone();
let dangling = comments.dangling_comments(item);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should comments be handled in PatternMatchSequence?

Copy link
Member

Choose a reason for hiding this comment

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

It depends on the comments. What's the code snipped that requires the dangling comment handling?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@MichaReiser

For example:

x = [1, 2, 3, 4]

match x:
    case [
        1,
        *
        # comment here
        rest,
    ]:
        print(rest)

@MichaReiser
Copy link
Member

Hmm yeah, it may be challenging to test this just now without match as, singleton formatting, or sequence formatting. There are PRs for as and singleton but it may be good to do sequence first.

@harupy
Copy link
Contributor Author

harupy commented Aug 18, 2023

Hmm yeah, it may be challenging to test this just now without match as, singleton formatting, or sequence formatting. There are PRs for as and singleton but it may be good to do sequence first.

Can I tackle #6645 first?

@MichaReiser
Copy link
Member

Hmm yeah, it may be challenging to test this just now without match as, singleton formatting, or sequence formatting. There are PRs for as and singleton but it may be good to do sequence first.

Can I tackle #6645 first?

Sure. You'll have to comment on the issue so that I can assign it to you.

@konstin konstin added the formatter Related to the formatter label Aug 21, 2023
@harupy harupy marked this pull request as ready for review August 23, 2023 09:56
Comment on lines +251 to +256
case [1, 2, * # comment
rest]:
pass
case [1, 2, * # comment
_]:
pass
Copy link
Contributor Author

@harupy harupy Aug 23, 2023

Choose a reason for hiding this comment

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

This is formatted first to:

    case [
        "NOT_YET_IMPLEMENTED_PatternMatchValue",
        "NOT_YET_IMPLEMENTED_PatternMatchValue",
        *rest  # comment
        ,
    ]:
        pass
    case [
        "NOT_YET_IMPLEMENTED_PatternMatchValue",
        "NOT_YET_IMPLEMENTED_PatternMatchValue",
        *_  # comment
        ,
    ]:
        pass

then formatted to:

    case [
        "NOT_YET_IMPLEMENTED_PatternMatchValue",
        "NOT_YET_IMPLEMENTED_PatternMatchValue",
        *rest,  # comment
    ]:
        pass
    case [
        "NOT_YET_IMPLEMENTED_PatternMatchValue",
        "NOT_YET_IMPLEMENTED_PatternMatchValue",
        *_,  # comment
    ]:
        pass

Copy link
Member

Choose a reason for hiding this comment

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

If you look at

match foo:
    case [* # comment
        rest, 1]:
        pass
    case [*rest # comment
          , 1]:
        pass

you get

{
    Node {
        kind: PatternMatchStar,
        range: 21..45,
        source: `* # comment⏎`,
    }: {
        "leading": [],
        "dangling": [
            SourceComment {
                text: "# comment",
                position: EndOfLine,
                formatted: true,
            },
        ],
        "trailing": [],
    },
    Node {
        kind: PatternMatchStar,
        range: 74..79,
        source: `*rest`,
    }: {
        "leading": [],
        "dangling": [],
        "trailing": [
            SourceComment {
                text: "# comment",
                position: EndOfLine,
                formatted: true,
            },
        ],
    },
}

so i think the best solution is to manually reassign the comment from dangling to trailing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@konstin Thanks for the comment. How can we access this leading-dangiling-trailing mapping?

Copy link
Member

Choose a reason for hiding this comment

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

@harupy - I typically run something like cargo run foo.py --emit=stdout --print-comments from ruff/crates/ruff_python_formatter.

Copy link
Member

Choose a reason for hiding this comment

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

Oh sorry. You're asking how to modify these assignments. Any such modifications need to be done in placement.rs -- those are essentially custom rules that let us change the association of comments. See, e.g., handle_trailing_expression_starred_star_end_of_line_comment which is somewhat similar.

Copy link
Member

Choose a reason for hiding this comment

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

You have to add a case in handle_enclosed_comment for when the enclosing node is PatternMatchStar. The general logic to fix comment placement is in placement.rs/place_comment.

/// ```
fn handle_pattern_match_star_comment(comment: DecoratedComment) -> CommentPlacement {
debug_assert!(comment.enclosing_node().is_pattern_match_star());
let AnyNodeRef::PatternMatchStar(range, ..) = comment.enclosing_node() else {
Copy link
Member

Choose a reason for hiding this comment

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

You can get the ast::PatternMatchStar from the AnyNodeRef::PatternMatchStar(_) in the match and then pass it to the function (like handle_leading_class_with_decorators_comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

diff --git a/crates/ruff_python_formatter/src/comments/placement.rs b/crates/ruff_python_formatter/src/comments/placement.rs
index 01dc6b905..a367e6043 100644
--- a/crates/ruff_python_formatter/src/comments/placement.rs
+++ b/crates/ruff_python_formatter/src/comments/placement.rs
@@ -206,7 +206,7 @@ fn handle_enclosed_comment<'a>(
         }
         AnyNodeRef::WithItem(_) => handle_with_item_comment(comment, locator),
         AnyNodeRef::PatternMatchAs(_) => handle_pattern_match_as_comment(comment, locator),
-        AnyNodeRef::PatternMatchStar(_) => handle_pattern_match_star_comment(comment),
+        AnyNodeRef::PatternMatchStar(range, ..) => CommentPlacement::trailing(range, comment),
         AnyNodeRef::StmtFunctionDef(_) => handle_leading_function_with_decorators_comment(comment),
         AnyNodeRef::StmtClassDef(class_def) => {
             handle_leading_class_with_decorators_comment(comment, class_def)

Can we just do this?

Copy link
Contributor Author

@harupy harupy Aug 23, 2023

Choose a reason for hiding this comment

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

Ah I see, this doesn't work for the following case:

    case [
        1,
        *
        # comment
        rest,
    ]:
        pass

Copy link
Member

Choose a reason for hiding this comment

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

I think I'd suggest treating these as dangling, and then rendering them as leading comments within the node.

Copy link
Member

Choose a reason for hiding this comment

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

(Leading is more consistent with how we treat comments in starred expressions.)

Copy link
Member

Choose a reason for hiding this comment

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

(Tweaked this to merge.)

@charliermarsh charliermarsh enabled auto-merge (squash) August 24, 2023 01:49
@charliermarsh charliermarsh merged commit 205d234 into astral-sh:main Aug 24, 2023
16 checks passed
@harupy harupy deleted the format-PatternMatchStar branch August 24, 2023 01:58
@harupy
Copy link
Contributor Author

harupy commented Aug 24, 2023

@charliermarsh Thanks for the update and merging!

@charliermarsh
Copy link
Member

Thanks @harupy for another great contribution!

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.

Format PatternMatchStar
4 participants