Skip to content

Commit fe8b72d

Browse files
ytmimicalebcartwright
authored andcommitted
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.
1 parent 9386b32 commit fe8b72d

File tree

19 files changed

+528
-7
lines changed

19 files changed

+528
-7
lines changed

Configurations.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2392,6 +2392,78 @@ By default this option is set as a percentage of [`max_width`](#max_width) provi
23922392

23932393
See also [`max_width`](#max_width) and [`use_small_heuristics`](#use_small_heuristics)
23942394

2395+
## `single_line_let_else_max_width`
2396+
2397+
Maximum line length for single line let-else statements.
2398+
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.
2399+
A value of `0` (zero) means the divergent `else` block will always be formatted over multiple lines.
2400+
Note this occurs when `use_small_heuristics` is set to `Off`.
2401+
2402+
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.
2403+
2404+
- **Default value**: `50`
2405+
- **Possible values**: any positive integer that is less than or equal to the value specified for [`max_width`](#max_width)
2406+
- **Stable**: Yes
2407+
2408+
#### `50` (default):
2409+
2410+
```rust
2411+
fn main() {
2412+
let Some(w) = opt else { return Ok(()) };
2413+
2414+
let Some(x) = opt else { return };
2415+
2416+
let Some(y) = opt else {
2417+
return;
2418+
};
2419+
2420+
let Some(z) = some_very_very_very_very_long_name else {
2421+
return;
2422+
};
2423+
}
2424+
```
2425+
2426+
#### `0`:
2427+
2428+
```rust
2429+
fn main() {
2430+
let Some(w) = opt else {
2431+
return Ok(());
2432+
};
2433+
2434+
let Some(x) = opt else {
2435+
return;
2436+
};
2437+
2438+
let Some(y) = opt else {
2439+
return;
2440+
};
2441+
2442+
let Some(z) = some_very_very_very_very_long_name else {
2443+
return;
2444+
};
2445+
}
2446+
```
2447+
2448+
#### `100`:
2449+
2450+
```rust
2451+
fn main() {
2452+
let Some(w) = opt else { return Ok(()) };
2453+
2454+
let Some(x) = opt else { return };
2455+
2456+
let Some(y) = opt else {
2457+
return;
2458+
};
2459+
2460+
let Some(z) = some_very_very_very_very_long_name else { return };
2461+
}
2462+
```
2463+
2464+
See also [`max_width`](#max_width) and [`use_small_heuristics`](#use_small_heuristics)
2465+
2466+
23952467
## `space_after_colon`
23962468

23972469
Leave a space after the colon.
@@ -2804,6 +2876,7 @@ The ratios are:
28042876
* [`array_width`](#array_width) - `60%`
28052877
* [`chain_width`](#chain_width) - `60%`
28062878
* [`single_line_if_else_max_width`](#single_line_if_else_max_width) - `50%`
2879+
* [`single_line_let_else_max_width`](#single_line_let_else_max_width) - `50%`
28072880

28082881
For example when `max_width` is set to `100`, the width settings are:
28092882
* `fn_call_width=60`
@@ -2813,6 +2886,7 @@ For example when `max_width` is set to `100`, the width settings are:
28132886
* `array_width=60`
28142887
* `chain_width=60`
28152888
* `single_line_if_else_max_width=50`
2889+
* `single_line_let_else_max_width=50`
28162890

28172891
and when `max_width` is set to `200`:
28182892
* `fn_call_width=120`
@@ -2822,6 +2896,7 @@ and when `max_width` is set to `200`:
28222896
* `array_width=120`
28232897
* `chain_width=120`
28242898
* `single_line_if_else_max_width=100`
2899+
* `single_line_let_else_max_width=100`
28252900

28262901
```rust
28272902
enum Lorem {
@@ -2891,6 +2966,7 @@ So if `max_width` is set to `200`, then all the width settings are also set to `
28912966
* `array_width=200`
28922967
* `chain_width=200`
28932968
* `single_line_if_else_max_width=200`
2969+
* `single_line_let_else_max_width=200`
28942970

28952971
```rust
28962972
enum Lorem {
@@ -2918,6 +2994,7 @@ See also:
29182994
* [`array_width`](#array_width)
29192995
* [`chain_width`](#chain_width)
29202996
* [`single_line_if_else_max_width`](#single_line_if_else_max_width)
2997+
* [`single_line_let_else_max_width`](#single_line_let_else_max_width)
29212998

29222999
## `use_try_shorthand`
29233000

src/config/config_type.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ macro_rules! create_config {
121121
| "use_small_heuristics"
122122
| "fn_call_width"
123123
| "single_line_if_else_max_width"
124+
| "single_line_let_else_max_width"
124125
| "attr_fn_like_width"
125126
| "struct_lit_width"
126127
| "struct_variant_width"
@@ -269,6 +270,7 @@ macro_rules! create_config {
269270
| "use_small_heuristics"
270271
| "fn_call_width"
271272
| "single_line_if_else_max_width"
273+
| "single_line_let_else_max_width"
272274
| "attr_fn_like_width"
273275
| "struct_lit_width"
274276
| "struct_variant_width"
@@ -407,6 +409,14 @@ macro_rules! create_config {
407409
"single_line_if_else_max_width",
408410
);
409411
self.single_line_if_else_max_width.2 = single_line_if_else_max_width;
412+
413+
let single_line_let_else_max_width = get_width_value(
414+
self.was_set().single_line_let_else_max_width(),
415+
self.single_line_let_else_max_width.2,
416+
heuristics.single_line_let_else_max_width,
417+
"single_line_let_else_max_width",
418+
);
419+
self.single_line_let_else_max_width.2 = single_line_let_else_max_width;
410420
}
411421

412422
fn set_heuristics(&mut self) {

src/config/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ create_config! {
5858
chain_width: usize, 60, true, "Maximum length of a chain to fit on a single line.";
5959
single_line_if_else_max_width: usize, 50, true, "Maximum line length for single line if-else \
6060
expressions. A value of zero means always break if-else expressions.";
61+
single_line_let_else_max_width: usize, 50, true, "Maximum line length for single line \
62+
let-else statements. A value of zero means always format the divergent `else` block \
63+
over multiple lines.";
6164

6265
// Comments. macros, and strings
6366
wrap_comments: bool, false, false, "Break comments to fit on the line";
@@ -473,6 +476,9 @@ mod test {
473476
chain_width: usize, 60, true, "Maximum length of a chain to fit on a single line.";
474477
single_line_if_else_max_width: usize, 50, true, "Maximum line length for single \
475478
line if-else expressions. A value of zero means always break if-else expressions.";
479+
single_line_let_else_max_width: usize, 50, false, "Maximum line length for single \
480+
line let-else statements. A value of zero means always format the divergent \
481+
`else` block over multiple lines.";
476482

477483
// Options that are used by the tests
478484
stable_option: bool, false, true, "A stable option";
@@ -619,6 +625,7 @@ struct_variant_width = 35
619625
array_width = 60
620626
chain_width = 60
621627
single_line_if_else_max_width = 50
628+
single_line_let_else_max_width = 50
622629
wrap_comments = false
623630
format_code_in_doc_comments = false
624631
doc_comment_code_block_width = 100

src/config/options.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,9 @@ pub struct WidthHeuristics {
236236
// Maximum line length for single line if-else expressions. A value
237237
// of zero means always break if-else expressions.
238238
pub(crate) single_line_if_else_max_width: usize,
239+
// Maximum line length for single line let-else statements. A value of zero means
240+
// always format the divergent `else` block over multiple lines.
241+
pub(crate) single_line_let_else_max_width: usize,
239242
}
240243

241244
impl fmt::Display for WidthHeuristics {
@@ -255,6 +258,7 @@ impl WidthHeuristics {
255258
array_width: usize::max_value(),
256259
chain_width: usize::max_value(),
257260
single_line_if_else_max_width: 0,
261+
single_line_let_else_max_width: 0,
258262
}
259263
}
260264

@@ -267,6 +271,7 @@ impl WidthHeuristics {
267271
array_width: max_width,
268272
chain_width: max_width,
269273
single_line_if_else_max_width: max_width,
274+
single_line_let_else_max_width: max_width,
270275
}
271276
}
272277

@@ -288,6 +293,7 @@ impl WidthHeuristics {
288293
array_width: (60.0 * max_width_ratio).round() as usize,
289294
chain_width: (60.0 * max_width_ratio).round() as usize,
290295
single_line_if_else_max_width: (50.0 * max_width_ratio).round() as usize,
296+
single_line_let_else_max_width: (50.0 * max_width_ratio).round() as usize,
291297
}
292298
}
293299
}

src/items.rs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -138,17 +138,30 @@ impl Rewrite for ast::Local {
138138
);
139139
result.push_str(&else_kw);
140140

141-
let allow_single_line = allow_single_line_let_else_block(&result, block);
141+
// At this point we've written `let {pat} = {expr} else' into the buffer, and we
142+
// want to calculate up front if there's room to write the divergent block on the
143+
// same line. The available space varies based on indentation so we clamp the width
144+
// on the smaller of `shape.width` and `single_line_let_else_max_width`.
145+
let max_width =
146+
std::cmp::min(shape.width, context.config.single_line_let_else_max_width());
147+
148+
// If available_space hits zero we know for sure this will be a multi-lined block
149+
let available_space = max_width.saturating_sub(result.len());
150+
151+
let allow_single_line = !force_newline_else
152+
&& available_space > 0
153+
&& allow_single_line_let_else_block(&result, block);
142154

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

146-
if allow_single_line && !rw_else_block.contains('\n') {
147-
let available_space = shape.width.saturating_sub(result.len());
148-
if available_space <= rw_else_block.len() {
149-
// writing this on one line would exceed the available width
150-
rw_else_block = rewrite_let_else_block(block, false, context, shape)?;
151-
}
158+
let single_line_else = !rw_else_block.contains('\n');
159+
let else_block_exceeds_width = available_space <= rw_else_block.len();
160+
161+
if allow_single_line && single_line_else && else_block_exceeds_width {
162+
// writing this on one line would exceed the available width
163+
// so rewrite the else block over multiple lines.
164+
rw_else_block = rewrite_let_else_block(block, false, context, shape)?;
152165
}
153166

154167
result.push_str(&rw_else_block);
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// rustfmt-single_line_let_else_max_width: 100
2+
3+
fn main() {
4+
let Some(a) = opt else {};
5+
6+
let Some(b) = opt else { return };
7+
8+
let Some(c) = opt else {
9+
return
10+
};
11+
12+
let Some(c) = opt else {
13+
// a comment should always force the block to be multi-lined
14+
return
15+
};
16+
17+
let Some(c) = opt else { /* a comment should always force the block to be multi-lined */ return };
18+
19+
let Some(d) = some_very_very_very_very_long_name else { return };
20+
21+
let Expr::Slice(ast::ExprSlice { lower, upper, step, range: _ }) = slice.as_ref() else {
22+
return
23+
};
24+
25+
let Some((base_place, current)) = self.lower_expr_as_place(current, *base, true)? else {
26+
return Ok(None)
27+
};
28+
29+
let Some(doc_attr) = variant.attrs.iter().find(|attr| attr.path().is_ident("doc")) else {
30+
return Err(Error::new(variant.span(), r#"expected a doc comment"#))
31+
};
32+
33+
let Some((base_place, current)) = self.lower_expr_as_place(current, *base, true) else {
34+
return Ok(None)
35+
};
36+
37+
let Stmt::Expr(Expr::Call(ExprCall { args: some_args, .. }), _) = last_stmt else {
38+
return Err(Error::new(last_stmt.span(), "expected last expression to be `Some(match (..) { .. })`"))
39+
};
40+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// rustfmt-single_line_let_else_max_width: 50
2+
3+
fn main() {
4+
let Some(a) = opt else {};
5+
6+
let Some(b) = opt else { return };
7+
8+
let Some(c) = opt else {
9+
return
10+
};
11+
12+
let Some(c) = opt else {
13+
// a comment should always force the block to be multi-lined
14+
return
15+
};
16+
17+
let Some(c) = opt else { /* a comment should always force the block to be multi-lined */ return };
18+
19+
let Some(d) = some_very_very_very_very_long_name else { return };
20+
21+
let Expr::Slice(ast::ExprSlice { lower, upper, step, range: _ }) = slice.as_ref() else {
22+
return
23+
};
24+
25+
let Some((base_place, current)) = self.lower_expr_as_place(current, *base, true)? else {
26+
return Ok(None)
27+
};
28+
29+
let Some(doc_attr) = variant.attrs.iter().find(|attr| attr.path().is_ident("doc")) else {
30+
return Err(Error::new(variant.span(), r#"expected a doc comment"#))
31+
};
32+
33+
let Some((base_place, current)) = self.lower_expr_as_place(current, *base, true) else {
34+
return Ok(None)
35+
};
36+
37+
let Stmt::Expr(Expr::Call(ExprCall { args: some_args, .. }), _) = last_stmt else {
38+
return Err(Error::new(last_stmt.span(), "expected last expression to be `Some(match (..) { .. })`"))
39+
};
40+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// rustfmt-single_line_let_else_max_width: 0
2+
3+
fn main() {
4+
let Some(a) = opt else {};
5+
6+
let Some(b) = opt else { return };
7+
8+
let Some(c) = opt else {
9+
return
10+
};
11+
12+
let Some(c) = opt else {
13+
// a comment should always force the block to be multi-lined
14+
return
15+
};
16+
17+
let Some(c) = opt else { /* a comment should always force the block to be multi-lined */ return };
18+
19+
let Some(d) = some_very_very_very_very_long_name else { return };
20+
21+
let Expr::Slice(ast::ExprSlice { lower, upper, step, range: _ }) = slice.as_ref() else {
22+
return
23+
};
24+
25+
let Some((base_place, current)) = self.lower_expr_as_place(current, *base, true)? else {
26+
return Ok(None)
27+
};
28+
29+
let Some(doc_attr) = variant.attrs.iter().find(|attr| attr.path().is_ident("doc")) else {
30+
return Err(Error::new(variant.span(), r#"expected a doc comment"#))
31+
};
32+
33+
let Some((base_place, current)) = self.lower_expr_as_place(current, *base, true) else {
34+
return Ok(None)
35+
};
36+
37+
let Stmt::Expr(Expr::Call(ExprCall { args: some_args, .. }), _) = last_stmt else {
38+
return Err(Error::new(last_stmt.span(), "expected last expression to be `Some(match (..) { .. })`"))
39+
};
40+
}

tests/source/configs/use_small_heuristics/default.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,13 @@ fn main() {
2323
sit
2424
};
2525
}
26+
27+
fn format_let_else() {
28+
let Some(a) = opt else {};
29+
30+
let Some(b) = opt else { return };
31+
32+
let Some(c) = opt else { return };
33+
34+
let Some(d) = some_very_very_very_very_long_name else { return };
35+
}

0 commit comments

Comments
 (0)