Implement trailing_semicolon = "Preserve"#6149
Implement trailing_semicolon = "Preserve"#6149compiler-errors wants to merge 6 commits intorust-lang:mainfrom
trailing_semicolon = "Preserve"#6149Conversation
src/expr.rs
Outdated
| let shape = if expr_type == ExprType::Statement | ||
| && semicolon_for_expr_extra_hacky_double_counted_spacing(context, expr) | ||
| { |
There was a problem hiding this comment.
I cannot remember what the bug policy here is, but ideally we'd just remove this adjustment to shape since it's already accounted for in format_stmt. That causes self-formatting test to fail because some expressions actually fit onto one line.
There was a problem hiding this comment.
You can fix the shape adjustment if the fix is version gated to version=Two. You could updated the logic so that we only sub 1 when version=One is set,
There was a problem hiding this comment.
I fixed it in Version=Two, added a test for one&two, and fixed the cases that changed rustfmt's own formatting (since rustfmt apparently uses Two! 😆)
src/expr.rs
Outdated
| let shape = if expr_type == ExprType::Statement | ||
| && semicolon_for_expr_extra_hacky_double_counted_spacing(context, expr) | ||
| { |
There was a problem hiding this comment.
You can fix the shape adjustment if the fix is version gated to version=Two. You could updated the logic so that we only sub 1 when version=One is set,
| let result = match stmt.kind { | ||
| ast::StmtKind::Local(ref local) => local.rewrite(context, shape), | ||
| ast::StmtKind::Expr(ref ex) | ast::StmtKind::Semi(ref ex) => { | ||
| ast::StmtKind::Semi(ref ex) => { |
There was a problem hiding this comment.
was this the cause of the double semicolon counting?
There was a problem hiding this comment.
See the comment below; I pointed out the reason for the the double semicolon counting in more detail.
I split these two branches because we need to preserve semicolons only if we have a StmtKind::Semi, and not if we have a StmtKind::Expr.
src/utils.rs
Outdated
| /// Previously, we used to have `trailing_semicolon = always` enabled, and due to | ||
| /// a bug between `format_stmt` and `format_expr`, we used to subtract the size of | ||
| /// `;` *TWICE* from the shape. This means that an expr that would fit onto a line | ||
| /// of, e.g. 99 (limit 100) after subtracting one for the semicolon would still be | ||
| /// wrapped. | ||
| /// | ||
| /// This function reimplements the old heuristic of double counting the "phantom" | ||
| /// semicolon that should have already been accounted for, to not break existing | ||
| /// formatting with the `trailing_semicolon = preserve` behavior. |
There was a problem hiding this comment.
Would you mind showing an example where things are wrapping early, maybe even adding a version=One test case if we go the route of fixing things for version=Two.
There was a problem hiding this comment.
fn main() {
return hellooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo(arg);
}
The return statement line is exactly 100 chars long. When formatting the expression, we first subtract one from the shape here:
Lines 119 to 128 in 7289391
(See let shape = shape.sub_width(suffix.len())?; )
And then once again here:
Lines 67 to 71 in 7289391
(in format_expr).
In practice, for example, we need to format return helloo...o(arg) which is an expression that is 95 characters long (100 - 4 identation - 1 semicolon) but after subtracting from the shape twice, we only have have a budget of 94 characters, even though the whole expression should fit in the line.
I can add it as a version=One test.
There was a problem hiding this comment.
For the record, i discovered this because it happens twice in practice in the rustfmt codebase, so the self-formatting test failed.
There was a problem hiding this comment.
Yeah, we use version=Two within rustfmt as a way to dogfood unstable formatting (at least that's my assumption)
| /// (`return`/`break`/`continue`). | ||
| pub enum TrailingSemicolon { | ||
| /// Always rewrite `return;` and `break;` expressions to have a trailing semicolon, | ||
| /// unless the block is a single-line block, e.g. `let PAT = e else { return }`. |
There was a problem hiding this comment.
This is kind of weird, but preexisting behavior, I believe.
I don't exactly understand what rustfmt does to avoid adding a semicolon for single-line-style blocks.
There was a problem hiding this comment.
There are different kinds of blocks. What kinds of blocks are you referencing? Just let-else blocks?
To the best of my knowledge all let-else blocks are handled when rewriting ast::Local nodes. The code removes 1 from the shape to account for the trailing semicolon (line 127). Maybe it shouldn't?
Lines 49 to 188 in 4b1596f
There was a problem hiding this comment.
I'm pretty sure all block rewriting eventually calls into rewrite_block_inner, which eventually delegates to a few other block rewrite functions, e.g rewrite_empty_block or rewrite_single_line_block, and I'm fairly certain none of the block rewriting code handles trailing semicolons.
| /// Always rewrite `return;` and `break;` expressions to have a trailing semicolon, | ||
| /// unless the block is a single-line block, e.g. `let PAT = e else { return }`. | ||
| Always, | ||
| /// Always return `return` and `break` expressions to remove the trailing semicolon. |
There was a problem hiding this comment.
Just want to avoid any confusion with the docs. Since this is the Never variant it might be better to not use the word "Always" in the description. One potential rewording:
/// Never add a trailing semicolon. This removes trailing semicolon's from `return` and `break` expressions.
Also, this won't remove them from continue;?
| ```rust | ||
| fn foo() -> usize { | ||
| return 0; | ||
| } | ||
|
|
||
| fn bar() -> usize { | ||
| return 0 | ||
| } | ||
| ``` |
There was a problem hiding this comment.
These aren't changes we necessarily need to make, but it might also be nice to include examples with break; and continue; to further demonstrate how this will work. Possibly adding a let-else example.
| /// semicolon that should have already been accounted for, to not break existing | ||
| /// formatting with the `trailing_semicolon = Preserve` behavior for Version = One. | ||
| #[inline] | ||
| pub(crate) fn semicolon_for_expr_extra_hacky_double_counted_spacing( |
There was a problem hiding this comment.
Do we still need this function if we're version gating the fix and only applying the old behavior for version=One?
| @@ -0,0 +1,49 @@ | |||
| // rustfmt-trailing_semicolon: Never | |||
There was a problem hiding this comment.
value should be Preserve here, or filename should be different if not
|
Overall lgtm, my impression is that there's only a couple outstanding nits so this is definitely a change we can and will make for 2024. From a sequencing pov, I'd like to make sure we get the style guide text tweaked and (once merged) this PR in a subtree sync so that the style and formatter changes are as together as possible. Doesn't have to literally be the same day but close proximity would be ideal. AFAIK we're still blocked on the next sync (upstream issue in a dependency) so this will be on-hold for a short period. The off-by-one bug fix is something we should capture on #5577 as well to ensure we're tracking and can announce it as part of 2024 style edition changes, but I'd also like to see how this changes other codebases on the diff check to get a feel for the impact. Related comment albeit not salient to merging this PR.. the original off by one issue looks like another case where one part of the rustfmt codebase was doing something it probably shouldn't have had the responsibility for doing in the first place, and then another part of the codebase was trying to reuse it and needed some internal logic accounting for that extra step. There's too many cases where functions are doing things adding/removing spaces or other tokens that other functions are then trying to account for or peel back, so let's use this as a reminder to ourselves to try to avoid taking routes that appear more expedient in the moment. If it feels/smells wrong, then it probably is and some broader refactoring is likely warranted |
@calebcartwright I think you might have missed this #6140 (comment), but we're not blocked on dependencies anymore. |
Implement
trailing_semicolon = "Preserve"to preserve existing semicolons but also don't add new ones even if, e.g., we break a block.I discovered some bugs in the formatting of
return REALLY_LONG_EXPR;where the expression itself would fit onto one line, but due to double counting (subtracting the size of the semicolon in bothformat_stmtAND informat_expr) we end up breaking up the statement at LIMIT - 1 rather than LIMIT.See the comment on
semicolon_for_expr_extra_hacky_double_counted_spacing. Bikeshed that name if you want.