Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions Configurations.md
Original file line number Diff line number Diff line change
Expand Up @@ -1731,6 +1731,58 @@ use core::slice;
#[cfg(feature = "alloc")] use core::slice;
```

## `let_else_force_newline`

Force `else` branch of a `let_else` expression to always come after a newline.

- **Default value**: `false`
- **Possible values**: `true`, `false`
- **Stable**: No (Tracking issue: [TODO](todo))

#### `false` (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;
};
}
```

#### `true`:

```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;
};
}
```

## `match_arm_blocks`

Controls whether arm bodies are wrapped in cases where the first line of the body cannot fit on the same line as the `=>` operator.
Expand Down
4 changes: 4 additions & 0 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ create_config! {
format_generated_files: FormatGeneratedFiles, false, "Format generated files";
generated_marker_line_search_limit: GeneratedMarkerLineSearchLimit, false, "Number of lines to \
check for a `@generated` marker when `format_generated_files` is enabled";
let_else_force_newline: LetElseForceNewline, true, "Always put the else branch of let_else \
statements on a new line.";

// Options that can change the source code beyond whitespace/blocks (somewhat linty things)
merge_derives: MergeDerives, true, "Merge multiple `#[derive(...)]` into a single one";
Expand Down Expand Up @@ -820,6 +822,7 @@ version = "One"
inline_attribute_width = 0
format_generated_files = true
generated_marker_line_search_limit = 5
let_else_force_newline = false
merge_derives = true
use_try_shorthand = false
use_field_init_shorthand = false
Expand Down Expand Up @@ -912,6 +915,7 @@ version = "Two"
inline_attribute_width = 0
format_generated_files = true
generated_marker_line_search_limit = 5
let_else_force_newline = false
merge_derives = true
use_try_shorthand = false
use_field_init_shorthand = false
Expand Down
1 change: 1 addition & 0 deletions src/config/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,7 @@ config_option_with_style_edition_default!(
InlineAttributeWidth, usize, _ => 0;
FormatGeneratedFiles, bool, _ => true;
GeneratedMarkerLineSearchLimit, usize, _ => 5;
LetElseForceNewline, bool, _ => false;

// Options that can change the source code beyond whitespace/blocks (somewhat linty things)
MergeDerives, bool, _ => true;
Expand Down
3 changes: 2 additions & 1 deletion src/items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,8 @@ impl Rewrite for ast::Local {
result.as_str()
};
let force_newline_else = pat_str.contains('\n')
|| !same_line_else_kw_and_brace(init_str, context, else_kw_span, nested_shape);
|| !same_line_else_kw_and_brace(init_str, context, else_kw_span, nested_shape)
|| context.config.let_else_force_newline();
let else_kw = rewrite_else_kw_with_comments(
force_newline_else,
true,
Expand Down
182 changes: 182 additions & 0 deletions tests/source/let_else_force_newline.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
// rustfmt-let_else_force_newline: true

fn main() {
// Although this won't compile it still parses so make sure we can format empty else blocks
let Some(x) = opt else {};

// let-else may be formatted on a single line if they are "short"
// and only contain a single expression
let Some(x) = opt else { return };

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

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

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

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

let Some(x) = y.foo("abc", fairly_long_identifier, "def", "123456", "string", "cheese") else { bar() };

let Some(x) = abcdef().foo("abc", some_really_really_really_long_ident, "ident", "123456").bar().baz().qux("fffffffffffffffff") else { foo_bar() };
}

fn with_comments_around_else_keyword() {
let Some(x) = opt /* pre else keyword block-comment */ else { return };

let Some(x) = opt else /* post else keyword block-comment */ { return };

let Some(x) = opt /* pre else keyword block-comment */ else /* post else keyword block-comment */ { return };

let Some(x) = opt // pre else keyword line-comment
else { return };

let Some(x) = opt else
// post else keyword line-comment
{ return };

let Some(x) = opt // pre else keyword line-comment
else
// post else keyword line-comment
{ return };

}

fn unbreakable_initializer_expr_pre_formatting_let_else_length_near_max_width() {
// Pre Formatting:
// The length of `(indent)let pat = init else block;` is 100 (max_width)
// Post Formatting:
// The formatting is left unchanged!
let Some(x) = some_really_really_really_really_really_really_really_long_name_A else { return };

// Pre Formatting:
// The length of `(indent)let pat = init else block;` is 100 (max_width)
// Post Formatting:
// The else keyword and opening brace remain on the same line as the initializer expr,
// and the else block is formatted over multiple lines because we can't fit the
// else block on the same line as the initializer expr.
let Some(x) = some_really_really_really_really_really_really_really_long_name___B else {return};

// Pre Formatting:
// The length of `(indent)let pat = init else block;` is 100 (max_width)
// Post Formatting:
// The else keyword and opening brace remain on the same line as the initializer expr,
// and the else block is formatted over multiple lines because we can't fit the
// else block on the same line as the initializer expr.
let Some(x) = some_really_really_really_really_long_name_____C else {some_divergent_function()};

// Pre Formatting:
// The length of `(indent)let pat = init else block;` is 101 (> max_width)
// Post Formatting:
// The else keyword and opening brace remain on the same line as the initializer expr,
// and the else block is formatted over multiple lines because we can't fit the
// else block on the same line as the initializer expr.
let Some(x) = some_really_really_really_really_really_really_really_long_name__D else { return };
}

fn unbreakable_initializer_expr_pre_formatting_length_up_to_opening_brace_near_max_width() {
// Pre Formatting:
// The length of `(indent)let pat = init else {` is 99 (< max_width)
// Post Formatting:
// The else keyword and opening brace remain on the same line as the initializer expr,
// and the else block is formatted over multiple lines because we can't fit the
// else block on the same line as the initializer expr.
let Some(x) = some_really_really_really_really_really_really_really_really_long_name___E else {return};

// Pre Formatting:
// The length of `(indent)let pat = init else {` is 101 (> max_width)
// Post Formatting:
// The else keyword and opening brace cannot fit on the same line as the initializer expr.
// They are formatted on the next line.
let Some(x) = some_really_really_really_really_really_really_really_really_long_name_____F else {return};
}

fn unbreakable_initializer_expr_pre_formatting_length_through_initializer_expr_near_max_width() {
// Pre Formatting:
// The length of `(indent)let pat = init` is 99 (< max_width)
// Post Formatting:
// The else keyword and opening brace cannot fit on the same line as the initializer expr.
// They are formatted on the next line.
let Some(x) = some_really_really_really_really_really_really_really_really_really_long_name___G else {return};

// Pre Formatting:
// The length of `(indent)let pat = init` is 100 (max_width)
// Post Formatting:
// Break after the `=` and put the initializer expr on it's own line.
// 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};

// Pre Formatting:
// The length of `(indent)let pat = init` is 109 (> max_width)
// Post Formatting:
// Break after the `=` and put the initializer expr on it's own line.
// Because the initializer expr is multi-lined the else is placed on it's own line.
// The initializer expr has a length of 91, which when indented on the next line
// The `(indent)init` line has a length of 99. This is the max length that the `init` can be
// before we start running into max_width issues. I suspect this is because the shape is
// accounting for the `;` at the end of the `let-else` statement.
let Some(x) = some_really_really_really_really_really_really_really_really_really_really_long_name______I else {return};

// Pre Formatting:
// The length of `(indent)let pat = init` is 110 (> max_width)
// Post Formatting:
// Max length issues prevent us from formatting.
// The initializer expr has a length of 92, which if it would be indented on the next line
// the `(indent)init` line has a length of 100 which == max_width of 100.
// One might expect formatting to succeed, but I suspect the reason we hit max_width issues is
// because the Shape is accounting for the `;` at the end of the `let-else` statement.
let Some(x) = some_really_really_really_really_really_really_really_really_really_really_really_long_nameJ else {return};
}

fn long_patterns() {
let Foo {x: Bar(..), y: FooBar(..), z: Baz(..)} = opt else {
return;
};

// with version=One we don't wrap long array patterns
let [aaaaaaaaaaaaaaaa, bbbbbbbbbbbbbbb, cccccccccccccccccc, dddddddddddddddddd] = opt else {
return;
};

let ("aaaaaaaaaaaaaaaaaaa" | "bbbbbbbbbbbbbbbbb" | "cccccccccccccccccccccccc" | "dddddddddddddddd" | "eeeeeeeeeeeeeeee") = opt else {
return;
};

let Some(Ok((Message::ChangeColor(super::color::Color::Rgb(r, g, b)), Point { x, y, z }))) = opt else {
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 };
}

fn issue5901() {
#[cfg(target_os = "linux")]
let Some(x) = foo else { todo!() };

#[cfg(target_os = "linux")]
// Some comments between attributes and let-else statement
let Some(x) = foo else { todo!() };

#[cfg(target_os = "linux")]
#[cfg(target_arch = "x86_64")]
let Some(x) = foo else { todo!() };

// The else block will be multi-lined because attributes and comments before `let`
// are included when calculating max width
#[cfg(target_os = "linux")]
#[cfg(target_arch = "x86_64")]
// Some comments between attributes and let-else statement
let Some(x) = foo() else { todo!() };
}
56 changes: 56 additions & 0 deletions tests/source/let_else_force_newline_v2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// rustfmt-style_edition: 2024
// rustfmt-let_else_force_newline: true

fn issue5901() {
#[cfg(target_os = "linux")]
let Some(x) = foo else { todo!() };

#[cfg(target_os = "linux")]
// Some comments between attributes and let-else statement
let Some(x) = foo else { todo!() };

#[cfg(target_os = "linux")]
#[cfg(target_arch = "x86_64")]
let Some(x) = foo else { todo!() };

// The else block is multi-lined
#[cfg(target_os = "linux")]
let Some(x) = foo else { return; };

// The else block will be single-lined because attributes and comments before `let`
// are no longer included when calculating max width
#[cfg(target_os = "linux")]
#[cfg(target_arch = "x86_64")]
// Some comments between attributes and let-else statement
let Some(x) = foo else { todo!() };

// Some more test cases for v2 formatting with attributes

#[cfg(target_os = "linux")]
#[cfg(target_arch = "x86_64")]
let Some(x) = opt
// pre else keyword line-comment
else { return; };

#[cfg(target_os = "linux")]
#[cfg(target_arch = "x86_64")]
let Some(x) = opt else
// post else keyword line-comment
{ return; };

#[cfg(target_os = "linux")]
#[cfg(target_arch = "x86_64")]
let Foo {x: Bar(..), y: FooBar(..), z: Baz(..)} = opt else {
return;
};

#[cfg(target_os = "linux")]
#[cfg(target_arch = "x86_64")]
let Some(Ok((Message::ChangeColor(super::color::Color::Rgb(r, g, b)), Point { x, y, z }))) = opt else {
return;
};

#[cfg(target_os = "linux")]
#[cfg(target_arch = "x86_64")]
let Some(x) = very_very_very_very_very_very_very_very_very_very_very_very_long_expression_in_assign_rhs() else { return; };
}
Loading
Loading