diff --git a/src/chains.rs b/src/chains.rs index 6122aa14614..4b6824a9073 100644 --- a/src/chains.rs +++ b/src/chains.rs @@ -23,6 +23,7 @@ use Indent; use rewrite::{Rewrite, RewriteContext}; use utils::first_line_width; use expr::rewrite_call; +use config::BlockIndentStyle; use syntax::{ast, ptr}; use syntax::codemap::{mk_sp, Span}; @@ -41,17 +42,24 @@ pub fn rewrite_chain(mut expr: &ast::Expr, expr = subexpr; } - let parent = subexpr_list.pop().unwrap(); - let parent_rewrite = try_opt!(expr.rewrite(context, width, offset)); - let (extra_indent, extend) = if !parent_rewrite.contains('\n') && is_continuable(parent) || - parent_rewrite.len() <= context.config.tab_spaces { - (Indent::new(0, parent_rewrite.len()), true) - } else { - (Indent::new(context.config.tab_spaces, 0), false) + let parent_block_indent = match context.config.chain_base_indent { + BlockIndentStyle::Visual => offset, + BlockIndentStyle::Inherit => context.block_indent, + BlockIndentStyle::Tabbed => context.block_indent.block_indent(context.config), + }; + let parent_context = &RewriteContext { block_indent: parent_block_indent, ..*context }; + let parent = subexpr_list.pop().unwrap(); + let parent_rewrite = try_opt!(expr.rewrite(parent_context, width, offset)); + let (indent, extend) = if !parent_rewrite.contains('\n') && is_continuable(parent) || + parent_rewrite.len() <= context.config.tab_spaces { + (offset + Indent::new(0, parent_rewrite.len()), true) + } else if is_block_expr(parent, &parent_rewrite) { + (parent_block_indent, false) + } else { + (offset + Indent::new(context.config.tab_spaces, 0), false) }; - let indent = offset + extra_indent; - let max_width = try_opt!(width.checked_sub(extra_indent.width())); + let max_width = try_opt!((width + offset.width()).checked_sub(indent.width())); let mut rewrites = try_opt!(subexpr_list.iter() .rev() .map(|e| { @@ -114,7 +122,7 @@ pub fn rewrite_chain(mut expr: &ast::Expr, _ => total_width <= width && rewrites.iter().all(|s| !s.contains('\n')), }; - let connector = if fits_single_line { + let connector = if fits_single_line && !parent_rewrite.contains('\n') { String::new() } else { format!("\n{}", indent.to_string(context.config)) @@ -132,6 +140,27 @@ pub fn rewrite_chain(mut expr: &ast::Expr, rewrites.join(&connector))) } +// States whether an expression's last line exclusively consists of closing +// parens, braces and brackets in its idiomatic formatting. +fn is_block_expr(expr: &ast::Expr, repr: &str) -> bool { + match expr.node { + ast::Expr_::ExprStruct(..) | + ast::Expr_::ExprWhile(..) | + ast::Expr_::ExprWhileLet(..) | + ast::Expr_::ExprIf(..) | + ast::Expr_::ExprIfLet(..) | + ast::Expr_::ExprBlock(..) | + ast::Expr_::ExprLoop(..) | + ast::Expr_::ExprForLoop(..) | + ast::Expr_::ExprMatch(..) => repr.contains('\n'), + ast::Expr_::ExprParen(ref expr) | + ast::Expr_::ExprBinary(_, _, ref expr) | + ast::Expr_::ExprIndex(_, ref expr) | + ast::Expr_::ExprUnary(_, ref expr) => is_block_expr(expr, repr), + _ => false, + } +} + fn pop_expr_chain<'a>(expr: &'a ast::Expr) -> Option<&'a ast::Expr> { match expr.node { ast::Expr_::ExprMethodCall(_, _, ref expressions) => { diff --git a/src/config.rs b/src/config.rs index 6a7e686dc11..d788b17cfe7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -290,9 +290,10 @@ create_config! { "Multiline style on literal structs"; enum_trailing_comma: bool, true, "Put a trailing comma on enum declarations"; report_todo: ReportTactic, ReportTactic::Always, - "Report all occurrences of TODO in source file comments"; + "Report all, none or unnumbered occurrences of TODO in source file comments"; report_fixme: ReportTactic, ReportTactic::Never, - "Report all occurrences of FIXME in source file comments"; + "Report all, none or unnumbered occurrences of FIXME in source file comments"; + chain_base_indent: BlockIndentStyle, BlockIndentStyle::Visual, "Indent on chain base"; // Alphabetically, case sensitive. reorder_imports: bool, false, "Reorder import statements alphabetically"; single_line_if_else: bool, false, "Put else on same line as closing brace for if statements"; diff --git a/tests/source/chains-block-indented-base.rs b/tests/source/chains-block-indented-base.rs new file mode 100644 index 00000000000..05459dc6973 --- /dev/null +++ b/tests/source/chains-block-indented-base.rs @@ -0,0 +1,30 @@ +// rustfmt-chain_base_indent: Inherit +// Test chain formatting with block indented base + +fn floaters() { + 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()); + } + } + } +} diff --git a/tests/source/chains.rs b/tests/source/chains.rs index 2a400b306d2..a50a9c51253 100644 --- a/tests/source/chains.rs +++ b/tests/source/chains.rs @@ -55,3 +55,49 @@ fn main() { 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() +} diff --git a/tests/target/chains-block-indented-base.rs b/tests/target/chains-block-indented-base.rs new file mode 100644 index 00000000000..5b9863689de --- /dev/null +++ b/tests/target/chains-block-indented-base.rs @@ -0,0 +1,31 @@ +// rustfmt-chain_base_indent: Inherit +// Test chain formatting with block indented base + +fn floaters() { + 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()); + } + } + } +} diff --git a/tests/target/chains.rs b/tests/target/chains.rs index ea9b6867efe..9e195b42d6e 100644 --- a/tests/target/chains.rs +++ b/tests/target/chains.rs @@ -62,3 +62,67 @@ fn main() { }) .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() +}