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

Add match_arm_indent option #6525

Merged
merged 1 commit into from
Apr 3, 2025
Merged
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
36 changes: 36 additions & 0 deletions Configurations.md
Original file line number Diff line number Diff line change
Expand Up @@ -1862,6 +1862,42 @@ fn foo() {
}
```

## `match_arm_indent`

Controls whether match arms are indented. If disabled, match arms will be formatted at the same indentation level as the outer `match` statement. Meaning that match blocks will only be indented once, not twice.

- **Default value**: `true`
- **Possible values**: `true`, `false`
- **Stable**: No (tracking issue: [#6533](https://github.com/rust-lang/rustfmt/issues/6533))

#### `true` (default):

```rust
fn main() {
match value {
Enum::A => {
let mut work = first();
work += second();
}
Enum::B => short_work(),
}
}
```

#### `false`:

```rust
fn main() {
match value {
Enum::A => {
let mut work = first();
work += second();
}
Enum::B => short_work(),
}
}
```

## `match_block_trailing_comma`

Put a trailing comma after a block based match arm (non-block arms are not affected)
Expand Down
4 changes: 4 additions & 0 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ create_config! {
on the same line with the pattern of arms";
match_arm_leading_pipes: MatchArmLeadingPipeConfig, true,
"Determines whether leading pipes are emitted on match arms";
match_arm_indent: MatchArmIndent, false,
"Determines whether match arms are indented";
force_multiline_blocks: ForceMultilineBlocks, false,
"Force multiline closure bodies and match arms to be wrapped in a block";
fn_args_layout: FnArgsLayout, true,
Expand Down Expand Up @@ -803,6 +805,7 @@ struct_field_align_threshold = 0
enum_discrim_align_threshold = 0
match_arm_blocks = true
match_arm_leading_pipes = "Never"
match_arm_indent = true
force_multiline_blocks = false
fn_params_layout = "Tall"
brace_style = "SameLineWhere"
Expand Down Expand Up @@ -894,6 +897,7 @@ struct_field_align_threshold = 0
enum_discrim_align_threshold = 0
match_arm_blocks = true
match_arm_leading_pipes = "Never"
match_arm_indent = true
force_multiline_blocks = false
fn_params_layout = "Tall"
brace_style = "SameLineWhere"
Expand Down
1 change: 1 addition & 0 deletions src/config/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,7 @@ config_option_with_style_edition_default!(
EnumDiscrimAlignThreshold, usize, _ => 0;
MatchArmBlocks, bool, _ => true;
MatchArmLeadingPipeConfig, MatchArmLeadingPipe, _ => MatchArmLeadingPipe::Never;
MatchArmIndent, bool, _ => true;
ForceMultilineBlocks, bool, _ => false;
FnArgsLayout, Density, _ => Density::Tall;
FnParamsLayout, Density, _ => Density::Tall;
Expand Down
24 changes: 16 additions & 8 deletions src/matches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,16 +100,21 @@ pub(crate) fn rewrite_match(
_ => " ",
};

let nested_indent_str = shape
.indent
.block_indent(context.config)
.to_string(context.config);
let nested_indent = if context.config.match_arm_indent() {
shape.indent.block_indent(context.config)
} else {
shape.indent
};
let nested_indent_str = nested_indent.to_string(context.config);

// Inner attributes.
let inner_attrs = &inner_attributes(attrs);
let inner_attrs_str = if inner_attrs.is_empty() {
String::new()
} else {
let shape = if context.config.style_edition() <= StyleEdition::Edition2021 {
let shape = if context.config.style_edition() <= StyleEdition::Edition2021
|| !context.config.match_arm_indent()
{
shape
} else {
shape.block_indent(context.config.tab_spaces())
Expand Down Expand Up @@ -204,9 +209,12 @@ fn rewrite_match_arms(
span: Span,
open_brace_pos: BytePos,
) -> RewriteResult {
let arm_shape = shape
.block_indent(context.config.tab_spaces())
.with_max_width(context.config);
let arm_shape = if context.config.match_arm_indent() {
shape.block_indent(context.config.tab_spaces())
} else {
shape
}
.with_max_width(context.config);

let arm_len = arms.len();
let is_last_iter = repeat(false)
Expand Down
24 changes: 24 additions & 0 deletions tests/source/configs/match_arm_indent/attrs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// rustfmt-match_arm_indent: false

fn single_oneline() {
match value { #[cfg(sslv2)] Sslv2 => handle(), }
}

fn single_multiline() {
match value {
Sslv3 => handle(),
#[cfg(sslv2)] Sslv2 => { handle1(); handle2();}
#[cfg(TLSv1)] Tlsv1 if condition || something_else || and_a_third_thing || long_condition || basically => {}
#[cfg(sslv23)] Sslv23 if condition || something_else || and_a_third_thing || long_condition || basically => {actuall_content(); "ret";}
}
}

fn multiple() {
match value {
Sslv3 => handle(),
#[attr] #[cfg(sslv2)] Sslv2 => { handle1(); handle2();}
#[attr] #[cfg(TLSv1)] Tlsv1 if condition || something_else || and_a_third_thing || long_condition || basically => {}

#[attr] #[cfg(sslv23)] Sslv23 if condition || something_else || and_a_third_thing || long_condition || basically => {actuall_content(); "ret";}
}
}
32 changes: 32 additions & 0 deletions tests/source/configs/match_arm_indent/guards.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// rustfmt-match_arm_indent: false

// Guards are indented if the pattern is longer than 6 characters
fn test() {
match value {
LongOption
if condition || something_else || and_a_third_thing || long_condition || basically =>
{
do_stuff();
other_stuff();
}

A23456 if loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong => {
"1";
"2";
}
}
}

fn complicated() {
match rewrite {
// reflows
Ok(ref body_str)
if is_block
|| (!body_str.contains('\n')
&& unicode_str_width(body_str) <= body_shape.width) =>
{
return combine_orig_body(body_str);
}
_ => rewrite,
}
}
18 changes: 18 additions & 0 deletions tests/source/configs/match_arm_indent/leading_pipes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// rustfmt-match_arm_indent: false
// rustfmt-match_arm_leading_pipes: Always

fn pipes() {
match value {
SingleOption => {1;2;}

Multiple | Options | Together => {1;2;}

Multiple | Options | But | Long | And | This | Time | With | A | Guard if condition => {1; 2;}

Multiple | Options | But | Long | And | This | Time | With | A | Guard if condition || and || single_expr => 1,

a@Enum::Variant1 | a@Enum::Variant2 => {1;2;}

#[attr] JustInCase => r#final(),
}
}
65 changes: 65 additions & 0 deletions tests/source/configs/match_arm_indent/nested.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// rustfmt-match_arm_indent: false

fn r#match() {
match value {
Arm::Prev => f(),
// inline match
ModeratelyLongOption(n) => match n {
A => f(),
B => {
1;
2;
3;
}
AnotherLongerOption => {
1;
2;
}
_ if there || is || a || guard => {
nothing_changes();
}
},
Arm::Next => {
1;
2;
3;
}
}
}

// things which break up the nested match arm
fn r#break() {
match value {
Arm::Prev => f(),
// inline match
ModeratelyLongOption(n) => #[attr] match n {
A => f(),
B => c(),
C => 1,
},
Arm::Next => n(),
Two | Patterns => /* inline comment */ match val {
C => 3,
D => func(),
}
Arm::Last => l(),
}
}

fn parens() {
let result = Some(Other(match value { Option1 => 1, Option2 => {stuff(); 2}}));
}

fn silly() {
match value {
Inner(i1) => match i1 {
Inner(i2) => match i2 {
Inner(i3) => match i3 {
Inner(i4) => match i4 {
Inner => "it's a readability tradeoff, really"
}
}
}
}
}
}
28 changes: 28 additions & 0 deletions tests/source/configs/match_arm_indent/patterns.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// rustfmt-match_arm_indent: false

fn multiple() {
match body.kind {
// We do not allow `if` to stay on the same line, since we could easily mistake
// `pat => if cond { ... }` and `pat if cond => { ... }`.
ast::ExprKind::If(..) => false,
// We do not allow collapsing a block around expression with condition
// to avoid it being cluttered with match arm.
ast::ExprKind::ForLoop { .. } | ast::ExprKind::While(..) => false,
ast::ExprKind::Loop(..)
| ast::ExprKind::Match(..)
| ast::ExprKind::Block(..)
| ast::ExprKind::Closure(..)
| ast::ExprKind::Array(..)
| ast::ExprKind::Call(..)
| ast::ExprKind::MethodCall(..)
| ast::ExprKind::MacCall(..)
| ast::ExprKind::Struct(..)
| ast::ExprKind::Tup(..) => true,
ast::ExprKind::AddrOf(_, _, ref expr)
| ast::ExprKind::Try(ref expr)
| ast::ExprKind::Unary(_, ref expr)
| ast::ExprKind::Index(ref expr, _, _)
| ast::ExprKind::Cast(ref expr, _) => can_flatten_block_around_this(expr),
_ => false,
}
}
19 changes: 19 additions & 0 deletions tests/source/configs/match_arm_indent/unindent.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// rustfmt-match_arm_indent: false
// Unindent the match arms

fn foo() {
match x {
a => {
"line1";
"line2"
}
ThisIsA::Guard if true => {
"line1";
"line2"
}
ThisIsA::ReallyLongPattern(ThatWillForce::TheGuard, ToWrapOnto::TheFollowingLine) if true => {
"line1";
"line2"
}
}
}
48 changes: 48 additions & 0 deletions tests/target/configs/match_arm_indent/attrs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// rustfmt-match_arm_indent: false

fn single_oneline() {
match value {
#[cfg(sslv2)]
Sslv2 => handle(),
}
}

fn single_multiline() {
match value {
Sslv3 => handle(),
#[cfg(sslv2)]
Sslv2 => {
handle1();
handle2();
}
#[cfg(TLSv1)]
Tlsv1 if condition || something_else || and_a_third_thing || long_condition || basically => {}
#[cfg(sslv23)]
Sslv23 if condition || something_else || and_a_third_thing || long_condition || basically => {
actuall_content();
"ret";
}
}
}

fn multiple() {
match value {
Sslv3 => handle(),
#[attr]
#[cfg(sslv2)]
Sslv2 => {
handle1();
handle2();
}
#[attr]
#[cfg(TLSv1)]
Tlsv1 if condition || something_else || and_a_third_thing || long_condition || basically => {}

#[attr]
#[cfg(sslv23)]
Sslv23 if condition || something_else || and_a_third_thing || long_condition || basically => {
actuall_content();
"ret";
}
}
}
31 changes: 31 additions & 0 deletions tests/target/configs/match_arm_indent/guards.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// rustfmt-match_arm_indent: false

// Guards are indented if the pattern is longer than 6 characters
fn test() {
match value {
LongOption
if condition || something_else || and_a_third_thing || long_condition || basically =>
{
do_stuff();
other_stuff();
}

A23456 if loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong => {
"1";
"2";
}
}
}

fn complicated() {
match rewrite {
// reflows
Ok(ref body_str)
if is_block
|| (!body_str.contains('\n') && unicode_str_width(body_str) <= body_shape.width) =>
{
return combine_orig_body(body_str);
}
_ => rewrite,
}
}
Loading
Loading