diff --git a/Configurations.md b/Configurations.md index 67debec1ddb..58d7aac52ef 100644 --- a/Configurations.md +++ b/Configurations.md @@ -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) diff --git a/src/config/mod.rs b/src/config/mod.rs index e381165581e..b03674b6b3c 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -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, @@ -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" @@ -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" diff --git a/src/config/options.rs b/src/config/options.rs index fea8e80434e..00f9c3f7ec1 100644 --- a/src/config/options.rs +++ b/src/config/options.rs @@ -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; diff --git a/src/matches.rs b/src/matches.rs index 2955f5d119a..1727a9de868 100644 --- a/src/matches.rs +++ b/src/matches.rs @@ -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()) @@ -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) diff --git a/tests/source/configs/match_arm_indent/attrs.rs b/tests/source/configs/match_arm_indent/attrs.rs new file mode 100644 index 00000000000..0616a612cd0 --- /dev/null +++ b/tests/source/configs/match_arm_indent/attrs.rs @@ -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";} + } +} diff --git a/tests/source/configs/match_arm_indent/guards.rs b/tests/source/configs/match_arm_indent/guards.rs new file mode 100644 index 00000000000..ba4df30d6ed --- /dev/null +++ b/tests/source/configs/match_arm_indent/guards.rs @@ -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, + } +} diff --git a/tests/source/configs/match_arm_indent/leading_pipes.rs b/tests/source/configs/match_arm_indent/leading_pipes.rs new file mode 100644 index 00000000000..d090b8f7bf1 --- /dev/null +++ b/tests/source/configs/match_arm_indent/leading_pipes.rs @@ -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(), + } +} diff --git a/tests/source/configs/match_arm_indent/nested.rs b/tests/source/configs/match_arm_indent/nested.rs new file mode 100644 index 00000000000..89e1f635799 --- /dev/null +++ b/tests/source/configs/match_arm_indent/nested.rs @@ -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" + } + } + } + } + } +} diff --git a/tests/source/configs/match_arm_indent/patterns.rs b/tests/source/configs/match_arm_indent/patterns.rs new file mode 100644 index 00000000000..3950de48348 --- /dev/null +++ b/tests/source/configs/match_arm_indent/patterns.rs @@ -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, + } +} diff --git a/tests/source/configs/match_arm_indent/unindent.rs b/tests/source/configs/match_arm_indent/unindent.rs new file mode 100644 index 00000000000..9177a381ab3 --- /dev/null +++ b/tests/source/configs/match_arm_indent/unindent.rs @@ -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" + } + } +} diff --git a/tests/target/configs/match_arm_indent/attrs.rs b/tests/target/configs/match_arm_indent/attrs.rs new file mode 100644 index 00000000000..1063294203a --- /dev/null +++ b/tests/target/configs/match_arm_indent/attrs.rs @@ -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"; + } + } +} diff --git a/tests/target/configs/match_arm_indent/guards.rs b/tests/target/configs/match_arm_indent/guards.rs new file mode 100644 index 00000000000..b218c1a9ce2 --- /dev/null +++ b/tests/target/configs/match_arm_indent/guards.rs @@ -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, + } +} diff --git a/tests/target/configs/match_arm_indent/leading_pipes.rs b/tests/target/configs/match_arm_indent/leading_pipes.rs new file mode 100644 index 00000000000..84b94d81485 --- /dev/null +++ b/tests/target/configs/match_arm_indent/leading_pipes.rs @@ -0,0 +1,35 @@ +// 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(), + } +} diff --git a/tests/target/configs/match_arm_indent/nested.rs b/tests/target/configs/match_arm_indent/nested.rs new file mode 100644 index 00000000000..bd08aacff7a --- /dev/null +++ b/tests/target/configs/match_arm_indent/nested.rs @@ -0,0 +1,79 @@ +// 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", + }, + }, + }, + }, + } +} diff --git a/tests/target/configs/match_arm_indent/noindent-tabs.rs b/tests/target/configs/match_arm_indent/noindent-tabs.rs new file mode 100644 index 00000000000..a4b6dc2c87e --- /dev/null +++ b/tests/target/configs/match_arm_indent/noindent-tabs.rs @@ -0,0 +1,23 @@ +// rustfmt-match_arm_indent: false +// rustfmt-hard_tabs: true +// rustfmt-tab_spaces: 8 + +// Large-indentation style, brought to you by the Linux kernel +fn foo() { + match value { + 0 => { + "one"; + "two"; + } + 1 | 2 | 3 => { + "line1"; + "line2"; + } + 100..1000 => oneline(), + + _ => { + // catch-all + todo!(); + } + } +} diff --git a/tests/target/configs/match_arm_indent/noindent.rs b/tests/target/configs/match_arm_indent/noindent.rs new file mode 100644 index 00000000000..02db0622ed3 --- /dev/null +++ b/tests/target/configs/match_arm_indent/noindent.rs @@ -0,0 +1,21 @@ +// rustfmt-match_arm_indent: false +// Don't indent the match arms + +fn foo() { + match value { + 0 => { + "one"; + "two"; + } + 1 | 2 | 3 => { + "line1"; + "line2"; + } + 100..1000 => oneline(), + + _ => { + // catch-all + todo!(); + } + } +} diff --git a/tests/target/configs/match_arm_indent/patterns.rs b/tests/target/configs/match_arm_indent/patterns.rs new file mode 100644 index 00000000000..c7cea167878 --- /dev/null +++ b/tests/target/configs/match_arm_indent/patterns.rs @@ -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, + } +} diff --git a/tests/target/configs/match_arm_indent/unindent.rs b/tests/target/configs/match_arm_indent/unindent.rs new file mode 100644 index 00000000000..c74518b5e24 --- /dev/null +++ b/tests/target/configs/match_arm_indent/unindent.rs @@ -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" + } + } +}