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

implement single_line_let_else_max_width #5790

Merged

Conversation

ytmimi
Copy link
Contributor

@ytmimi ytmimi commented Jun 20, 2023

This is a follow up PR to #5690

This allows users to configure the maximum length of a single line let-else statements. let-else statements that otherwise meet the requirements to be formatted on a single line (as described by the style guide) will have their divergent else block formatted over multiple lines if they exceed this length.

@calebcartwright
Copy link
Member

Thanks for getting this up so quickly! We're only going to get this one shot at it, so will need to make sure this is both complete/perfect (as much as it can be) and stable which is a bit of an usual path for a net-new config opt.

Will try to review later tonight after work

@ytmimi ytmimi force-pushed the let_else_formatting_with_option branch from 0f02d2b to 32fdb2d Compare June 20, 2023 14:01
@calebcartwright
Copy link
Member

also @fee1-dead if you have the bandwidth an are interested in giving this a review your perspective would be greatly appreciated

src/config/config_type.rs Show resolved Hide resolved
src/items.rs Outdated Show resolved Hide resolved
Copy link
Member

@fee1-dead fee1-dead left a comment

Choose a reason for hiding this comment

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

LGTM. Had a few nits

src/config/options.rs Outdated Show resolved Hide resolved
src/items.rs Outdated Show resolved Hide resolved
@ytmimi ytmimi force-pushed the let_else_formatting_with_option branch from c2673b2 to 3c37268 Compare June 23, 2023 11:31
Copy link
Member

@calebcartwright calebcartwright left a comment

Choose a reason for hiding this comment

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

Implementation LGTM, thank you! Few requested changes/additions to the docs and tests then should be good to go

let Some(x) = opt else { return };

let Some(x) = some_very_very_very_very_long_name else {
return;
Copy link
Member

Choose a reason for hiding this comment

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

let's remove the semicolon here.

in this context we want to demonstrate snippets that are eligible for single line based on all other rules, so that whether they are wrapped is solely dependent upon length and the value of the config option. the semis are load bearing though which means the snippets would always have to be wrapped regardless

Suggested change
return;
return

Copy link
Contributor Author

@ytmimi ytmimi Jun 27, 2023

Choose a reason for hiding this comment

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

Unfortunately I can't remove the ;. The configuration_snippet_tests would fail without it since rustfmt will add the ; by default.

Copy link
Member

Choose a reason for hiding this comment

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

Sometimes I forget that we can't readily utilize other config options (e.g. trailing_semicolon) in these snippets.

I also don't recall whether we've discussed this before, but I think having the ability to specify additional options in the snippets (perhaps similar to what we do in the system&idempotence tests?) would be helpful

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 think the idea has briefly come up before. It's a change that I'd really like to see! I think it'll also be very helpful if we need to demonstrate formatting differences between version (and eventually style_edition)

Configurations.md Outdated Show resolved Hide resolved
Configurations.md Show resolved Hide resolved
Comment on lines 2436 to 2444
let Some(y) = opt else {
return;
};
Copy link
Member

Choose a reason for hiding this comment

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

Same ordering suggestion as prior section

Configurations.md Show resolved Hide resolved
src/config/mod.rs Outdated Show resolved Hide resolved
src/config/mod.rs Outdated Show resolved Hide resolved
@@ -236,6 +236,9 @@ pub struct WidthHeuristics {
// Maximum line length for single line if-else expressions. A value
// of zero means always break if-else expressions.
pub(crate) single_line_if_else_max_width: usize,
// Maximum line length for single line let-else statements. A value of zero means
// always format the divergent `else block` over multiple lines.
Copy link
Member

Choose a reason for hiding this comment

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

Same theme as prior comments

Suggested change
// always format the divergent `else block` over multiple lines.
// always format the divergent `else` block over multiple lines.

Comment on lines +145 to +165
let max_width =
std::cmp::min(shape.width, context.config.single_line_let_else_max_width());

// If available_space hits zero we know for sure this will be a multi-lined block
let available_space = max_width.saturating_sub(result.len());

let allow_single_line = !force_newline_else
&& available_space > 0
&& allow_single_line_let_else_block(&result, block);

let mut rw_else_block =
rewrite_let_else_block(block, allow_single_line, context, shape)?;

if allow_single_line && !rw_else_block.contains('\n') {
let available_space = shape.width.saturating_sub(result.len());
if available_space <= rw_else_block.len() {
// writing this on one line would exceed the available width
rw_else_block = rewrite_let_else_block(block, false, context, shape)?;
}
let single_line_else = !rw_else_block.contains('\n');
// +1 for the trailing `;`
let else_block_exceeds_width = rw_else_block.len() + 1 > available_space;

if allow_single_line && single_line_else && else_block_exceeds_width {
// writing this on one line would exceed the available width
// so rewrite the else block over multiple lines.
rw_else_block = rewrite_let_else_block(block, false, context, shape)?;
Copy link
Member

Choose a reason for hiding this comment

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

I concur with some of the earlier discussions on this that the high level theme of limit derivation, checking if a formatted version can fit within that limit, else format over multiple lines, feels like a potentially recurrent pattern that could be encapsulated in some manner that lends itself to reuse.

However, even if so, that's the type of internal change we could make afterwards with no impact on resultant behavior so I'd also agree that should be punted to a hypothetical future iteration.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

great! appreciate you sharing your thoughts on that!

let Some(d) = some_very_very_very_very_long_name else {
return;
};
}
Copy link
Member

Choose a reason for hiding this comment

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

Could we include some additional cases at least here (with the val set to 50) that cover the other init expr scenarios?

E.g.

// Because the initializer expr is multi-lined the else is placed on it's own line.
    let Some(x) =
        some_really_really_really_really_really_really_really_really_really_long_name____H
    else {
        return;
    };

As well as the multilined init expr that starts on the same line as the assignment operator and ends with a sequence of one or more parens/brackets/braces?

@ytmimi
Copy link
Contributor Author

ytmimi commented Jun 27, 2023

@calebcartwright let me know if these additional test cases are good. I'm happy to add any others you think are necessary for the new option.

Comment on lines 22 to 30
let Expr::Slice(ast::ExprSlice {
lower,
upper,
step,
range: _,
}) = slice.as_ref() else {
return;
};
Copy link
Member

Choose a reason for hiding this comment

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

Glad we added this test, as I'm unsure whether this is desired behavior.

It would appear that the prescriptions in https://doc.rust-lang.org/nightly/style-guide/statements.html#else-blocks-let-else-statements do not explicitly cover the scenario where the statement is multilined prior to the the else block with an initializer expression that is single line.

I feel like one could read this:

Otherwise, the else keyword and opening brace should be placed on the next line after the end of the initializer expression, and the else keyword should have the same indentation level as the let keyword.

As implying that if any of the prior scenarios aren't true, then break before the else keyword, but that is rather implicit. Thoughts? 🤔

Copy link
Member

Choose a reason for hiding this comment

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

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'll investigate this case and look into what's goin on. Now that you point it out I think I would have expected it to be formatted like:

    let Expr::Slice(ast::ExprSlice {
        lower,
        upper,
        step,
        range: _,
    }) = slice.as_ref()
    else {
        return;
    };

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The changes to produce the above formatting are pretty minimal. Let me know if I should go ahead and amend this PR once you hear back from t-style.

@calebcartwright
Copy link
Member

I wouldn't be opposed to merging this as-is, and tackling any potential changes needed to address the above scenario in a separate PR.

My only slight reservation is that it may create some ambiguity for folks subscribed to the tracking issue (i.e. this PR and the associated issue are the last pending "big steps" articulated there), though I guess the same would be true for the blog post.

Lmk what you think; I'm probably leaning towards merging but not a strongly held position

@ytmimi
Copy link
Contributor Author

ytmimi commented Jun 27, 2023

updating the PR now

@calebcartwright
Copy link
Member

updating the PR now

in that case I guess we'll park this until there's direction one way or another from t-style 😄

I think there's enough here to justify having separate commits, though I also think the current 10 could probably be condensed into a smaller number. That's not strictly necessary, but wanted to share my observation in case you concur and would be interested in adjusting the commit history

@ytmimi
Copy link
Contributor Author

ytmimi commented Jun 27, 2023

Yeah, there's definitely room to reduce the number of commits. I'm happy to rebase! I just figured it was easier to have individual commits while working on this.

If we get word from t-style I can always remove the last commit. I'll hold off on making changes until it's clear how to move forward, though I guess we have discretion to make this change without an amendment to the guide, since this is the implied formatting from the style guide, correct?

@calebcartwright
Copy link
Member

though I guess we have discretion to make this change without an amendment to the guide, since this is the implied formatting from the style guide, correct?

Eh, I suspect this is something the Style team needs to explicitly respond to one way or the other, even if the position is "we defer to tools to interpret", though I suspect that's the least likely outcome

@calebcartwright
Copy link
Member

though I guess we have discretion to make this change without an amendment to the guide, since this is the implied formatting from the style guide, correct?

Eh, I suspect this is something the Style team needs to explicitly respond to one way or the other, even if the position is "we defer to tools to interpret", though I suspect that's the least likely outcome

Seems wrapping is the expected behavior, and I anticipate an update to the rules to make this more explicit. As such I'm probably going to merge this later tonight, and move the blog post out of draft.

@ytmimi
Copy link
Contributor Author

ytmimi commented Jun 28, 2023

alright, I'll try to get the rebase in soon, just have a few more things I need to do for work

@calebcartwright
Copy link
Member

alright, I'll try to get the rebase in soon, just have a few more things I need to do for work

sorry I forgot about modifying the commit history. no rush, whenever you're ready.

i just wanted to share that we had an answer on the expected behavior from the style team

This allows users to configure the maximum length of a single line
`let-else` statements. `let-else` statements that otherwise meet the
requirements to be formatted on a single line will have their divergent
`else` block formatted over multiple lines if they exceed this length.

**Note**: `single_line_let_else_max_widt` will be introduced as a stable
configuration option.
By reversing the logic I felt that the code became a clearer. Also,
added a comment to make it clear that we need to take the trailing
semicolon for the `let-else` statement into account.
This rule wasn't explicity stated in the style guide so it was missed,
but luckily we caught it during testing.
@ytmimi ytmimi force-pushed the let_else_formatting_with_option branch from e889a3f to 3abd617 Compare June 29, 2023 13:07
@ytmimi
Copy link
Contributor Author

ytmimi commented Jun 29, 2023

@calebcartwright I think we'er good to go now. I've broken this up into 3 commits. The first fully implements single_line_let_else_max_width, while the other 2 make tweaks to the let-else formatting code.

Of those 2 extra commits the first inverts some existing logic, but doesn't change any formatting behavior, and the second implements wrapping the else when the pattern is too long.

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

Successfully merging this pull request may close these issues.

3 participants