implement single_line_let_else_max_width

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.
This commit is contained in:
Yacin Tmimi 2023-04-09 02:13:11 -04:00 committed by Caleb Cartwright
parent 9386b32f5a
commit fe8b72d98e
19 changed files with 528 additions and 7 deletions

View file

@ -2392,6 +2392,78 @@ By default this option is set as a percentage of [`max_width`](#max_width) provi
See also [`max_width`](#max_width) and [`use_small_heuristics`](#use_small_heuristics)
## `single_line_let_else_max_width`
Maximum line length for single line let-else statements.
See the [let-else statement section of the Rust Style Guide](https://github.com/rust-lang/rust/blob/master/src/doc/style-guide/src/statements.md#else-blocks-let-else-statements) for more details on when a let-else statement may be written on a single line.
A value of `0` (zero) means the divergent `else` block will always be formatted over multiple lines.
Note this occurs when `use_small_heuristics` is set to `Off`.
By default this option is set as a percentage of [`max_width`](#max_width) provided by [`use_small_heuristics`](#use_small_heuristics), but a value set directly for `single_line_let_else_max_width` will take precedence.
- **Default value**: `50`
- **Possible values**: any positive integer that is less than or equal to the value specified for [`max_width`](#max_width)
- **Stable**: Yes
#### `50` (default):
```rust
fn main() {
let Some(w) = opt else { return Ok(()) };
let Some(x) = opt else { return };
let Some(y) = opt else {
return;
};
let Some(z) = some_very_very_very_very_long_name else {
return;
};
}
```
#### `0`:
```rust
fn main() {
let Some(w) = opt else {
return Ok(());
};
let Some(x) = opt else {
return;
};
let Some(y) = opt else {
return;
};
let Some(z) = some_very_very_very_very_long_name else {
return;
};
}
```
#### `100`:
```rust
fn main() {
let Some(w) = opt else { return Ok(()) };
let Some(x) = opt else { return };
let Some(y) = opt else {
return;
};
let Some(z) = some_very_very_very_very_long_name else { return };
}
```
See also [`max_width`](#max_width) and [`use_small_heuristics`](#use_small_heuristics)
## `space_after_colon`
Leave a space after the colon.
@ -2804,6 +2876,7 @@ The ratios are:
* [`array_width`](#array_width) - `60%`
* [`chain_width`](#chain_width) - `60%`
* [`single_line_if_else_max_width`](#single_line_if_else_max_width) - `50%`
* [`single_line_let_else_max_width`](#single_line_let_else_max_width) - `50%`
For example when `max_width` is set to `100`, the width settings are:
* `fn_call_width=60`
@ -2813,6 +2886,7 @@ For example when `max_width` is set to `100`, the width settings are:
* `array_width=60`
* `chain_width=60`
* `single_line_if_else_max_width=50`
* `single_line_let_else_max_width=50`
and when `max_width` is set to `200`:
* `fn_call_width=120`
@ -2822,6 +2896,7 @@ and when `max_width` is set to `200`:
* `array_width=120`
* `chain_width=120`
* `single_line_if_else_max_width=100`
* `single_line_let_else_max_width=100`
```rust
enum Lorem {
@ -2891,6 +2966,7 @@ So if `max_width` is set to `200`, then all the width settings are also set to `
* `array_width=200`
* `chain_width=200`
* `single_line_if_else_max_width=200`
* `single_line_let_else_max_width=200`
```rust
enum Lorem {
@ -2918,6 +2994,7 @@ See also:
* [`array_width`](#array_width)
* [`chain_width`](#chain_width)
* [`single_line_if_else_max_width`](#single_line_if_else_max_width)
* [`single_line_let_else_max_width`](#single_line_let_else_max_width)
## `use_try_shorthand`

View file

@ -121,6 +121,7 @@ macro_rules! create_config {
| "use_small_heuristics"
| "fn_call_width"
| "single_line_if_else_max_width"
| "single_line_let_else_max_width"
| "attr_fn_like_width"
| "struct_lit_width"
| "struct_variant_width"
@ -269,6 +270,7 @@ macro_rules! create_config {
| "use_small_heuristics"
| "fn_call_width"
| "single_line_if_else_max_width"
| "single_line_let_else_max_width"
| "attr_fn_like_width"
| "struct_lit_width"
| "struct_variant_width"
@ -407,6 +409,14 @@ macro_rules! create_config {
"single_line_if_else_max_width",
);
self.single_line_if_else_max_width.2 = single_line_if_else_max_width;
let single_line_let_else_max_width = get_width_value(
self.was_set().single_line_let_else_max_width(),
self.single_line_let_else_max_width.2,
heuristics.single_line_let_else_max_width,
"single_line_let_else_max_width",
);
self.single_line_let_else_max_width.2 = single_line_let_else_max_width;
}
fn set_heuristics(&mut self) {

View file

@ -58,6 +58,9 @@ create_config! {
chain_width: usize, 60, true, "Maximum length of a chain to fit on a single line.";
single_line_if_else_max_width: usize, 50, true, "Maximum line length for single line if-else \
expressions. A value of zero means always break if-else expressions.";
single_line_let_else_max_width: usize, 50, true, "Maximum line length for single line \
let-else statements. A value of zero means always format the divergent `else` block \
over multiple lines.";
// Comments. macros, and strings
wrap_comments: bool, false, false, "Break comments to fit on the line";
@ -473,6 +476,9 @@ mod test {
chain_width: usize, 60, true, "Maximum length of a chain to fit on a single line.";
single_line_if_else_max_width: usize, 50, true, "Maximum line length for single \
line if-else expressions. A value of zero means always break if-else expressions.";
single_line_let_else_max_width: usize, 50, false, "Maximum line length for single \
line let-else statements. A value of zero means always format the divergent \
`else` block over multiple lines.";
// Options that are used by the tests
stable_option: bool, false, true, "A stable option";
@ -619,6 +625,7 @@ struct_variant_width = 35
array_width = 60
chain_width = 60
single_line_if_else_max_width = 50
single_line_let_else_max_width = 50
wrap_comments = false
format_code_in_doc_comments = false
doc_comment_code_block_width = 100

View file

@ -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.
pub(crate) single_line_let_else_max_width: usize,
}
impl fmt::Display for WidthHeuristics {
@ -255,6 +258,7 @@ impl WidthHeuristics {
array_width: usize::max_value(),
chain_width: usize::max_value(),
single_line_if_else_max_width: 0,
single_line_let_else_max_width: 0,
}
}
@ -267,6 +271,7 @@ impl WidthHeuristics {
array_width: max_width,
chain_width: max_width,
single_line_if_else_max_width: max_width,
single_line_let_else_max_width: max_width,
}
}
@ -288,6 +293,7 @@ impl WidthHeuristics {
array_width: (60.0 * max_width_ratio).round() as usize,
chain_width: (60.0 * max_width_ratio).round() as usize,
single_line_if_else_max_width: (50.0 * max_width_ratio).round() as usize,
single_line_let_else_max_width: (50.0 * max_width_ratio).round() as usize,
}
}
}

View file

@ -138,17 +138,30 @@ impl Rewrite for ast::Local {
);
result.push_str(&else_kw);
let allow_single_line = allow_single_line_let_else_block(&result, block);
// At this point we've written `let {pat} = {expr} else' into the buffer, and we
// want to calculate up front if there's room to write the divergent block on the
// same line. The available space varies based on indentation so we clamp the width
// on the smaller of `shape.width` and `single_line_let_else_max_width`.
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');
let else_block_exceeds_width = available_space <= rw_else_block.len();
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)?;
}
result.push_str(&rw_else_block);

View file

@ -0,0 +1,40 @@
// rustfmt-single_line_let_else_max_width: 100
fn main() {
let Some(a) = opt else {};
let Some(b) = opt else { return };
let Some(c) = opt else {
return
};
let Some(c) = opt else {
// a comment should always force the block to be multi-lined
return
};
let Some(c) = opt else { /* a comment should always force the block to be multi-lined */ return };
let Some(d) = some_very_very_very_very_long_name else { return };
let Expr::Slice(ast::ExprSlice { lower, upper, step, range: _ }) = slice.as_ref() else {
return
};
let Some((base_place, current)) = self.lower_expr_as_place(current, *base, true)? else {
return Ok(None)
};
let Some(doc_attr) = variant.attrs.iter().find(|attr| attr.path().is_ident("doc")) else {
return Err(Error::new(variant.span(), r#"expected a doc comment"#))
};
let Some((base_place, current)) = self.lower_expr_as_place(current, *base, true) else {
return Ok(None)
};
let Stmt::Expr(Expr::Call(ExprCall { args: some_args, .. }), _) = last_stmt else {
return Err(Error::new(last_stmt.span(), "expected last expression to be `Some(match (..) { .. })`"))
};
}

View file

@ -0,0 +1,40 @@
// rustfmt-single_line_let_else_max_width: 50
fn main() {
let Some(a) = opt else {};
let Some(b) = opt else { return };
let Some(c) = opt else {
return
};
let Some(c) = opt else {
// a comment should always force the block to be multi-lined
return
};
let Some(c) = opt else { /* a comment should always force the block to be multi-lined */ return };
let Some(d) = some_very_very_very_very_long_name else { return };
let Expr::Slice(ast::ExprSlice { lower, upper, step, range: _ }) = slice.as_ref() else {
return
};
let Some((base_place, current)) = self.lower_expr_as_place(current, *base, true)? else {
return Ok(None)
};
let Some(doc_attr) = variant.attrs.iter().find(|attr| attr.path().is_ident("doc")) else {
return Err(Error::new(variant.span(), r#"expected a doc comment"#))
};
let Some((base_place, current)) = self.lower_expr_as_place(current, *base, true) else {
return Ok(None)
};
let Stmt::Expr(Expr::Call(ExprCall { args: some_args, .. }), _) = last_stmt else {
return Err(Error::new(last_stmt.span(), "expected last expression to be `Some(match (..) { .. })`"))
};
}

View file

@ -0,0 +1,40 @@
// rustfmt-single_line_let_else_max_width: 0
fn main() {
let Some(a) = opt else {};
let Some(b) = opt else { return };
let Some(c) = opt else {
return
};
let Some(c) = opt else {
// a comment should always force the block to be multi-lined
return
};
let Some(c) = opt else { /* a comment should always force the block to be multi-lined */ return };
let Some(d) = some_very_very_very_very_long_name else { return };
let Expr::Slice(ast::ExprSlice { lower, upper, step, range: _ }) = slice.as_ref() else {
return
};
let Some((base_place, current)) = self.lower_expr_as_place(current, *base, true)? else {
return Ok(None)
};
let Some(doc_attr) = variant.attrs.iter().find(|attr| attr.path().is_ident("doc")) else {
return Err(Error::new(variant.span(), r#"expected a doc comment"#))
};
let Some((base_place, current)) = self.lower_expr_as_place(current, *base, true) else {
return Ok(None)
};
let Stmt::Expr(Expr::Call(ExprCall { args: some_args, .. }), _) = last_stmt else {
return Err(Error::new(last_stmt.span(), "expected last expression to be `Some(match (..) { .. })`"))
};
}

View file

@ -23,3 +23,13 @@ fn main() {
sit
};
}
fn format_let_else() {
let Some(a) = opt else {};
let Some(b) = opt else { return };
let Some(c) = opt else { return };
let Some(d) = some_very_very_very_very_long_name else { return };
}

View file

@ -23,3 +23,13 @@ fn main() {
sit
};
}
fn format_let_else() {
let Some(a) = opt else {};
let Some(b) = opt else { return };
let Some(c) = opt else { return };
let Some(d) = some_very_very_very_very_long_name else { return };
}

View file

@ -23,3 +23,13 @@ fn main() {
sit
};
}
fn format_let_else() {
let Some(a) = opt else {};
let Some(b) = opt else { return };
let Some(c) = opt else { return };
let Some(d) = some_very_very_very_very_long_name else { return };
}

View file

@ -1,3 +1,5 @@
// rustfmt-single_line_let_else_max_width: 100
fn main() {
// Although this won't compile it still parses so make sure we can format empty else blocks
let Some(x) = opt else {};
@ -149,3 +151,12 @@ fn long_patterns() {
return;
};
}
fn with_trailing_try_operator() {
// Currently the trailing ? forces the else on the next line
// This may be revisited in style edition 2024
let Some(next_bucket) = ranking_rules[cur_ranking_rule_index].next_bucket(ctx, logger, &ranking_rule_universes[cur_ranking_rule_index])? else { return };
// Maybe this is a workaround?
let Ok(Some(next_bucket)) = ranking_rules[cur_ranking_rule_index].next_bucket(ctx, logger, &ranking_rule_universes[cur_ranking_rule_index]) else { return };
}

View file

@ -0,0 +1,59 @@
// rustfmt-single_line_let_else_max_width: 100
fn main() {
let Some(a) = opt else {};
let Some(b) = opt else { return };
let Some(c) = opt else { return };
let Some(c) = opt else {
// a comment should always force the block to be multi-lined
return;
};
let Some(c) = opt else {
/* a comment should always force the block to be multi-lined */
return;
};
let Some(d) = some_very_very_very_very_long_name else { return };
let Expr::Slice(ast::ExprSlice {
lower,
upper,
step,
range: _,
}) = slice.as_ref() else {
return;
};
let Some((base_place, current)) = self.lower_expr_as_place(current, *base, true)? else {
return Ok(None);
};
let Some(doc_attr) = variant
.attrs
.iter()
.find(|attr| attr.path().is_ident("doc"))
else {
return Err(Error::new(variant.span(), r#"expected a doc comment"#));
};
let Some((base_place, current)) = self.lower_expr_as_place(current, *base, true) else {
return Ok(None);
};
let Stmt::Expr(
Expr::Call(ExprCall {
args: some_args, ..
}),
_,
) = last_stmt
else {
return Err(Error::new(
last_stmt.span(),
"expected last expression to be `Some(match (..) { .. })`",
));
};
}

View file

@ -0,0 +1,61 @@
// rustfmt-single_line_let_else_max_width: 50
fn main() {
let Some(a) = opt else {};
let Some(b) = opt else { return };
let Some(c) = opt else { return };
let Some(c) = opt else {
// a comment should always force the block to be multi-lined
return;
};
let Some(c) = opt else {
/* a comment should always force the block to be multi-lined */
return;
};
let Some(d) = some_very_very_very_very_long_name else {
return;
};
let Expr::Slice(ast::ExprSlice {
lower,
upper,
step,
range: _,
}) = slice.as_ref() else {
return;
};
let Some((base_place, current)) = self.lower_expr_as_place(current, *base, true)? else {
return Ok(None);
};
let Some(doc_attr) = variant
.attrs
.iter()
.find(|attr| attr.path().is_ident("doc"))
else {
return Err(Error::new(variant.span(), r#"expected a doc comment"#));
};
let Some((base_place, current)) = self.lower_expr_as_place(current, *base, true) else {
return Ok(None);
};
let Stmt::Expr(
Expr::Call(ExprCall {
args: some_args, ..
}),
_,
) = last_stmt
else {
return Err(Error::new(
last_stmt.span(),
"expected last expression to be `Some(match (..) { .. })`",
));
};
}

View file

@ -0,0 +1,65 @@
// rustfmt-single_line_let_else_max_width: 0
fn main() {
let Some(a) = opt else {};
let Some(b) = opt else {
return;
};
let Some(c) = opt else {
return;
};
let Some(c) = opt else {
// a comment should always force the block to be multi-lined
return;
};
let Some(c) = opt else {
/* a comment should always force the block to be multi-lined */
return;
};
let Some(d) = some_very_very_very_very_long_name else {
return;
};
let Expr::Slice(ast::ExprSlice {
lower,
upper,
step,
range: _,
}) = slice.as_ref() else {
return;
};
let Some((base_place, current)) = self.lower_expr_as_place(current, *base, true)? else {
return Ok(None);
};
let Some(doc_attr) = variant
.attrs
.iter()
.find(|attr| attr.path().is_ident("doc"))
else {
return Err(Error::new(variant.span(), r#"expected a doc comment"#));
};
let Some((base_place, current)) = self.lower_expr_as_place(current, *base, true) else {
return Ok(None);
};
let Stmt::Expr(
Expr::Call(ExprCall {
args: some_args, ..
}),
_,
) = last_stmt
else {
return Err(Error::new(
last_stmt.span(),
"expected last expression to be `Some(match (..) { .. })`",
));
};
}

View file

@ -24,3 +24,15 @@ fn main() {
let lorem = if ipsum { dolor } else { sit };
}
fn format_let_else() {
let Some(a) = opt else {};
let Some(b) = opt else { return };
let Some(c) = opt else { return };
let Some(d) = some_very_very_very_very_long_name else {
return;
};
}

View file

@ -13,3 +13,13 @@ fn main() {
let lorem = if ipsum { dolor } else { sit };
}
fn format_let_else() {
let Some(a) = opt else {};
let Some(b) = opt else { return };
let Some(c) = opt else { return };
let Some(d) = some_very_very_very_very_long_name else { return };
}

View file

@ -23,3 +23,19 @@ fn main() {
sit
};
}
fn format_let_else() {
let Some(a) = opt else {};
let Some(b) = opt else {
return;
};
let Some(c) = opt else {
return;
};
let Some(d) = some_very_very_very_very_long_name else {
return;
};
}

View file

@ -1,3 +1,5 @@
// rustfmt-single_line_let_else_max_width: 100
fn main() {
// Although this won't compile it still parses so make sure we can format empty else blocks
let Some(x) = opt else {};
@ -228,3 +230,25 @@ fn long_patterns() {
return;
};
}
fn with_trailing_try_operator() {
// Currently the trailing ? forces the else on the next line
// This may be revisited in style edition 2024
let Some(next_bucket) = ranking_rules[cur_ranking_rule_index].next_bucket(
ctx,
logger,
&ranking_rule_universes[cur_ranking_rule_index],
)?
else {
return;
};
// Maybe this is a workaround?
let Ok(Some(next_bucket)) = ranking_rules[cur_ranking_rule_index].next_bucket(
ctx,
logger,
&ranking_rule_universes[cur_ranking_rule_index],
) else {
return;
};
}