diff --git a/Configurations.md b/Configurations.md index d2e5613eba9..c480703bff8 100644 --- a/Configurations.md +++ b/Configurations.md @@ -1604,6 +1604,185 @@ fn foo() { } ``` +## `match_arm_wrapping` + +Controls when to block wrap match arm bodies. + +- **Default value**: `"Default"` +- **Possible values**: `"Default"`, `"FitFirstLine"`, `"FitEntireBody"`, `"Always"`, `"Preserve` +- **Stable**: No (tracking issue: #4896) + +### Example + +#### Original code + +```rust +#![rustfmt::skip] + +fn main() { + match lorem { + 1000 => foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo(x), + 2000 => { + println!("{}", sit) + } + 3000 => panic!(), + 4000 => { + () + } + 5000 => this.a_very_long_function_name(foo, bar, bazz, fizz, another_argument, some_more_arguments, which_dont_fit), + } +} +``` + +#### `"Default"` (default): + +The default block wrapping settings, as described in the Style Guide. + +```rust +fn main() { + match lorem { + 1000 => { + foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo(x) + } + 2000 => { + println!("{}", sit) + } + 3000 => panic!(), + 4000 => (), + 5000 => this.a_very_long_function_name( + foo, + bar, + bazz, + fizz, + another_argument, + some_more_arguments, + which_dont_fit, + ), + } +} +``` + +#### `"FitFirstLine"`: + +Same as the default, except don't block wrap match arms when the opening line of its body can't fit on the same line as the `=>`. + +```rust +fn main() { + match lorem { + 1000 => + foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo(x), + 2000 => { + println!("{}", sit) + } + 3000 => panic!(), + 4000 => (), + 5000 => this.a_very_long_function_name( + foo, + bar, + bazz, + fizz, + another_argument, + some_more_arguments, + which_dont_fit, + ), + } +} +``` + +#### `"Always"`: + +Always block wrap match arm bodies. + +```rust +fn main() { + match lorem { + 1000 => { + foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo(x) + } + 2000 => { + println!("{}", sit) + } + 3000 => { + panic!() + } + 4000 => { + () + } + 5000 => { + this.a_very_long_function_name( + foo, + bar, + bazz, + fizz, + another_argument, + some_more_arguments, + which_dont_fit, + ) + } + } +} +``` + +#### `"Preserve"`: + +Preserve block wrapping on match arm bodies if the developer originally had the body wrapped. + +```rust +fn main() { + match lorem { + 1000 => { + foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo(x) + } + 2000 => { + println!("{}", sit) + } + 3000 => panic!(), + 4000 => { + () + } + 5000 => this.a_very_long_function_name( + foo, + bar, + bazz, + fizz, + another_argument, + some_more_arguments, + which_dont_fit, + ), + } +} +``` + +#### `"FitEntireBody"`: + +Same as default, except block wrap the match arm if the entire body cannot fit on the same line as the `=>`. + +```rust +fn main() { + match lorem { + 1000 => { + foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo(x) + } + 2000 => { + println!("{}", sit) + } + 3000 => panic!(), + 4000 => (), + 5000 => { + this.a_very_long_function_name( + foo, + bar, + bazz, + fizz, + another_argument, + some_more_arguments, + which_dont_fit, + ) + } + } +} +``` + ## `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/config_type.rs b/src/config/config_type.rs index 7fc4486ddcd..08134322439 100644 --- a/src/config/config_type.rs +++ b/src/config/config_type.rs @@ -106,6 +106,7 @@ macro_rules! create_config { | "chain_width" => self.0.set_heuristics(), "license_template_path" => self.0.set_license_template(), "merge_imports" => self.0.set_merge_imports(), + "match_arm_blocks" => self.0.set_match_arm_blocks(), &_ => (), } } @@ -166,6 +167,7 @@ macro_rules! create_config { self.set_license_template(); self.set_ignore(dir); self.set_merge_imports(); + self.set_match_arm_blocks(); self } @@ -249,14 +251,16 @@ macro_rules! create_config { | "chain_width" => self.set_heuristics(), "license_template_path" => self.set_license_template(), "merge_imports" => self.set_merge_imports(), + "match_arm_blocks" => self.set_match_arm_blocks(), &_ => (), } } #[allow(unreachable_pub)] pub fn is_hidden_option(name: &str) -> bool { - const HIDE_OPTIONS: [&str; 5] = - ["verbose", "verbose_diff", "file_lines", "width_heuristics", "merge_imports"]; + const HIDE_OPTIONS: [&str; 6] = + ["verbose", "verbose_diff", "file_lines", "width_heuristics", "merge_imports", + "match_arm_blocks"]; HIDE_OPTIONS.contains(&name) } @@ -421,6 +425,22 @@ macro_rules! create_config { } } + fn set_match_arm_blocks(&mut self) { + if self.was_set().match_arm_blocks() { + eprintln!( + "Warning: the `match_arm_blocks` option is deprecated. \ + Use `match_arm_wrapping` instead" + ); + if !self.was_set().match_arm_wrapping() { + self.match_arm_wrapping.2 = if self.match_arm_blocks() { + MatchArmWrapping::Default + } else { + MatchArmWrapping::FitFirstLine + }; + } + } + } + #[allow(unreachable_pub)] /// Returns `true` if the config key was explicitly set and is the default value. pub fn is_default(&self, key: &str) -> bool { diff --git a/src/config/mod.rs b/src/config/mod.rs index 8c04363b1fd..96469bd6d56 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -110,8 +110,9 @@ create_config! { "Align struct fields if their diffs fits within threshold"; enum_discrim_align_threshold: usize, 0, false, "Align enum variants discrims, if their diffs fit within threshold"; - match_arm_blocks: bool, true, false, "Wrap the body of arms in blocks when it does not fit on \ - the same line with the pattern of arms"; + match_arm_wrapping: MatchArmWrapping, MatchArmWrapping::Default, false, + "Wrap the body of match arms according to the given options"; + match_arm_blocks: bool, true, false, "(deprecated: use match_arm_wrapping instead)"; match_arm_leading_pipes: MatchArmLeadingPipe, MatchArmLeadingPipe::Never, true, "Determines whether leading pipes are emitted on match arms"; force_multiline_blocks: bool, false, false, @@ -426,6 +427,11 @@ mod test { "Merge imports"; merge_imports: bool, false, false, "(deprecated: use imports_granularity instead)"; + // match_arm_blocks deprecation + match_arm_blocks: bool, true, false, "(deprecated: use match_arm_wrapping instead)"; + match_arm_wrapping: MatchArmWrapping, MatchArmWrapping::Default, false, + "Wrap the body of match arms according to the given options"; + // Width Heuristics use_small_heuristics: Heuristics, Heuristics::Default, true, "Whether to use different formatting for items and \ @@ -590,6 +596,7 @@ combine_control_expr = true overflow_delimited_expr = false struct_field_align_threshold = 0 enum_discrim_align_threshold = 0 +match_arm_wrapping = "Default" match_arm_blocks = true match_arm_leading_pipes = "Never" force_multiline_blocks = false @@ -723,6 +730,68 @@ make_backup = false } } + #[cfg(test)] + mod deprecated_option_match_arm_blocks { + use super::*; + + #[test] + fn test_old_option_set() { + if !crate::is_nightly_channel!() { + return; + } + // Old option defaults to true - set it to false + let toml = r#" + unstable_features = true + match_arm_blocks = false + "#; + let config = Config::from_toml(toml, Path::new("")).unwrap(); + assert_eq!(config.match_arm_wrapping(), MatchArmWrapping::FitFirstLine); + } + + #[test] + fn test_both_set() { + if !crate::is_nightly_channel!() { + return; + } + let toml = r#" + unstable_features = true + match_arm_blocks = false + match_arm_wrapping = "Default" + "#; + let config = Config::from_toml(toml, Path::new("")).unwrap(); + assert_eq!(config.match_arm_wrapping(), MatchArmWrapping::Default); + } + + #[test] + fn test_new_overridden() { + if !crate::is_nightly_channel!() { + return; + } + let toml = r#" + unstable_features = true + merge_imports = false + "#; + let mut config = Config::from_toml(toml, Path::new("")).unwrap(); + config.override_value("match_arm_wrapping", "Default"); + assert_eq!(config.match_arm_wrapping(), MatchArmWrapping::Default); + } + + #[test] + fn test_old_overridden() { + if !crate::is_nightly_channel!() { + return; + } + let toml = r#" + unstable_features = true + match_arm_wrapping = "FitFirstLine" + "#; + let mut config = Config::from_toml(toml, Path::new("")).unwrap(); + config.override_value("match_arm_blocks", "false"); + // no effect: the new option always takes precedence + assert_eq!(config.match_arm_wrapping(), MatchArmWrapping::FitFirstLine); + } + } + #[cfg(test)] mod use_small_heuristics { use super::*; diff --git a/src/config/options.rs b/src/config/options.rs index 3b91021813c..ca80834c19a 100644 --- a/src/config/options.rs +++ b/src/config/options.rs @@ -442,3 +442,20 @@ pub enum MatchArmLeadingPipe { /// Preserve any existing leading pipes Preserve, } + +/// Controls wrapping for match arm bodies +#[config_type] +pub enum MatchArmWrapping { + /// Follow the Style Guide Prescription + Default, + /// Same as Default, except don't block wrap match arms when the opening line of its body + /// can't fit on the same line as the `=>`. + FitFirstLine, + /// Always block wrap match arms + Always, + /// Preserve the block wrapping on match arms + Preserve, + /// Same as Default, except wrap the match arm if the entire body cannot fit on the same line + /// as the `=>`. + FitEntireBody, +} diff --git a/src/matches.rs b/src/matches.rs index 140ec226c02..5a994f353d1 100644 --- a/src/matches.rs +++ b/src/matches.rs @@ -7,7 +7,9 @@ use rustc_span::{BytePos, Span}; use crate::comment::{combine_strs_with_missing_comments, rewrite_comment}; use crate::config::lists::*; -use crate::config::{Config, ControlBraceStyle, IndentStyle, MatchArmLeadingPipe, Version}; +use crate::config::{ + Config, ControlBraceStyle, IndentStyle, MatchArmLeadingPipe, MatchArmWrapping, Version, +}; use crate::expr::{ format_expr, is_empty_block, is_simple_block, is_unsafe_block, prefer_next_line, rewrite_cond, ExprType, RhsTactics, @@ -324,6 +326,10 @@ fn flatten_arm_body<'a>( if let ast::ExprKind::Block(..) = expr.kind { flatten_arm_body(context, expr, None) } else { + if context.config.match_arm_wrapping() == MatchArmWrapping::Preserve { + return (false, body); + } + let cond_becomes_muti_line = opt_shape .and_then(|shape| rewrite_cond(context, expr, shape)) .map_or(false, |cond| cond.contains('\n')); @@ -413,26 +419,25 @@ fn rewrite_match_body( } let indent_str = shape.indent.to_string_with_newline(context.config); - let (body_prefix, body_suffix) = - if context.config.match_arm_blocks() && !context.inside_macro() { + let (body_prefix, body_suffix) = match context.config.match_arm_wrapping() { + MatchArmWrapping::FitFirstLine => ("", String::from(",")), + _ if !context.inside_macro() => { let comma = if context.config.match_block_trailing_comma() { "," } else { "" }; - let semicolon = if context.config.version() == Version::One { - "" - } else { - if semicolon_for_expr(context, body) { - ";" - } else { - "" - } + + let semicolon = match context.config.version() { + Version::One => "", + _ if semicolon_for_expr(context, body) => ";", + _ => "", }; + ("{", format!("{}{}}}{}", semicolon, indent_str, comma)) - } else { - ("", String::from(",")) - }; + } + _ => ("", String::from(",")), + }; let block_sep = match context.config.control_brace_style() { ControlBraceStyle::AlwaysNextLine => format!("{}{}", alt_block_sep, body_prefix), @@ -460,7 +465,10 @@ fn rewrite_match_body( let orig_body_shape = shape .offset_left(extra_offset(pats_str, shape) + 4) .and_then(|shape| shape.sub_width(comma.len())); - let orig_body = if forbid_same_line || !arrow_comment.is_empty() { + let orig_body = if forbid_same_line + || !arrow_comment.is_empty() + || (!is_block && context.config.match_arm_wrapping() == MatchArmWrapping::Always) + { None } else if let Some(body_shape) = orig_body_shape { let rewrite = nop_block_collapse( @@ -481,6 +489,7 @@ fn rewrite_match_body( } else { None }; + let orig_budget = orig_body_shape.map_or(0, |shape| shape.width); // Try putting body on the next line and see if it looks better. @@ -489,9 +498,12 @@ fn rewrite_match_body( format_expr(body, ExprType::Statement, context, next_line_body_shape), next_line_body_shape.width, ); + match (orig_body, next_line_body) { (Some(ref orig_str), Some(ref next_line_str)) - if prefer_next_line(orig_str, next_line_str, RhsTactics::Default) => + if prefer_next_line(orig_str, next_line_str, RhsTactics::Default) + || (context.config.match_arm_wrapping() == MatchArmWrapping::FitEntireBody + && orig_str.contains('\n')) => { combine_next_line_body(next_line_str) } diff --git a/tests/source/configs/match_arm_wrapping/always.rs b/tests/source/configs/match_arm_wrapping/always.rs new file mode 100644 index 00000000000..318e963164f --- /dev/null +++ b/tests/source/configs/match_arm_wrapping/always.rs @@ -0,0 +1,21 @@ +// rustfmt-match_arm_wrapping: Always +// Wrap match-arms + +fn main() { + match lorem { + 1000 => foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo(x), + 2000 => { + println!("{}", sit) + } + 3000 => panic!(), + 4000 => { + () + } + 5000 => this.a_very_long_function_name(foo, bar, bazz, fizz, another_argument, some_more_arguments, which_dont_fit), + ipsum => { + // Some comment + let dolor = ipsum % 2; + println!("{}", dolor); + } + } +} diff --git a/tests/source/configs/match_arm_wrapping/default.rs b/tests/source/configs/match_arm_wrapping/default.rs new file mode 100644 index 00000000000..d004cc75f0e --- /dev/null +++ b/tests/source/configs/match_arm_wrapping/default.rs @@ -0,0 +1,21 @@ +// rustfmt-match_arm_wrapping: Default +// Wrap match-arms + +fn main() { + match lorem { + 1000 => foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo(x), + 2000 => { + println!("{}", sit) + } + 3000 => panic!(), + 4000 => { + () + } + 5000 => this.a_very_long_function_name(foo, bar, bazz, fizz, another_argument, some_more_arguments, which_dont_fit), + ipsum => { + // Some comment + let dolor = ipsum % 2; + println!("{}", dolor); + } + } +} diff --git a/tests/source/configs/match_arm_wrapping/fit_entire_body.rs b/tests/source/configs/match_arm_wrapping/fit_entire_body.rs new file mode 100644 index 00000000000..91f8da4ae22 --- /dev/null +++ b/tests/source/configs/match_arm_wrapping/fit_entire_body.rs @@ -0,0 +1,21 @@ +// rustfmt-match_arm_wrapping: FitEntireBody +// Wrap match-arms + +fn main() { + match lorem { + 1000 => foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo(x), + 2000 => { + println!("{}", sit) + } + 3000 => panic!(), + 4000 => { + () + } + 5000 => this.a_very_long_function_name(foo, bar, bazz, fizz, another_argument, some_more_arguments, which_dont_fit), + ipsum => { + // Some comment + let dolor = ipsum % 2; + println!("{}", dolor); + } + } +} diff --git a/tests/source/configs/match_arm_wrapping/fit_first_line.rs b/tests/source/configs/match_arm_wrapping/fit_first_line.rs new file mode 100644 index 00000000000..5142f73bdd8 --- /dev/null +++ b/tests/source/configs/match_arm_wrapping/fit_first_line.rs @@ -0,0 +1,21 @@ +// rustfmt-match_arm_wrapping: FitFirstLine +// Wrap match-arms + +fn main() { + match lorem { + 1000 => foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo(x), + 2000 => { + println!("{}", sit) + } + 3000 => panic!(), + 4000 => { + () + } + 5000 => this.a_very_long_function_name(foo, bar, bazz, fizz, another_argument, some_more_arguments, which_dont_fit), + ipsum => { + // Some comment + let dolor = ipsum % 2; + println!("{}", dolor); + } + } +} diff --git a/tests/source/configs/match_arm_wrapping/preserve.rs b/tests/source/configs/match_arm_wrapping/preserve.rs new file mode 100644 index 00000000000..642272c1619 --- /dev/null +++ b/tests/source/configs/match_arm_wrapping/preserve.rs @@ -0,0 +1,21 @@ +// rustfmt-match_arm_wrapping: Preserve +// Wrap match-arms + +fn main() { + match lorem { + 1000 => foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo(x), + 2000 => { + println!("{}", sit) + } + 3000 => panic!(), + 4000 => { + () + } + 5000 => this.a_very_long_function_name(foo, bar, bazz, fizz, another_argument, some_more_arguments, which_dont_fit), + ipsum => { + // Some comment + let dolor = ipsum % 2; + println!("{}", dolor); + } + } +} diff --git a/tests/target/configs/match_arm_wrapping/always.rs b/tests/target/configs/match_arm_wrapping/always.rs new file mode 100644 index 00000000000..a7c56796f0f --- /dev/null +++ b/tests/target/configs/match_arm_wrapping/always.rs @@ -0,0 +1,35 @@ +// rustfmt-match_arm_wrapping: Always +// Wrap match-arms + +fn main() { + match lorem { + 1000 => { + foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo(x) + } + 2000 => { + println!("{}", sit) + } + 3000 => { + panic!() + } + 4000 => { + () + } + 5000 => { + this.a_very_long_function_name( + foo, + bar, + bazz, + fizz, + another_argument, + some_more_arguments, + which_dont_fit, + ) + } + ipsum => { + // Some comment + let dolor = ipsum % 2; + println!("{}", dolor); + } + } +} diff --git a/tests/target/configs/match_arm_wrapping/default.rs b/tests/target/configs/match_arm_wrapping/default.rs new file mode 100644 index 00000000000..7867da97f06 --- /dev/null +++ b/tests/target/configs/match_arm_wrapping/default.rs @@ -0,0 +1,29 @@ +// rustfmt-match_arm_wrapping: Default +// Wrap match-arms + +fn main() { + match lorem { + 1000 => { + foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo(x) + } + 2000 => { + println!("{}", sit) + } + 3000 => panic!(), + 4000 => (), + 5000 => this.a_very_long_function_name( + foo, + bar, + bazz, + fizz, + another_argument, + some_more_arguments, + which_dont_fit, + ), + ipsum => { + // Some comment + let dolor = ipsum % 2; + println!("{}", dolor); + } + } +} diff --git a/tests/target/configs/match_arm_wrapping/fit_entire_body.rs b/tests/target/configs/match_arm_wrapping/fit_entire_body.rs new file mode 100644 index 00000000000..e9ce3944887 --- /dev/null +++ b/tests/target/configs/match_arm_wrapping/fit_entire_body.rs @@ -0,0 +1,31 @@ +// rustfmt-match_arm_wrapping: FitEntireBody +// Wrap match-arms + +fn main() { + match lorem { + 1000 => { + foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo(x) + } + 2000 => { + println!("{}", sit) + } + 3000 => panic!(), + 4000 => (), + 5000 => { + this.a_very_long_function_name( + foo, + bar, + bazz, + fizz, + another_argument, + some_more_arguments, + which_dont_fit, + ) + } + ipsum => { + // Some comment + let dolor = ipsum % 2; + println!("{}", dolor); + } + } +} diff --git a/tests/target/configs/match_arm_wrapping/fit_first_line.rs b/tests/target/configs/match_arm_wrapping/fit_first_line.rs new file mode 100644 index 00000000000..ceebc7371da --- /dev/null +++ b/tests/target/configs/match_arm_wrapping/fit_first_line.rs @@ -0,0 +1,28 @@ +// rustfmt-match_arm_wrapping: FitFirstLine +// Wrap match-arms + +fn main() { + match lorem { + 1000 => + foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo(x), + 2000 => { + println!("{}", sit) + } + 3000 => panic!(), + 4000 => (), + 5000 => this.a_very_long_function_name( + foo, + bar, + bazz, + fizz, + another_argument, + some_more_arguments, + which_dont_fit, + ), + ipsum => { + // Some comment + let dolor = ipsum % 2; + println!("{}", dolor); + } + } +} diff --git a/tests/target/configs/match_arm_wrapping/preserve.rs b/tests/target/configs/match_arm_wrapping/preserve.rs new file mode 100644 index 00000000000..9a5199df2cf --- /dev/null +++ b/tests/target/configs/match_arm_wrapping/preserve.rs @@ -0,0 +1,31 @@ +// rustfmt-match_arm_wrapping: Preserve +// Wrap match-arms + +fn main() { + match lorem { + 1000 => { + foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo(x) + } + 2000 => { + println!("{}", sit) + } + 3000 => panic!(), + 4000 => { + () + } + 5000 => this.a_very_long_function_name( + foo, + bar, + bazz, + fizz, + another_argument, + some_more_arguments, + which_dont_fit, + ), + ipsum => { + // Some comment + let dolor = ipsum % 2; + println!("{}", dolor); + } + } +}