Skip to content

Format StmtTry#5222

Merged
MichaReiser merged 19 commits intoastral-sh:mainfrom
davidszotten:format-stmt-try
Jun 28, 2023
Merged

Format StmtTry#5222
MichaReiser merged 19 commits intoastral-sh:mainfrom
davidszotten:format-stmt-try

Conversation

@davidszotten
Copy link
Contributor

@davidszotten davidszotten commented Jun 20, 2023

Format StmtTry

@MichaReiser MichaReiser linked an issue Jun 21, 2023 that may be closed by this pull request
@konstin konstin added the formatter Related to the formatter label Jun 21, 2023
@davidszotten davidszotten force-pushed the format-stmt-try branch 2 times, most recently from 153c3a8 to 106c0e3 Compare June 21, 2023 13:56
@konstin
Copy link
Member

konstin commented Jun 21, 2023

Personally, i find it easier to add fixtures first so i can directly see what my code currently does, with fixtures in the PR it is also easier to give guidance since i can directly see how it currently behaves (I usually run cargo run --bin ruff_python_formatter -- --emit stdout --print-ir --print-comments scratch.py for a specific problem, then copy it to the fixture, run INSTA_UPDATE=always cargo test --package ruff_python_formatter and look at git diff, others like cargo insta review, etc.).

@github-actions
Copy link
Contributor

github-actions bot commented Jun 21, 2023

PR Check Results

Ecosystem

✅ ecosystem check detected no changes.

Benchmark

Linux

group                                      main                                   pr
-----                                      ----                                   --
formatter/large/dataset.py                 1.00      7.8±0.18ms     5.2 MB/sec    1.00      7.8±0.13ms     5.2 MB/sec
formatter/numpy/ctypeslib.py               1.00  1831.3±26.59µs     9.1 MB/sec    1.01  1847.4±28.20µs     9.0 MB/sec
formatter/numpy/globals.py                 1.03    218.3±5.26µs    13.5 MB/sec    1.00    211.2±5.69µs    14.0 MB/sec
formatter/pydantic/types.py                1.04      4.1±0.05ms     6.2 MB/sec    1.00      4.0±0.07ms     6.4 MB/sec
linter/all-rules/large/dataset.py          1.04     16.2±0.32ms     2.5 MB/sec    1.00     15.6±0.28ms     2.6 MB/sec
linter/all-rules/numpy/ctypeslib.py        1.03      4.0±0.05ms     4.1 MB/sec    1.00      3.9±0.07ms     4.3 MB/sec
linter/all-rules/numpy/globals.py          1.02    513.5±4.11µs     5.7 MB/sec    1.00    504.7±4.40µs     5.8 MB/sec
linter/all-rules/pydantic/types.py         1.05      7.2±0.06ms     3.5 MB/sec    1.00      6.9±0.11ms     3.7 MB/sec
linter/default-rules/large/dataset.py      1.10      8.5±0.09ms     4.8 MB/sec    1.00      7.7±0.17ms     5.3 MB/sec
linter/default-rules/numpy/ctypeslib.py    1.08  1825.3±22.12µs     9.1 MB/sec    1.00  1690.6±33.14µs     9.8 MB/sec
linter/default-rules/numpy/globals.py      1.08    203.6±1.46µs    14.5 MB/sec    1.00    189.4±3.12µs    15.6 MB/sec
linter/default-rules/pydantic/types.py     1.09      3.8±0.06ms     6.7 MB/sec    1.00      3.5±0.04ms     7.3 MB/sec

Windows

group                                      main                                   pr
-----                                      ----                                   --
formatter/large/dataset.py                 1.00      8.1±0.10ms     5.0 MB/sec    1.19      9.6±0.11ms     4.2 MB/sec
formatter/numpy/ctypeslib.py               1.00  1817.4±33.40µs     9.2 MB/sec    1.14      2.1±0.02ms     8.0 MB/sec
formatter/numpy/globals.py                 1.00    211.1±4.34µs    14.0 MB/sec    1.09    230.9±7.00µs    12.8 MB/sec
formatter/pydantic/types.py                1.00      4.1±0.08ms     6.2 MB/sec    1.13      4.7±0.07ms     5.5 MB/sec
linter/all-rules/large/dataset.py          1.00     15.7±0.20ms     2.6 MB/sec    1.00     15.8±0.36ms     2.6 MB/sec
linter/all-rules/numpy/ctypeslib.py        1.01      4.1±0.07ms     4.0 MB/sec    1.00      4.1±0.05ms     4.1 MB/sec
linter/all-rules/numpy/globals.py          1.00    498.8±7.67µs     5.9 MB/sec    1.00    499.2±7.48µs     5.9 MB/sec
linter/all-rules/pydantic/types.py         1.01      7.0±0.12ms     3.6 MB/sec    1.00      7.0±0.10ms     3.7 MB/sec
linter/default-rules/large/dataset.py      1.00      8.1±0.09ms     5.0 MB/sec    1.00      8.1±0.11ms     5.0 MB/sec
linter/default-rules/numpy/ctypeslib.py    1.00  1742.5±29.80µs     9.6 MB/sec    1.00  1746.1±21.72µs     9.5 MB/sec
linter/default-rules/numpy/globals.py      1.00    200.0±3.85µs    14.8 MB/sec    1.00    199.8±3.39µs    14.8 MB/sec
linter/default-rules/pydantic/types.py     1.00      3.7±0.07ms     6.9 MB/sec    1.02      3.7±0.05ms     6.8 MB/sec

...
# before except 2
except (
KeyError # except line 2
Copy link
Member

Choose a reason for hiding this comment

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

as far as i can see this is currently the only misplaced comment, right?

black also seems to remove the parentheses here even though it otherwise doesn't

Copy link
Contributor Author

Choose a reason for hiding this comment

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

comment placement looks ok to me here (except line 2)

but i don't understand what causes us to break the (KeyError) over multiple lines

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Member

@konstin konstin Jun 21, 2023

Choose a reason for hiding this comment

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

black format

try:
    ...
except (KeyError) as key:  # except line 2
    ...

as

try:
    ...
except KeyError as key:  # except line 2
    ...

which looks better to me. This is another one of the rare cases where black removes parentheses

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i agree that it's better to remove them. but i'm not sure what is preserving them (i was wrong above, here it isn't a tuple but just an ExprName)

Copy link
Member

Choose a reason for hiding this comment

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

You might need to change the attachment of # except line 2 in placement.rs, either attach it trailing to a node that doesn't break the line if it doesn't need to or mark it as dangling and manually format after the colon. we're doing the latter (formatting after : manually) in some other nodes

Copy link
Member

Choose a reason for hiding this comment

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

Yeah... The fact that there's no explicit node for bodies really makes things annoying. The issue here is that the except line 2 comment falls into the middle of the ExceptHandlerExceptHandler node (it is inside of that node's range). The last preceding node is the KeyError because key (Identifier) is not an AST node (it probably should, but it is not 😢).

We probably need something similar to (or extend)

fn handle_trailing_end_of_line_condition_comment<'a>(
comment: DecoratedComment<'a>,
locator: &Locator,
) -> CommentPlacement<'a> {

@davidszotten
Copy link
Contributor Author

add fixtures first

just to clarify: did you look at the pr before i committed the fixtures (a bit late in the process) or are you suggesting committing them first (even before any code changes?) in a separate commit to then see changes as the impl is added?

@davidszotten
Copy link
Contributor Author

til about --print-ir and --print-comments

very helpful, have been doing lots of this manually with dbg statements, d'oh

@konstin
Copy link
Member

konstin commented Jun 21, 2023

just to clarify: did you look at the pr before i committed the fixtures (a bit late in the process) or are you suggesting committing them first (even before any code changes?) in a separate commit to then see changes as the impl is added?

No my idea was just to put code you tested in the fixtures so other people can also see them when looking at your PR. We squash merge PRs, so commit order or structure is not important for ruff PRs. Personally, i try to skim external contributor draft PRs once they are created and comment if i see something, and add a thorough review once the PR is marked as ready. (You can of course always ping me or others on draft PRs if you have specific questions)

@davidszotten
Copy link
Contributor Author

just to clarify: did you look at the pr before i committed the fixtures (a bit late in the process) or are you suggesting committing them first (even before any code changes?) in a separate commit to then see changes as the impl is added?

No my idea was just to put code you tested in the fixtures so other people can also see them when looking at your PR. We squash merge PRs, so commit order or structure is not important for ruff PRs. Personally, i try to skim external contributor draft PRs once they are created and comment if i see something, and add a thorough review once the PR is marked as ready. (You can of course always ping me or others on draft PRs if you have specific questions)

are you still missing something i've referenced but not included here (in try.py)?

if this is about timing, makes sense, i'll try to commit my scratch into a fixture sooner

@konstin
Copy link
Member

konstin commented Jun 21, 2023

The try.py looks even better than what i had in mind, thanks for adding this! My second comment was meant to be about general ruff PR procedures, not about this PR specifically

@davidszotten davidszotten changed the title wip Format StmtTry Format StmtTry Jun 21, 2023
@davidszotten davidszotten marked this pull request as ready for review June 21, 2023 20:09
...
# before except 2
except (
KeyError # except line 2
Copy link
Member

Choose a reason for hiding this comment

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

You might need to change the attachment of # except line 2 in placement.rs, either attach it trailing to a node that doesn't break the line if it doesn't need to or mark it as dangling and manually format after the colon. we're doing the latter (formatting after : manually) in some other nodes

}
}

leading_node_comments(except_handler_except_handler).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.

nit: can you merge those into the write call below?

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.

This looks really good. I putting this back in your queue, mainly to move the ExceptHandler formatting to FormatExceptHandlerExceptHandler.

Feel free to ignore the incorrectly placed comment for now and instead add a TODO. We can tackle this as a separate pR

...
# before except 2
except (
KeyError # except line 2
Copy link
Member

Choose a reason for hiding this comment

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

Yeah... The fact that there's no explicit node for bodies really makes things annoying. The issue here is that the except line 2 comment falls into the middle of the ExceptHandlerExceptHandler node (it is inside of that node's range). The last preceding node is the KeyError because key (Identifier) is not an AST node (it probably should, but it is not 😢).

We probably need something similar to (or extend)

fn handle_trailing_end_of_line_condition_comment<'a>(
comment: DecoratedComment<'a>,
locator: &Locator,
) -> CommentPlacement<'a> {

@davidszotten
Copy link
Contributor Author

thanks for the comments, will see how i get on. while waiting for them, i also tried to see if i could re-use this code for TryStar but had one annoyance and one blocker

annoyance: since their contents is the same i tried destructing the item and then passing the components to a helper function to do the work, but when borrowing them across the function barrier i had e.g. body: &[Stmt<TextRange>] instead of body: Vec<_> and i wasn't able to call body.format(). i tried a few of the compiler suggestions but this was already at the limit of my rust knowledge. i worked around this by making an enum and passing the entire item

blocker: the except statement is emitted by the handler fmt, how do i pass context in there (or where can it look) so it knows if it's inside a try or a trystar?

@MichaReiser
Copy link
Member

MichaReiser commented Jun 22, 2023

thanks for the comments, will see how i get on. while waiting for them, i also tried to see if i could re-use this code for TryStar but had one annoyance and one blocker

annoyance: since their contents is the same i tried destructing the item and then passing the components to a helper function to do the work, but when borrowing them across the function barrier i had e.g. body: &[Stmt<TextRange>] instead of body: Vec<_> and i wasn't able to call body.format(). i tried a few of the compiler suggestions but this was already at the limit of my rust knowledge. i worked around this by making an enum and passing the entire item

One pattern that worked well in the past is to either:

I don't have a strong preference for either.

blocker: the except statement is emitted by the handler fmt, how do i pass context in there (or where can it look) so it knows if it's inside a try or a trystar?

You can implement FormatRuleWithOptions and then call your formatting like this

except_handler.format().with_options(Context::TryStar).fmt(f)

See FormatExpr as an example.

@davidszotten davidszotten force-pushed the format-stmt-try branch 2 times, most recently from 968726f to cfe8ac1 Compare June 23, 2023 09:31
@MichaReiser
Copy link
Member

I'm so impressed by how you tackle this. You're probably working on the most complicated statement!

@davidszotten
Copy link
Contributor Author

I'm so impressed by how you tackle this. You're probably working on the most complicated statement!

Thanks, and again, thanks for your patience and help!


return CommentPlacement::dangling(comment.enclosing_node(), 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.

would probably be a good idea to have some tests for these functions. not sure what we have available to make tests ergonomic. will have a look around, but you might have good ideas already

Copy link
Member

Choose a reason for hiding this comment

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

We have some snapshot tests in the comments/mod.rs file, that test if the comments get associated with the right node. But they are quiet similar to the formatting snapshot tests.

#[test]
fn if_elif_else_comments() {
let source = r#"
if x == y:
pass # trailing `pass` comment
# Root `if` trailing comment
# Leading elif comment
elif x < y:
pass
# `elif` trailing comment
# Leading else comment
else:
pass
# `else` trailing comment
"#;
let test_case = CommentsTestCase::from_code(source);
let comments = test_case.to_comments();
assert_debug_snapshot!(comments.debug(test_case.source_code));
}

I plan to review your PR on Monday.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes i did find those and tried to (ab?)use them to generate input i could pass to the functions here like handle_trailing_end_of_line_except_comment but i failed.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah. It is testing the whole place_comment and not only handle_treiling_end_of_line_except_comment. This is intentional because the construction of the DecoratedComment is very specific and easy to get wrong when constructing by hand (and we want to make sure that changes the DecoratedComment construction resulting in a failure of the except comment placement are catched too). But you can tailor your source code so that it calls your function with the arguments that you want to test.

@davidszotten davidszotten requested a review from MichaReiser June 23, 2023 17:45
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.

This looks good to me except that I think that some comment placement is incorrect. I'll leave it up to you if you want to:

  • Split the comment placement fixes into its own PR
  • Follow up as part of this PR

@MichaReiser
Copy link
Member

MichaReiser commented Jun 26, 2023

@davidszotten is this PR ready for merging from your side(besides the merge conflicts?). I lost the overview.

still working on comments
i think there are still issues with blank lines between the sections
seems to fix some other cases but not `except (KeyError):  # comment`
…h_comments

TODO: now misses a blank newline before a leading comment to the first except
block
refactor
`handle_in_between_except_handlers_or_except_handler_and_else_or_finally_comment`
with early returns to avoid very nested indentation and add a check for
subsequent except handlers to attach comments to (as leading) instead of
leaving as dangling on the try for else/finally
@davidszotten
Copy link
Contributor Author

@davidszotten is this PR ready for merging from your side(besides the merge conflicts?). I lost the overview.

#5222 (comment) and #5222 (comment) are waiting for your replies i think

@MichaReiser MichaReiser enabled auto-merge (squash) June 28, 2023 09:52
@MichaReiser MichaReiser merged commit 1979103 into astral-sh:main Jun 28, 2023
@davidszotten
Copy link
Contributor Author

🎉 thanks

@MichaReiser
Copy link
Member

Thank you!

@davidszotten davidszotten deleted the format-stmt-try branch July 7, 2023 20:47
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: Try / TryStar (includes ExceptHandler)

3 participants