diff --git a/Configurations.md b/Configurations.md index 024ff46ffc3..1afb7413248 100644 --- a/Configurations.md +++ b/Configurations.md @@ -263,13 +263,41 @@ fn lorem() -> T ## `use_small_heuristics` -Whether to use different formatting for items and expressions if they satisfy a heuristic notion of 'small'. +Specifies how long to make lines of code, in an abstract way based on a heuristic. -- **Default value**: `true` -- **Possible values**: `true`, `false` +- **Default value**: `medium` +- **Possible values**: `minimum`, `medium`, `maximum` - **Stable**: No -#### `true` (default): +#### `minimum`: + +```rust +enum Lorem { + Ipsum, + Dolor(bool), + Sit { + amet: Consectetur, + adipiscing: Elit, + }, +} + +fn main() { + lorem("lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing"); + + let lorem = Lorem { + ipsum: dolor, + sit: amet, + }; + + let lorem = if ipsum { + dolor + } else { + sit + }; +} +``` + +#### `medium` (default): ```rust enum Lorem { @@ -299,31 +327,22 @@ fn main() { } ``` -#### `false`: +#### `maximum` ```rust enum Lorem { Ipsum, Dolor(bool), - Sit { - amet: Consectetur, - adipiscing: Elit, - }, + Sit { amet: Consectetur, adipiscing: Elit }, } fn main() { lorem("lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing"); - let lorem = Lorem { - ipsum: dolor, - sit: amet, - }; + let lorem = Lorem { ipsum: dolor, sit: amet }; + let lorem = Lorem { ipsum: dolor }; - let lorem = if ipsum { - dolor - } else { - sit - }; + let lorem = if ipsum { dolor } else { sit }; } ``` diff --git a/rustfmt-config/src/config_type.rs b/rustfmt-config/src/config_type.rs index 2bc9ceace87..ceab565afb5 100644 --- a/rustfmt-config/src/config_type.rs +++ b/rustfmt-config/src/config_type.rs @@ -76,9 +76,12 @@ macro_rules! create_config { ($($i:ident: $ty:ty, $def:expr, $stb:expr, $( $dstring:expr ),+ );+ $(;)*) => ( #[derive(Clone)] pub struct Config { - // For each config item, we store a bool indicating whether it has - // been accessed and the value, and a bool whether the option was - // manually initialised, or taken from the default, + // For each config item, we store: + // - a bool indicating whether it has been accessed + // - a bool indicating whether the option was manually initialised, or taken from the + // default, + // - the value of the option + // - whether this option is stable (if false, it will only work on nightly builds) $($i: (Cell, bool, $ty, bool)),+ } @@ -358,12 +361,12 @@ macro_rules! create_config { } fn set_heuristics(&mut self) { - if self.use_small_heuristics.2 { - let max_width = self.max_width.2; - self.set().width_heuristics(WidthHeuristics::scaled(max_width)); - } else { - self.set().width_heuristics(WidthHeuristics::null()); - } + let width = match self.use_small_heuristics.2 { + LineWidth::Minimum => WidthHeuristics::null(), + LineWidth::Medium => WidthHeuristics::scaled(self.max_width.2), + LineWidth::Maximum => WidthHeuristics::maximum(self.max_width.2), + }; + self.set().width_heuristics(width); } } diff --git a/rustfmt-config/src/lib.rs b/rustfmt-config/src/lib.rs index 3b6dca769d9..56778d85dca 100644 --- a/rustfmt-config/src/lib.rs +++ b/rustfmt-config/src/lib.rs @@ -48,8 +48,7 @@ create_config! { tab_spaces: usize, 4, true, "Number of spaces per tab"; newline_style: NewlineStyle, NewlineStyle::Unix, true, "Unix or Windows line endings"; indent_style: IndentStyle, IndentStyle::Block, false, "How do we indent expressions or items."; - use_small_heuristics: bool, true, false, "Whether to use different formatting for items and\ - expressions if they satisfy a heuristic notion of 'small'."; + use_small_heuristics: LineWidth, LineWidth::Medium, false, "The general width of lines."; // strings and comments format_strings: bool, false, false, "Format string literals where necessary"; diff --git a/rustfmt-config/src/options.rs b/rustfmt-config/src/options.rs index 6aa3db4f3df..7aac2514a7d 100644 --- a/rustfmt-config/src/options.rs +++ b/rustfmt-config/src/options.rs @@ -189,6 +189,15 @@ configuration_option_enum! { Color: Auto, } +configuration_option_enum! { LineWidth: + // Always format lines as short as possible (?) + Minimum, + // Use width heuristics to determine whether to reformat a line + Medium, + // Format lines to be as long as possible + Maximum, +} + #[derive(Deserialize, Serialize, Clone, Debug)] pub struct WidthHeuristics { // Maximum width of the args of a function call before falling back @@ -235,6 +244,17 @@ impl WidthHeuristics { single_line_if_else_max_width: (50.0 * max_width_ratio).round() as usize, } } + + pub fn maximum(max_width: usize) -> WidthHeuristics { + WidthHeuristics { + fn_call_width: max_width, + struct_lit_width: max_width, + struct_variant_width: max_width, + array_width: max_width, + chain_width: max_width, + single_line_if_else_max_width: max_width, + } + } } impl ::std::str::FromStr for WidthHeuristics { diff --git a/rustfmt-core/tests/source/chains.rs b/rustfmt-core/tests/source/chains.rs index 0ed52ae61be..1e6242909cd 100644 --- a/rustfmt-core/tests/source/chains.rs +++ b/rustfmt-core/tests/source/chains.rs @@ -1,5 +1,5 @@ // rustfmt-normalize_comments: true -// rustfmt-use_small_heuristics: false +// rustfmt-use_small_heuristics: minimum // Test chain formatting. fn main() { diff --git a/rustfmt-core/tests/source/max-chains.rs b/rustfmt-core/tests/source/max-chains.rs new file mode 100644 index 00000000000..59d07cd4d4c --- /dev/null +++ b/rustfmt-core/tests/source/max-chains.rs @@ -0,0 +1,231 @@ +// rustfmt-normalize_comments: true +// rustfmt-use_small_heuristics: maximum +// Test chain formatting with `maximum` line lengths + +fn main() { + let a = b .c + .d.1 + .foo(|x| x + 1); + + bbbbbbbbbbbbbbbbbbb.ccccccccccccccccccccccccccccccccccccc + .ddddddddddddddddddddddddddd(); + + bbbbbbbbbbbbbbbbbbb.ccccccccccccccccccccccccccccccccccccc.ddddddddddddddddddddddddddd.eeeeeeee(); + + let f = fooooooooooooooooooooooooooooooooooooooooooooooooooo.baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaar; + + // Test case where first chain element isn't a path, but is shorter than + // the size of a tab. + x() + .y(|| match cond() { true => (), false => () }); + + loong_func() + .quux(move || if true { + 1 + } else { + 2 + }); + + some_fuuuuuuuuunction() + .method_call_a(aaaaa, bbbbb, |c| { + let x = c; + x + }); + + some_fuuuuuuuuunction().method_call_a(aaaaa, bbbbb, |c| { + let x = c; + x + }).method_call_b(aaaaa, bbbbb, |c| { + let x = c; + x + }); + + fffffffffffffffffffffffffffffffffff(a, + { + SCRIPT_TASK_ROOT + .with(|root| { + *root.borrow_mut() = Some(&script_task); + }); + }); + + let suuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuum = xxxxxxx + .map(|x| x + 5) + .map(|x| x / 2) + .fold(0, |acc, x| acc + x); + + body.fold(Body::new(), |mut body, chunk| { + body.extend(chunk); + Ok(body) + }).and_then(move |body| { + let req = Request::from_parts(parts, body); + f(req).map_err(|_| io::Error::new(io::ErrorKind::Other, "")) + }); + + aaaaaaaaaaaaaaaa.map(|x| { + x += 1; + x + }).filter(some_mod::some_filter) +} + +fn floaters() { + let z = Foo { + field1: val1, + field2: val2, + }; + + let x = Foo { + field1: val1, + field2: val2, + }.method_call().method_call(); + + let y = if cond { + val1 + } else { + val2 + } + .method_call(); + + { + match x { + PushParam => { + // params are 1-indexed + stack.push(mparams[match cur.to_digit(10) { + Some(d) => d as usize - 1, + None => return Err("bad param number".to_owned()), + }] + .clone()); + } + } + } + + if cond { some(); } else { none(); } + .bar() + .baz(); + + Foo { x: val } .baz(|| { force(); multiline(); }) .quux(); + + Foo { y: i_am_multi_line, z: ok } + .baz(|| { + force(); multiline(); + }) + .quux(); + + a + match x { true => "yay!", false => "boo!" }.bar() +} + +fn is_replaced_content() -> bool { + constellat.send(ConstellationMsg::ViewportConstrained( + self.id, constraints)).unwrap(); +} + +fn issue587() { + a.b::<()>(c); + + std::mem::transmute(dl.symbol::<()>("init").unwrap()) +} + +fn try_shorthand() { + let x = expr?; + let y = expr.kaas()?.test(); + let loooooooooooooooooooooooooooooooooooooooooong = does_this?.look?.good?.should_we_break?.after_the_first_question_mark?; + let yyyy = expr?.another?.another?.another?.another?.another?.another?.another?.another?.test(); + let zzzz = expr?.another?.another?.another?.another?; + let aaa = x ???????????? ?????????????? ???? ????? ?????????????? ????????? ?????????????? ??; + + let y = a.very .loooooooooooooooooooooooooooooooooooooong() .chain() + .inside() .weeeeeeeeeeeeeee()? .test() .0 + .x; + + parameterized(f, + substs, + def_id, + Ns::Value, + &[], + |tcx| tcx.lookup_item_type(def_id).generics)?; + fooooooooooooooooooooooooooo()?.bar()?.baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaz()?; +} + +fn issue_1004() { + match *self { + ty::ImplOrTraitItem::MethodTraitItem(ref i) => write!(f, "{:?}", i), + ty::ImplOrTraitItem::ConstTraitItem(ref i) => write!(f, "{:?}", i), + ty::ImplOrTraitItem::TypeTraitItem(ref i) => write!(f, "{:?}", i), + } + ?; + + ty::tls::with(|tcx| { + let tap = ty::Binder(TraitAndProjections(principal, projections)); + in_binder(f, tcx, &ty::Binder(""), Some(tap)) + }) + ?; +} + +fn issue1392() { + test_method(r#" + if foo { + a(); + } + else { + b(); + } + "#.trim()); +} + +// #2067 +impl Settings { + fn save(&self) -> Result<()> { + let mut file = File::create(&settings_path).chain_err(|| ErrorKind::WriteError(settings_path.clone()))?; + } +} + +fn issue2126() { + { + { + { + { + { + let x = self.span_from(sub_span.expect("No span found for struct arant variant")); + self.sspanpan_from_span(sub_span.expect("No span found for struct variant")); + let x = self.spanpan_from_span(sub_span.expect("No span found for struct variant"))?; + } + } + } + } + } +} + +// #2200 +impl Foo { + pub fn from_ast(diagnostic: &::errors::Handler, + attrs: &[ast::Attribute]) -> Attributes { + let other_attrs = attrs.iter().filter_map(|attr| { + attr.with_desugared_doc(|attr| { + if attr.check_name("doc") { + if let Some(mi) = attr.meta() { + if let Some(value) = mi.value_str() { + doc_strings.push(DocFragment::Include(line, + attr.span, + filename, + contents)); + } + } + } + }) + }).collect(); + } +} + +// #2415 +// Avoid orphan in chain +fn issue2415() { + let base_url = (|| { + // stuff + + Ok((|| { + // stuff + Some(value.to_string()) + })() + .ok_or("")?) + })() + .unwrap_or_else(|_: Box<::std::error::Error>| String::from("")); +} diff --git a/rustfmt-core/tests/target/chains.rs b/rustfmt-core/tests/target/chains.rs index 8a41eec2bde..63684c0972f 100644 --- a/rustfmt-core/tests/target/chains.rs +++ b/rustfmt-core/tests/target/chains.rs @@ -1,5 +1,5 @@ // rustfmt-normalize_comments: true -// rustfmt-use_small_heuristics: false +// rustfmt-use_small_heuristics: minimum // Test chain formatting. fn main() { diff --git a/rustfmt-core/tests/target/max-chains.rs b/rustfmt-core/tests/target/max-chains.rs new file mode 100644 index 00000000000..134f74d76fe --- /dev/null +++ b/rustfmt-core/tests/target/max-chains.rs @@ -0,0 +1,241 @@ +// rustfmt-normalize_comments: true +// rustfmt-use_small_heuristics: maximum +// Test chain formatting with `maximum` line lengths + +fn main() { + let a = b.c.d.1.foo(|x| x + 1); + + bbbbbbbbbbbbbbbbbbb.ccccccccccccccccccccccccccccccccccccc.ddddddddddddddddddddddddddd(); + + bbbbbbbbbbbbbbbbbbb + .ccccccccccccccccccccccccccccccccccccc + .ddddddddddddddddddddddddddd + .eeeeeeee(); + + let f = fooooooooooooooooooooooooooooooooooooooooooooooooooo + .baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaar; + + // Test case where first chain element isn't a path, but is shorter than + // the size of a tab. + x().y(|| match cond() { + true => (), + false => (), + }); + + loong_func().quux(move || if true { 1 } else { 2 }); + + some_fuuuuuuuuunction().method_call_a(aaaaa, bbbbb, |c| { + let x = c; + x + }); + + some_fuuuuuuuuunction() + .method_call_a(aaaaa, bbbbb, |c| { + let x = c; + x + }) + .method_call_b(aaaaa, bbbbb, |c| { + let x = c; + x + }); + + fffffffffffffffffffffffffffffffffff(a, { + SCRIPT_TASK_ROOT.with(|root| { + *root.borrow_mut() = Some(&script_task); + }); + }); + + let suuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuum = + xxxxxxx.map(|x| x + 5).map(|x| x / 2).fold(0, |acc, x| acc + x); + + body.fold(Body::new(), |mut body, chunk| { + body.extend(chunk); + Ok(body) + }).and_then(move |body| { + let req = Request::from_parts(parts, body); + f(req).map_err(|_| io::Error::new(io::ErrorKind::Other, "")) + }); + + aaaaaaaaaaaaaaaa + .map(|x| { + x += 1; + x + }) + .filter(some_mod::some_filter) +} + +fn floaters() { + let z = Foo { field1: val1, field2: val2 }; + + let x = Foo { field1: val1, field2: val2 }.method_call().method_call(); + + let y = if cond { val1 } else { val2 }.method_call(); + + { + match x { + PushParam => { + // params are 1-indexed + stack.push( + mparams[match cur.to_digit(10) { + Some(d) => d as usize - 1, + None => return Err("bad param number".to_owned()), + }].clone(), + ); + } + } + } + + if cond { + some(); + } else { + none(); + }.bar() + .baz(); + + Foo { x: val } + .baz(|| { + force(); + multiline(); + }) + .quux(); + + Foo { y: i_am_multi_line, z: ok } + .baz(|| { + force(); + multiline(); + }) + .quux(); + + a + match x { + true => "yay!", + false => "boo!", + }.bar() +} + +fn is_replaced_content() -> bool { + constellat.send(ConstellationMsg::ViewportConstrained(self.id, constraints)).unwrap(); +} + +fn issue587() { + a.b::<()>(c); + + std::mem::transmute(dl.symbol::<()>("init").unwrap()) +} + +fn try_shorthand() { + let x = expr?; + let y = expr.kaas()?.test(); + let loooooooooooooooooooooooooooooooooooooooooong = + does_this?.look?.good?.should_we_break?.after_the_first_question_mark?; + let yyyy = expr?.another?.another?.another?.another?.another?.another?.another?.another?.test(); + let zzzz = expr?.another?.another?.another?.another?; + let aaa = x??????????????????????????????????????????????????????????????????????????; + + let y = a.very + .loooooooooooooooooooooooooooooooooooooong() + .chain() + .inside() + .weeeeeeeeeeeeeee()? + .test() + .0 + .x; + + parameterized(f, substs, def_id, Ns::Value, &[], |tcx| tcx.lookup_item_type(def_id).generics)?; + fooooooooooooooooooooooooooo()? + .bar()? + .baaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaz()?; +} + +fn issue_1004() { + match *self { + ty::ImplOrTraitItem::MethodTraitItem(ref i) => write!(f, "{:?}", i), + ty::ImplOrTraitItem::ConstTraitItem(ref i) => write!(f, "{:?}", i), + ty::ImplOrTraitItem::TypeTraitItem(ref i) => write!(f, "{:?}", i), + }?; + + ty::tls::with(|tcx| { + let tap = ty::Binder(TraitAndProjections(principal, projections)); + in_binder(f, tcx, &ty::Binder(""), Some(tap)) + })?; +} + +fn issue1392() { + test_method( + r#" + if foo { + a(); + } + else { + b(); + } + "#.trim(), + ); +} + +// #2067 +impl Settings { + fn save(&self) -> Result<()> { + let mut file = File::create(&settings_path) + .chain_err(|| ErrorKind::WriteError(settings_path.clone()))?; + } +} + +fn issue2126() { + { + { + { + { + { + let x = self.span_from( + sub_span.expect("No span found for struct arant variant"), + ); + self.sspanpan_from_span( + sub_span.expect("No span found for struct variant"), + ); + let x = self.spanpan_from_span( + sub_span.expect("No span found for struct variant"), + )?; + } + } + } + } + } +} + +// #2200 +impl Foo { + pub fn from_ast(diagnostic: &::errors::Handler, attrs: &[ast::Attribute]) -> Attributes { + let other_attrs = attrs + .iter() + .filter_map(|attr| { + attr.with_desugared_doc(|attr| { + if attr.check_name("doc") { + if let Some(mi) = attr.meta() { + if let Some(value) = mi.value_str() { + doc_strings.push(DocFragment::Include( + line, + attr.span, + filename, + contents, + )); + } + } + } + }) + }) + .collect(); + } +} + +// #2415 +// Avoid orphan in chain +fn issue2415() { + let base_url = (|| { + // stuff + + Ok((|| { + // stuff + Some(value.to_string()) + })().ok_or("")?) + })().unwrap_or_else(|_: Box<::std::error::Error>| String::from("")); +}