about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMarcus Klaas <mail@marcusklaas.nl>2015-09-09 23:17:31 +0200
committerMarcus Klaas <mail@marcusklaas.nl>2015-09-11 00:53:01 +0200
commit03c660633fbac0492f891aed6c47731be90f23ca (patch)
treeac75b88327aede5814f4afaab1da72a446a8362f
parent48d17f54d34c5457e88ef7ffa234601c6e342a70 (diff)
Refine chain breaking heuristics
Don't make a single line chain when it is was multi line in the source; allow overflow of the last chain element onto the next lines without breaking the chain.
-rw-r--r--src/chains.rs89
-rw-r--r--src/config.rs15
-rw-r--r--src/expr.rs23
-rw-r--r--src/items.rs22
-rw-r--r--src/lib.rs1
-rw-r--r--src/lists.rs196
-rw-r--r--src/types.rs25
-rw-r--r--tests/config/small_tabs.toml4
-rw-r--r--tests/source/chains-no-overflow.rs38
-rw-r--r--tests/source/chains.rs9
-rw-r--r--tests/source/expr-no-hints.rs8
-rw-r--r--tests/source/expr-visual-indent.rs9
-rw-r--r--tests/system.rs10
-rw-r--r--tests/target/chains-no-overflow.rs45
-rw-r--r--tests/target/chains-no-overlow-2.rs16
-rw-r--r--tests/target/chains.rs38
-rw-r--r--tests/target/expr-no-hints.rs6
-rw-r--r--tests/target/expr-visual-indent.rs9
18 files changed, 356 insertions, 207 deletions
diff --git a/src/chains.rs b/src/chains.rs
index 4556946649a..bd9f302824b 100644
--- a/src/chains.rs
+++ b/src/chains.rs
@@ -20,7 +20,7 @@
 // argument function argument strategy.
 
 use rewrite::{Rewrite, RewriteContext};
-use utils::make_indent;
+use utils::{first_line_width, make_indent};
 use expr::rewrite_call;
 
 use syntax::{ast, ptr};
@@ -51,19 +51,67 @@ pub fn rewrite_chain(mut expr: &ast::Expr,
     let indent = offset + extra_indent;
 
     let max_width = try_opt!(width.checked_sub(extra_indent));
-    let rewrites = try_opt!(subexpr_list.into_iter()
-                                        .rev()
-                                        .map(|e| {
-                                            rewrite_chain_expr(e,
-                                                               total_span,
-                                                               context,
-                                                               max_width,
-                                                               indent)
-                                        })
-                                        .collect::<Option<Vec<_>>>());
-
-    let total_width = rewrites.iter().fold(0, |a, b| a + b.len()) + parent_rewrite.len();
-    let fits_single_line = total_width <= width && rewrites.iter().all(|s| !s.contains('\n'));
+    let mut rewrites = try_opt!(subexpr_list.iter()
+                                            .rev()
+                                            .map(|e| {
+                                                rewrite_chain_expr(e,
+                                                                   total_span,
+                                                                   context,
+                                                                   max_width,
+                                                                   indent)
+                                            })
+                                            .collect::<Option<Vec<_>>>());
+
+    // Total of all items excluding the last.
+    let almost_total = rewrites.split_last()
+                               .unwrap()
+                               .1
+                               .iter()
+                               .fold(0, |a, b| a + first_line_width(b)) +
+                       parent_rewrite.len();
+    let total_width = almost_total + first_line_width(rewrites.last().unwrap());
+    let veto_single_line = if context.config.take_source_hints && subexpr_list.len() > 1 {
+        // Look at the source code. Unless all chain elements start on the same
+        // line, we won't consider putting them on a single line either.
+        let first_line_no = context.codemap.lookup_char_pos(subexpr_list[0].span.lo).line;
+
+        subexpr_list[1..]
+            .iter()
+            .any(|ex| context.codemap.lookup_char_pos(ex.span.hi).line != first_line_no)
+    } else {
+        false
+    };
+
+    let fits_single_line = !veto_single_line &&
+                           match subexpr_list[0].node {
+        ast::Expr_::ExprMethodCall(ref method_name, ref types, ref expressions)
+            if context.config.chains_overflow_last => {
+            let (last, init) = rewrites.split_last_mut().unwrap();
+
+            if init.iter().all(|s| !s.contains('\n')) && total_width <= width {
+                let last_rewrite = width.checked_sub(almost_total)
+                                        .and_then(|inner_width| {
+                                            rewrite_method_call(method_name.node,
+                                                                types,
+                                                                expressions,
+                                                                total_span,
+                                                                context,
+                                                                inner_width,
+                                                                offset + almost_total)
+                                        });
+                match last_rewrite {
+                    Some(mut string) => {
+                        ::std::mem::swap(&mut string, last);
+                        true
+                    }
+                    None => false,
+                }
+            } else {
+                false
+            }
+        }
+        _ => total_width <= width && rewrites.iter().all(|s| !s.contains('\n')),
+    };
 
     let connector = if fits_single_line {
         String::new()
@@ -101,7 +149,11 @@ fn rewrite_chain_expr(expr: &ast::Expr,
                       -> Option<String> {
     match expr.node {
         ast::Expr_::ExprMethodCall(ref method_name, ref types, ref expressions) => {
-            rewrite_method_call(method_name.node, types, expressions, span, context, width, offset)
+            let inner = &RewriteContext {
+                block_indent: offset,
+                ..*context
+            };
+            rewrite_method_call(method_name.node, types, expressions, span, inner, width, offset)
         }
         ast::Expr_::ExprField(_, ref field) => {
             Some(format!(".{}", field.node))
@@ -137,10 +189,7 @@ fn rewrite_method_call(method_name: ast::Ident,
     };
 
     let callee_str = format!(".{}{}", method_name, type_str);
-    let inner_context = &RewriteContext {
-        block_indent: offset,
-        ..*context
-    };
+    let span = mk_sp(args[0].span.hi, span.hi);
 
-    rewrite_call(inner_context, &callee_str, args, span, width, offset)
+    rewrite_call(context, &callee_str, &args[1..], span, width, offset)
 }
diff --git a/src/config.rs b/src/config.rs
index 95994531950..d45f184051f 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -133,8 +133,8 @@ create_config! {
     fn_args_density: Density,
     fn_args_layout: StructLitStyle,
     fn_arg_indent: BlockIndentStyle,
-    where_density: Density, // Should we at least try to put the where clause on the same line as
-                            // the rest of the function decl?
+    where_density: Density, // Should we at least try to put the where clause on
+                            // the same line as the rest of the function decl?
     where_indent: BlockIndentStyle, // Visual will be treated like Tabbed
     where_layout: ListTactic,
     where_pred_indent: BlockIndentStyle,
@@ -147,14 +147,14 @@ create_config! {
     report_todo: ReportTactic,
     report_fixme: ReportTactic,
     reorder_imports: bool, // Alphabetically, case sensitive.
-    expr_indent_style: BlockIndentStyle,
-    closure_indent_style: BlockIndentStyle,
     single_line_if_else: bool,
     format_strings: bool,
+    chains_overflow_last: bool,
+    take_source_hints: bool, // Retain some formatting characteristics from
+                             // the source code.
 }
 
 impl Default for Config {
-
     fn default() -> Config {
         Config {
             max_width: 100,
@@ -181,11 +181,10 @@ impl Default for Config {
             report_todo: ReportTactic::Always,
             report_fixme: ReportTactic::Never,
             reorder_imports: false,
-            expr_indent_style: BlockIndentStyle::Tabbed,
-            closure_indent_style: BlockIndentStyle::Visual,
             single_line_if_else: false,
             format_strings: true,
+            chains_overflow_last: true,
+            take_source_hints: true,
         }
     }
-
 }
diff --git a/src/expr.rs b/src/expr.rs
index 4da191e8bf8..98b93a5faa7 100644
--- a/src/expr.rs
+++ b/src/expr.rs
@@ -40,6 +40,12 @@ impl Rewrite for ast::Expr {
                 }
             }
             ast::Expr_::ExprCall(ref callee, ref args) => {
+                // FIXME using byte lens instead of char lens (and probably all over the place too)
+                // 2 is for parens
+                let max_callee_width = try_opt!(width.checked_sub(2));
+                let callee_str = try_opt!(callee.rewrite(context, max_callee_width, offset));
+                let span = mk_sp(callee.span.hi, self.span.hi);
+
                 rewrite_call(context, &**callee, args, self.span, width, offset)
             }
             ast::Expr_::ExprParen(ref subexpr) => {
@@ -284,8 +290,10 @@ impl Rewrite for ast::Block {
                 };
 
                 if is_simple_block(self, context.codemap) && prefix.len() < width {
-                    let body =
-                        self.expr.as_ref().unwrap().rewrite(context, width - prefix.len(), offset);
+                    let body = self.expr
+                                   .as_ref()
+                                   .unwrap()
+                                   .rewrite(context, width - prefix.len(), offset);
                     if let Some(ref expr_str) = body {
                         let result = format!("{}{{ {} }}", prefix, expr_str);
                         if result.len() <= width && !result.contains('\n') {
@@ -677,15 +685,13 @@ impl Rewrite for ast::Arm {
         total_width += (pat_strs.len() - 1) * 3;
 
         let mut vertical = total_width > pat_budget || pat_strs.iter().any(|p| p.contains('\n'));
-        if !vertical {
+        if !vertical && context.config.take_source_hints {
             // If the patterns were previously stacked, keep them stacked.
-            // FIXME should be an option.
             let pat_span = mk_sp(pats[0].span.lo, pats[pats.len() - 1].span.hi);
             let pat_str = context.snippet(pat_span);
             vertical = pat_str.find('\n').is_some();
         }
 
-
         let pats_width = if vertical {
             pat_strs[pat_strs.len() - 1].len()
         } else {
@@ -1015,9 +1021,10 @@ fn rewrite_struct_lit<'a>(context: &RewriteContext,
                                  match *item {
                                      StructLitField::Regular(ref field) => field.span.lo,
                                      StructLitField::Base(ref expr) => {
-                                         let last_field_hi = fields.last()
-                                                                   .map_or(span.lo,
-                                                                           |field| field.span.hi);
+                                         let last_field_hi = fields.last().map_or(span.lo,
+                                                                                  |field| {
+                                                                                      field.span.hi
+                                                                                  });
                                          let snippet = context.snippet(mk_sp(last_field_hi,
                                                                              expr.span.lo));
                                          let pos = snippet.find_uncommented("..").unwrap();
diff --git a/src/items.rs b/src/items.rs
index a2e562d9d26..2547e8f5dd5 100644
--- a/src/items.rs
+++ b/src/items.rs
@@ -512,8 +512,9 @@ impl<'a> FmtVisitor<'a> {
                                              |arg| arg.ty.span.hi,
                                              |arg| {
                                                  // FIXME silly width, indent
-                                                 arg.ty.rewrite(&self.get_context(), 1000, 0)
-                                                       .unwrap()
+                                                 arg.ty
+                                                    .rewrite(&self.get_context(), 1000, 0)
+                                                    .unwrap()
                                              },
                                              span_after(field.span, "(", self.codemap),
                                              next_span_start);
@@ -810,15 +811,14 @@ impl<'a> FmtVisitor<'a> {
                          .map(|ty_param| ty_param.rewrite(&context, h_budget, offset).unwrap());
 
         // Extract comments between generics.
-        let lt_spans = lifetimes.iter()
-                                .map(|l| {
-                                    let hi = if l.bounds.is_empty() {
-                                        l.lifetime.span.hi
-                                    } else {
-                                        l.bounds[l.bounds.len() - 1].span.hi
-                                    };
-                                    codemap::mk_sp(l.lifetime.span.lo, hi)
-                                });
+        let lt_spans = lifetimes.iter().map(|l| {
+            let hi = if l.bounds.is_empty() {
+                l.lifetime.span.hi
+            } else {
+                l.bounds[l.bounds.len() - 1].span.hi
+            };
+            codemap::mk_sp(l.lifetime.span.lo, hi)
+        });
         let ty_spans = tys.iter().map(span_for_ty_param);
 
         let items = itemize_list(self.codemap,
diff --git a/src/lib.rs b/src/lib.rs
index 6993d3044d3..3f8900d6713 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -10,6 +10,7 @@
 
 #![feature(rustc_private)]
 #![feature(custom_attribute)]
+#![feature(slice_splits)]
 #![allow(unused_attributes)]
 
 // TODO we're going to allocate a whole bunch of temp Strings, is it worth
diff --git a/src/lists.rs b/src/lists.rs
index 3435bd8170b..678c84c199a 100644
--- a/src/lists.rs
+++ b/src/lists.rs
@@ -269,109 +269,107 @@ impl<'a, T, I, F1, F2, F3> Iterator for ListItems<'a, I, F1, F2, F3>
     fn next(&mut self) -> Option<Self::Item> {
         let white_space: &[_] = &[' ', '\t'];
 
-        self.inner
-            .next()
-            .map(|item| {
-                let mut new_lines = false;
-                // Pre-comment
-                let pre_snippet = self.codemap
-                                      .span_to_snippet(codemap::mk_sp(self.prev_span_end,
-                                                                      (self.get_lo)(&item)))
-                                      .unwrap();
-                let trimmed_pre_snippet = pre_snippet.trim();
-                let pre_comment = if !trimmed_pre_snippet.is_empty() {
-                    Some(trimmed_pre_snippet.to_owned())
-                } else {
-                    None
-                };
-
-                // Post-comment
-                let next_start = match self.inner.peek() {
-                    Some(ref next_item) => (self.get_lo)(next_item),
-                    None => self.next_span_start,
-                };
-                let post_snippet = self.codemap
-                                       .span_to_snippet(codemap::mk_sp((self.get_hi)(&item),
-                                                                       next_start))
-                                       .unwrap();
-
-                let comment_end = match self.inner.peek() {
-                    Some(..) => {
-                        let block_open_index = post_snippet.find("/*");
-                        let newline_index = post_snippet.find('\n');
-                        let separator_index = post_snippet.find_uncommented(",").unwrap();
-
-                        match (block_open_index, newline_index) {
-                            // Separator before comment, with the next item on same line.
-                            // Comment belongs to next item.
-                            (Some(i), None) if i > separator_index => {
-                                separator_index + 1
-                            }
-                            // Block-style post-comment before the separator.
-                            (Some(i), None) => {
-                                cmp::max(find_comment_end(&post_snippet[i..]).unwrap() + i,
-                                         separator_index + 1)
-                            }
-                            // Block-style post-comment. Either before or after the separator.
-                            (Some(i), Some(j)) if i < j => {
-                                cmp::max(find_comment_end(&post_snippet[i..]).unwrap() + i,
-                                         separator_index + 1)
-                            }
-                            // Potential *single* line comment.
-                            (_, Some(j)) => j + 1,
-                            _ => post_snippet.len(),
+        self.inner.next().map(|item| {
+            let mut new_lines = false;
+            // Pre-comment
+            let pre_snippet = self.codemap
+                                  .span_to_snippet(codemap::mk_sp(self.prev_span_end,
+                                                                  (self.get_lo)(&item)))
+                                  .unwrap();
+            let trimmed_pre_snippet = pre_snippet.trim();
+            let pre_comment = if !trimmed_pre_snippet.is_empty() {
+                Some(trimmed_pre_snippet.to_owned())
+            } else {
+                None
+            };
+
+            // Post-comment
+            let next_start = match self.inner.peek() {
+                Some(ref next_item) => (self.get_lo)(next_item),
+                None => self.next_span_start,
+            };
+            let post_snippet = self.codemap
+                                   .span_to_snippet(codemap::mk_sp((self.get_hi)(&item),
+                                                                   next_start))
+                                   .unwrap();
+
+            let comment_end = match self.inner.peek() {
+                Some(..) => {
+                    let block_open_index = post_snippet.find("/*");
+                    let newline_index = post_snippet.find('\n');
+                    let separator_index = post_snippet.find_uncommented(",").unwrap();
+
+                    match (block_open_index, newline_index) {
+                        // Separator before comment, with the next item on same line.
+                        // Comment belongs to next item.
+                        (Some(i), None) if i > separator_index => {
+                            separator_index + 1
                         }
-                    }
-                    None => {
-                        post_snippet.find_uncommented(self.terminator).unwrap_or(post_snippet.len())
-                    }
-                };
-
-                if !post_snippet.is_empty() && comment_end > 0 {
-                    // Account for extra whitespace between items. This is fiddly
-                    // because of the way we divide pre- and post- comments.
-
-                    // Everything from the separator to the next item.
-                    let test_snippet = &post_snippet[comment_end-1..];
-                    let first_newline = test_snippet.find('\n').unwrap_or(test_snippet.len());
-                    // From the end of the first line of comments.
-                    let test_snippet = &test_snippet[first_newline..];
-                    let first = test_snippet.find(|c: char| !c.is_whitespace())
-                                            .unwrap_or(test_snippet.len());
-                    // From the end of the first line of comments to the next non-whitespace char.
-                    let test_snippet = &test_snippet[..first];
-
-                    if test_snippet.chars().filter(|c| c == &'\n').count() > 1 {
-                        // There were multiple line breaks which got trimmed to nothing.
-                        new_lines = true;
+                        // Block-style post-comment before the separator.
+                        (Some(i), None) => {
+                            cmp::max(find_comment_end(&post_snippet[i..]).unwrap() + i,
+                                     separator_index + 1)
+                        }
+                        // Block-style post-comment. Either before or after the separator.
+                        (Some(i), Some(j)) if i < j => {
+                            cmp::max(find_comment_end(&post_snippet[i..]).unwrap() + i,
+                                     separator_index + 1)
+                        }
+                        // Potential *single* line comment.
+                        (_, Some(j)) => j + 1,
+                        _ => post_snippet.len(),
                     }
                 }
-
-                // Cleanup post-comment: strip separators and whitespace.
-                self.prev_span_end = (self.get_hi)(&item) + BytePos(comment_end as u32);
-                let post_snippet = post_snippet[..comment_end].trim();
-
-                let post_snippet_trimmed = if post_snippet.starts_with(',') {
-                    post_snippet[1..].trim_matches(white_space)
-                } else if post_snippet.ends_with(",") {
-                    post_snippet[..(post_snippet.len() - 1)].trim_matches(white_space)
-                } else {
-                    post_snippet
-                };
-
-                let post_comment = if !post_snippet_trimmed.is_empty() {
-                    Some(post_snippet_trimmed.to_owned())
-                } else {
-                    None
-                };
-
-                ListItem {
-                    pre_comment: pre_comment,
-                    item: (self.get_item_string)(&item),
-                    post_comment: post_comment,
-                    new_lines: new_lines,
+                None => {
+                    post_snippet.find_uncommented(self.terminator).unwrap_or(post_snippet.len())
+                }
+            };
+
+            if !post_snippet.is_empty() && comment_end > 0 {
+                // Account for extra whitespace between items. This is fiddly
+                // because of the way we divide pre- and post- comments.
+
+                // Everything from the separator to the next item.
+                let test_snippet = &post_snippet[comment_end-1..];
+                let first_newline = test_snippet.find('\n').unwrap_or(test_snippet.len());
+                // From the end of the first line of comments.
+                let test_snippet = &test_snippet[first_newline..];
+                let first = test_snippet.find(|c: char| !c.is_whitespace())
+                                        .unwrap_or(test_snippet.len());
+                // From the end of the first line of comments to the next non-whitespace char.
+                let test_snippet = &test_snippet[..first];
+
+                if test_snippet.chars().filter(|c| c == &'\n').count() > 1 {
+                    // There were multiple line breaks which got trimmed to nothing.
+                    new_lines = true;
                 }
-            })
+            }
+
+            // Cleanup post-comment: strip separators and whitespace.
+            self.prev_span_end = (self.get_hi)(&item) + BytePos(comment_end as u32);
+            let post_snippet = post_snippet[..comment_end].trim();
+
+            let post_snippet_trimmed = if post_snippet.starts_with(',') {
+                post_snippet[1..].trim_matches(white_space)
+            } else if post_snippet.ends_with(",") {
+                post_snippet[..(post_snippet.len() - 1)].trim_matches(white_space)
+            } else {
+                post_snippet
+            };
+
+            let post_comment = if !post_snippet_trimmed.is_empty() {
+                Some(post_snippet_trimmed.to_owned())
+            } else {
+                None
+            };
+
+            ListItem {
+                pre_comment: pre_comment,
+                item: (self.get_item_string)(&item),
+                post_comment: post_comment,
+                new_lines: new_lines,
+            }
+        })
     }
 }
 
diff --git a/src/types.rs b/src/types.rs
index 83e92ff314b..e0f3140d4d9 100644
--- a/src/types.rs
+++ b/src/types.rs
@@ -130,16 +130,16 @@ impl<'a> Rewrite for SegmentParam<'a> {
     // FIXME doesn't always use width, offset
     fn rewrite(&self, context: &RewriteContext, width: usize, offset: usize) -> Option<String> {
         Some(match *self {
-                SegmentParam::LifeTime(ref lt) => {
-                    pprust::lifetime_to_string(lt)
-                }
-                SegmentParam::Type(ref ty) => {
-                    try_opt!(ty.rewrite(context, width, offset))
-                }
-                SegmentParam::Binding(ref binding) => {
-                    format!("{} = {}", binding.ident, try_opt!(binding.ty.rewrite(context, width, offset)))
-                }
-            })
+            SegmentParam::LifeTime(ref lt) => {
+                pprust::lifetime_to_string(lt)
+            }
+            SegmentParam::Type(ref ty) => {
+                try_opt!(ty.rewrite(context, width, offset))
+            }
+            SegmentParam::Binding(ref binding) => {
+                format!("{} = {}", binding.ident, try_opt!(binding.ty.rewrite(context, width, offset)))
+            }
+        })
     }
 }
 
@@ -368,8 +368,9 @@ impl Rewrite for ast::TyParamBound {
 
 impl Rewrite for ast::TyParamBounds {
     fn rewrite(&self, context: &RewriteContext, width: usize, offset: usize) -> Option<String> {
-        let strs: Vec<_> =
-            self.iter().map(|b| b.rewrite(context, width, offset).unwrap()).collect();
+        let strs: Vec<_> = self.iter()
+                               .map(|b| b.rewrite(context, width, offset).unwrap())
+                               .collect();
         Some(strs.join(" + "))
     }
 }
diff --git a/tests/config/small_tabs.toml b/tests/config/small_tabs.toml
index c5122b760a0..754cde57cd9 100644
--- a/tests/config/small_tabs.toml
+++ b/tests/config/small_tabs.toml
@@ -21,7 +21,7 @@ enum_trailing_comma = true
 report_todo = "Always"
 report_fixme = "Never"
 reorder_imports = false
-expr_indent_style = "Tabbed"
-closure_indent_style = "Visual"
 single_line_if_else = false
 format_strings = true
+chains_overflow_last = true
+take_source_hints = true
diff --git a/tests/source/chains-no-overflow.rs b/tests/source/chains-no-overflow.rs
new file mode 100644
index 00000000000..fa2c66524fc
--- /dev/null
+++ b/tests/source/chains-no-overflow.rs
@@ -0,0 +1,38 @@
+// rustfmt-chains_overflow_last: false
+// Test chain formatting without overflowing the last item.
+
+fn main() {
+    bbbbbbbbbbbbbbbbbbb.ccccccccccccccccccccccccccccccccccccc
+                       .ddddddddddddddddddddddddddd();
+
+    bbbbbbbbbbbbbbbbbbb.ccccccccccccccccccccccccccccccccccccc.ddddddddddddddddddddddddddd.eeeeeeee();
+
+    x()
+        .y(|| match cond() { true => (), false => () });
+
+    loong_func()
+        .quux(move || if true {
+            1
+        } else {
+            2
+        });
+
+    fffffffffffffffffffffffffffffffffff(a,
+                                        {
+                                            SCRIPT_TASK_ROOT
+                                            .with(|root| {
+                                                                // Another case of write_list failing us.
+                                                *root.borrow_mut()  =   Some(&script_task);
+                                            });
+                                        });                                        
+
+    let suuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuum = xxxxxxx
+        .map(|x| x + 5)
+        .map(|x| x / 2)
+        .fold(0, |acc, x| acc + x);
+
+    aaaaaaaaaaaaaaaa.map(|x| {
+                         x += 1;
+                         x
+                     }).filter(some_mod::some_filter)
+}
diff --git a/tests/source/chains.rs b/tests/source/chains.rs
index 8282c3baa6b..d2f11f1dcbe 100644
--- a/tests/source/chains.rs
+++ b/tests/source/chains.rs
@@ -1,10 +1,10 @@
 // Test chain formatting.
 
 fn main() {
-    let a = b.c
-             .d
-             .1
-             .foo(|x| x + 1);
+    // Don't put chains on a single list if it wasn't so in source.
+    let a = b .c
+    .d.1
+                .foo(|x| x + 1);
 
     bbbbbbbbbbbbbbbbbbb.ccccccccccccccccccccccccccccccccccccc
                        .ddddddddddddddddddddddddddd();
@@ -25,7 +25,6 @@ fn main() {
                                         {
                                             SCRIPT_TASK_ROOT
                                             .with(|root| {
-                                                                // Another case of write_list failing us.
                                                 *root.borrow_mut()  =   Some(&script_task);
                                             });
                                         });                                        
diff --git a/tests/source/expr-no-hints.rs b/tests/source/expr-no-hints.rs
new file mode 100644
index 00000000000..7329a742bac
--- /dev/null
+++ b/tests/source/expr-no-hints.rs
@@ -0,0 +1,8 @@
+// rustfmt-take_source_hints: false
+// We know best!
+
+fn main() {
+    a.b
+     .c
+     .d();
+}
diff --git a/tests/source/expr-visual-indent.rs b/tests/source/expr-visual-indent.rs
deleted file mode 100644
index 3d7c1b92be8..00000000000
--- a/tests/source/expr-visual-indent.rs
+++ /dev/null
@@ -1,9 +0,0 @@
-// rustfmt-expr_indent_style: Visual
-
-// Visual level block indentation.
-
-fn matcher() {
-    Some(while true {
-        test();
-    })
-}
\ No newline at end of file
diff --git a/tests/system.rs b/tests/system.rs
index 4db15dbcfd1..d0b6ae78faa 100644
--- a/tests/system.rs
+++ b/tests/system.rs
@@ -165,12 +165,10 @@ fn read_significant_comments(file_name: &str) -> HashMap<String, String> {
           .map(|line| line.ok().expect("Failed getting line."))
           .take_while(|line| line_regex.is_match(&line))
           .filter_map(|line| {
-              regex.captures_iter(&line)
-                   .next()
-                   .map(|capture| {
-                       (capture.at(1).expect("Couldn\'t unwrap capture.").to_owned(),
-                        capture.at(2).expect("Couldn\'t unwrap capture.").to_owned())
-                   })
+              regex.captures_iter(&line).next().map(|capture| {
+                  (capture.at(1).expect("Couldn\'t unwrap capture.").to_owned(),
+                   capture.at(2).expect("Couldn\'t unwrap capture.").to_owned())
+              })
           })
           .collect()
 }
diff --git a/tests/target/chains-no-overflow.rs b/tests/target/chains-no-overflow.rs
new file mode 100644
index 00000000000..b4fdb9f99ce
--- /dev/null
+++ b/tests/target/chains-no-overflow.rs
@@ -0,0 +1,45 @@
+// rustfmt-chains_overflow_last: false
+// Test chain formatting without overflowing the last item.
+
+fn main() {
+    bbbbbbbbbbbbbbbbbbb.ccccccccccccccccccccccccccccccccccccc.ddddddddddddddddddddddddddd();
+
+    bbbbbbbbbbbbbbbbbbb.ccccccccccccccccccccccccccccccccccccc
+                       .ddddddddddddddddddddddddddd
+                       .eeeeeeee();
+
+    x().y(|| {
+           match cond() {
+               true => (),
+               false => (),
+           }
+       });
+
+    loong_func()
+        .quux(move || {
+            if true {
+                1
+            } else {
+                2
+            }
+        });
+
+    fffffffffffffffffffffffffffffffffff(a,
+                                        {
+                                            SCRIPT_TASK_ROOT.with(|root| {
+                                                                // Another case of write_list failing us.
+                                                                *root.borrow_mut() = Some(&script_task);
+                                                            });
+                                        });
+
+    let suuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuum = xxxxxxx.map(|x| x + 5)
+                                                                          .map(|x| x / 2)
+                                                                          .fold(0,
+                                                                                |acc, x| acc + x);
+
+    aaaaaaaaaaaaaaaa.map(|x| {
+                        x += 1;
+                        x
+                    })
+                    .filter(some_mod::some_filter)
+}
diff --git a/tests/target/chains-no-overlow-2.rs b/tests/target/chains-no-overlow-2.rs
new file mode 100644
index 00000000000..d795fa2f961
--- /dev/null
+++ b/tests/target/chains-no-overlow-2.rs
@@ -0,0 +1,16 @@
+// rustfmt-chains_overflow_last: false
+
+fn main() {
+    reader.lines()
+          .map(|line| line.ok().expect("Failed getting line."))
+          .take_while(|line| line_regex.is_match(&line))
+          .filter_map(|line| {
+              regex.captures_iter(&line)
+                   .next()
+                   .map(|capture| {
+                       (capture.at(1).expect("Couldn\'t unwrap capture.").to_owned(),
+                        capture.at(2).expect("Couldn\'t unwrap capture.").to_owned())
+                   })
+          })
+          .collect();
+}
diff --git a/tests/target/chains.rs b/tests/target/chains.rs
index 282dc58b492..e49233e22a2 100644
--- a/tests/target/chains.rs
+++ b/tests/target/chains.rs
@@ -1,7 +1,11 @@
 // Test chain formatting.
 
 fn main() {
-    let a = b.c.d.1.foo(|x| x + 1);
+    // Don't put chains on a single list if it wasn't so in source.
+    let a = b.c
+             .d
+             .1
+             .foo(|x| x + 1);
 
     bbbbbbbbbbbbbbbbbbb.ccccccccccccccccccccccccccccccccccccc.ddddddddddddddddddddddddddd();
 
@@ -10,27 +14,25 @@ fn main() {
                        .eeeeeeee();
 
     x().y(|| {
-           match cond() {
-               true => (),
-               false => (),
-           }
-       });
-
-    loong_func()
-        .quux(move || {
-            if true {
-                1
-            } else {
-                2
-            }
-        });
+        match cond() {
+            true => (),
+            false => (),
+        }
+    });
+
+    loong_func().quux(move || {
+        if true {
+            1
+        } else {
+            2
+        }
+    });
 
     fffffffffffffffffffffffffffffffffff(a,
                                         {
                                             SCRIPT_TASK_ROOT.with(|root| {
-                                                                // Another case of write_list failing us.
-                                                                *root.borrow_mut() = Some(&script_task);
-                                                            });
+                                                *root.borrow_mut() = Some(&script_task);
+                                            });
                                         });
 
     let suuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuum = xxxxxxx.map(|x| x + 5)
diff --git a/tests/target/expr-no-hints.rs b/tests/target/expr-no-hints.rs
new file mode 100644
index 00000000000..62dd64c28a6
--- /dev/null
+++ b/tests/target/expr-no-hints.rs
@@ -0,0 +1,6 @@
+// rustfmt-take_source_hints: false
+// We know best!
+
+fn main() {
+    a.b.c.d();
+}
diff --git a/tests/target/expr-visual-indent.rs b/tests/target/expr-visual-indent.rs
deleted file mode 100644
index f5b79d74fcb..00000000000
--- a/tests/target/expr-visual-indent.rs
+++ /dev/null
@@ -1,9 +0,0 @@
-// rustfmt-expr_indent_style: Visual
-
-// Visual level block indentation.
-
-fn matcher() {
-    Some(while true {
-        test();
-    })
-}