]> git.lizzy.rs Git - rust.git/blobdiff - src/chains.rs
Merge pull request #2043 from sunjay/lift_generics
[rust.git] / src / chains.rs
index ed5278e81fc3f1bedf7af245f570c3f81c08e63c..20e0be36fe406d3013e61bdc5e0732abb36f3e3c 100644 (file)
 /// alignment).
 /// E.g., `let foo = { aaaa; bbb; ccc }.bar.baz();`, we would layout for the
 /// following values of `chain_indent`:
+/// Block:
+/// ```
+/// let foo = {
+///     aaaa;
+///     bbb;
+///     ccc
+/// }.bar
+///     .baz();
+/// ```
 /// Visual:
 /// ```
 /// let foo = {
 ///           .bar
 ///           .baz();
 /// ```
-/// Inherit:
-/// ```
-/// let foo = {
-///     aaaa;
-///     bbb;
-///     ccc
-/// }
-/// .bar
-/// .baz();
-/// ```
-/// Tabbed:
-/// ```
-/// let foo = {
-///         aaaa;
-///         bbb;
-///         ccc
-///     }
-///     .bar
-///     .baz();
-/// ```
 ///
 /// If the first item in the chain is a block expression, we align the dots with
 /// the braces.
-/// Visual:
-/// ```
-/// let a = foo.bar
-///            .baz()
-///            .qux
-/// ```
-/// Inherit:
+/// Block:
 /// ```
 /// let a = foo.bar
-/// .baz()
-/// .qux
+///     .baz()
+///     .qux
 /// ```
-/// Tabbed:
+/// Visual:
 /// ```
 /// let a = foo.bar
-///     .baz()
-///     .qux
+///            .baz()
+///            .qux
 /// ```
 
-use Shape;
-use rewrite::{Rewrite, RewriteContext};
-use utils::{wrap_str, first_line_width, last_line_width, mk_sp};
-use expr::rewrite_call;
+use shape::Shape;
 use config::IndentStyle;
+use expr::rewrite_call;
 use macros::convert_try_mac;
+use rewrite::{Rewrite, RewriteContext};
+use utils::{first_line_width, last_line_extendable, last_line_width, mk_sp,
+            trimmed_last_line_width, wrap_str};
 
 use std::cmp::min;
 use std::iter;
@@ -98,40 +82,29 @@ pub fn rewrite_chain(expr: &ast::Expr, context: &RewriteContext, shape: Shape) -
     if chain_only_try(&subexpr_list) {
         return rewrite_try(&parent, subexpr_list.len(), context, shape);
     }
-    let trailing_try_num = subexpr_list
-        .iter()
-        .take_while(|e| match e.node {
-            ast::ExprKind::Try(..) => true,
-            _ => false,
-        })
-        .count();
+    let suffix_try_num = subexpr_list.iter().take_while(|e| is_try(e)).count();
+    let prefix_try_num = subexpr_list.iter().rev().take_while(|e| is_try(e)).count();
 
     // Parent is the first item in the chain, e.g., `foo` in `foo.bar.baz()`.
     let parent_shape = if is_block_expr(context, &parent, "\n") {
         match context.config.chain_indent() {
             IndentStyle::Visual => shape.visual_indent(0),
-            IndentStyle::Block => shape.block(),
+            IndentStyle::Block => shape,
         }
     } else {
         shape
     };
-    let parent_rewrite = try_opt!(parent.rewrite(context, parent_shape));
+    let parent_rewrite = parent
+        .rewrite(context, parent_shape)
+        .map(|parent_rw| parent_rw + &repeat_try(prefix_try_num))?;
     let parent_rewrite_contains_newline = parent_rewrite.contains('\n');
     let is_small_parent = parent_rewrite.len() <= context.config.tab_spaces();
 
     // Decide how to layout the rest of the chain. `extend` is true if we can
     // put the first non-parent item on the same line as the parent.
-    let first_subexpr_is_try = subexpr_list.last().map_or(false, is_try);
     let (nested_shape, extend) = if !parent_rewrite_contains_newline && is_continuable(&parent) {
-        let nested_shape = if first_subexpr_is_try {
-            parent_shape
-                .block_indent(context.config.tab_spaces())
-                .with_max_width(context.config)
-        } else {
-            chain_indent(context, shape.add_offset(parent_rewrite.len()))
-        };
         (
-            nested_shape,
+            chain_indent(context, shape.add_offset(parent_rewrite.len())),
             context.config.chain_indent() == IndentStyle::Visual || is_small_parent,
         )
     } else if is_block_expr(context, &parent, &parent_rewrite) {
@@ -142,13 +115,9 @@ pub fn rewrite_chain(expr: &ast::Expr, context: &RewriteContext, shape: Shape) -
             // brace.
             IndentStyle::Visual => (parent_shape, false),
         }
-    } else if parent_rewrite_contains_newline {
-        (chain_indent(context, parent_shape), false)
     } else {
         (
-            shape
-                .block_indent(context.config.tab_spaces())
-                .with_max_width(context.config),
+            chain_indent(context, shape.add_offset(parent_rewrite.len())),
             false,
         )
     };
@@ -157,10 +126,10 @@ pub fn rewrite_chain(expr: &ast::Expr, context: &RewriteContext, shape: Shape) -
 
     let first_child_shape = if extend {
         let overhead = last_line_width(&parent_rewrite);
-        let offset = parent_rewrite.lines().rev().next().unwrap().trim().len();
+        let offset = trimmed_last_line_width(&parent_rewrite);
         match context.config.chain_indent() {
-            IndentStyle::Visual => try_opt!(parent_shape.offset_left(overhead)),
-            IndentStyle::Block => try_opt!(parent_shape.block().offset_left(offset)),
+            IndentStyle::Visual => parent_shape.offset_left(overhead)?,
+            IndentStyle::Block => parent_shape.block().offset_left(offset)?,
         }
     } else {
         other_child_shape
@@ -171,97 +140,107 @@ pub fn rewrite_chain(expr: &ast::Expr, context: &RewriteContext, shape: Shape) -
         other_child_shape
     );
 
-    let child_shape_iter = Some(first_child_shape).into_iter().chain(
-        ::std::iter::repeat(other_child_shape).take(subexpr_list.len() - 1),
-    );
-    let iter = subexpr_list.iter().rev().zip(child_shape_iter);
-    let mut rewrites = try_opt!(
-        iter.map(|(e, shape)| {
-            rewrite_chain_subexpr(e, total_span, context, shape)
-        }).collect::<Option<Vec<_>>>()
-    );
+    let child_shape_iter = Some(first_child_shape)
+        .into_iter()
+        .chain(iter::repeat(other_child_shape));
+    let subexpr_num = subexpr_list.len();
+    let last_subexpr = &subexpr_list[suffix_try_num];
+    let subexpr_list = &subexpr_list[suffix_try_num..subexpr_num - prefix_try_num];
+    let iter = subexpr_list.iter().skip(1).rev().zip(child_shape_iter);
+    let mut rewrites = iter.map(|(e, shape)| {
+        rewrite_chain_subexpr(e, total_span, context, shape)
+    }).collect::<Option<Vec<_>>>()?;
 
     // Total of all items excluding the last.
-    let last_non_try_index = rewrites.len() - (1 + trailing_try_num);
-    let almost_total = rewrites[..last_non_try_index].iter().fold(
-        0,
-        |a, b| a + first_line_width(b),
-    ) + parent_rewrite.len();
-    let one_line_len = rewrites.iter().fold(0, |a, r| a + first_line_width(r)) +
-        parent_rewrite.len();
-
-    let one_line_budget = min(shape.width, context.config.chain_one_line_max());
-    let veto_single_line = if one_line_len > one_line_budget {
-        if rewrites.len() > 1 {
-            true
-        } else if rewrites.len() == 1 {
-            context.config.chain_split_single_child() || one_line_len > shape.width
-        } else {
-            false
-        }
-    } else 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 last_span = context.snippet(mk_sp(subexpr_list[1].span.hi, total_span.hi));
-        let first_span = context.snippet(subexpr_list[1].span);
-        let last_iter = last_span.chars().take_while(|c| c.is_whitespace());
-
-        first_span.chars().chain(last_iter).any(|c| c == '\n')
+    let extend_last_subexr = last_line_extendable(&parent_rewrite) && rewrites.is_empty();
+    let almost_total = if extend_last_subexr {
+        last_line_width(&parent_rewrite)
     } else {
-        false
+        rewrites.iter().fold(0, |a, b| a + b.len()) + parent_rewrite.len()
+    } + suffix_try_num;
+    let one_line_budget = if rewrites.is_empty() && !context.config.chain_split_single_child() {
+        shape.width
+    } else {
+        min(shape.width, context.config.chain_one_line_max())
     };
-
-    let mut fits_single_line = !veto_single_line && almost_total <= shape.width;
-    if fits_single_line {
-        let len = rewrites.len();
-        let (init, last) = rewrites.split_at_mut(len - (1 + trailing_try_num));
-        fits_single_line = init.iter().all(|s| !s.contains('\n'));
-
-        if fits_single_line {
-            fits_single_line = match expr.node {
-                ref e @ ast::ExprKind::MethodCall(..) => {
-                    if rewrite_method_call_with_overflow(
-                        e,
-                        &mut last[0],
-                        almost_total,
-                        total_span,
-                        context,
-                        shape,
-                    )
-                    {
-                        // If the first line of the last method does not fit into a single line
-                        // after the others, allow new lines.
-                        almost_total + first_line_width(&last[0]) < context.config.max_width()
-                    } else {
-                        false
+    let all_in_one_line = !parent_rewrite_contains_newline
+        && rewrites.iter().all(|s| !s.contains('\n'))
+        && almost_total < one_line_budget;
+    let last_shape = match context.config.chain_indent() {
+        IndentStyle::Visual => other_child_shape.sub_width(shape.rhs_overhead(context.config))?,
+        IndentStyle::Block => other_child_shape,
+    };
+    let last_shape = last_shape.sub_width(suffix_try_num)?;
+
+    // Rewrite the last child. The last child of a chain requires special treatment. We need to
+    // know whether 'overflowing' the last child make a better formatting:
+    //
+    // A chain with overflowing the last child:
+    // ```
+    // parent.child1.child2.last_child(
+    //     a,
+    //     b,
+    //     c,
+    // )
+    // ```
+    //
+    // A chain without overflowing the last child (in vertical layout):
+    // ```
+    // parent
+    //     .child1
+    //     .child2
+    //     .last_child(a, b, c)
+    // ```
+    //
+    // In particular, overflowing is effective when the last child is a method with a multi-lined
+    // block-like argument (e.g. closure):
+    // ```
+    // parent.child1.chlid2.last_child(|a, b, c| {
+    //     let x = foo(a, b, c);
+    //     let y = bar(a, b, c);
+    //
+    //     // ...
+    //
+    //     result
+    // })
+    // ```
+
+    // `rewrite_last` rewrites the last child on its own line. We use a closure here instead of
+    // directly calling `rewrite_chain_subexpr()` to avoid exponential blowup.
+    let rewrite_last = || rewrite_chain_subexpr(last_subexpr, total_span, context, last_shape);
+    let (last_subexpr_str, fits_single_line) = if all_in_one_line || extend_last_subexr {
+        // First we try to 'overflow' the last child and see if it looks better than using
+        // vertical layout.
+        parent_shape.offset_left(almost_total).map(|shape| {
+            if let Some(rw) = rewrite_chain_subexpr(last_subexpr, total_span, context, shape) {
+                // We allow overflowing here only if both of the following conditions match:
+                // 1. The entire chain fits in a single line expect the last child.
+                // 2. `last_chlid_str.lines().count() >= 5`.
+                let line_count = rw.lines().count();
+                let fits_single_line = almost_total + first_line_width(&rw) <= one_line_budget;
+                if fits_single_line && line_count >= 5 {
+                    (Some(rw), true)
+                } else {
+                    // We could not know whether overflowing is better than using vertical layout,
+                    // just by looking at the overflowed rewrite. Now we rewrite the last child
+                    // on its own line, and compare two rewrites to choose which is better.
+                    match rewrite_last() {
+                        Some(ref new_rw) if !fits_single_line => (Some(new_rw.clone()), false),
+                        Some(ref new_rw) if new_rw.lines().count() >= line_count => {
+                            (Some(rw), fits_single_line)
+                        }
+                        new_rw @ Some(..) => (new_rw, false),
+                        _ => (Some(rw), fits_single_line),
                     }
                 }
-                _ => !last[0].contains('\n'),
+            } else {
+                (rewrite_last(), false)
             }
-        }
-    }
-
-    // Try overflowing the last element if we are using block indent and it goes multi line
-    // or it fits in a single line but goes over the max width.
-    if !fits_single_line && context.use_block_indent() {
-        let (init, last) = rewrites.split_at_mut(last_non_try_index);
-        let almost_single_line = init.iter().all(|s| !s.contains('\n'));
-        if almost_single_line && last[0].contains('\n') {
-            let overflow_shape = Shape {
-                width: one_line_budget,
-                ..parent_shape
-            };
-            fits_single_line = rewrite_last_child_with_overflow(
-                context,
-                &subexpr_list[trailing_try_num],
-                overflow_shape,
-                total_span,
-                almost_total,
-                one_line_budget,
-                &mut last[0],
-            );
-        }
-    }
+        })?
+    } else {
+        (rewrite_last(), false)
+    };
+    rewrites.push(last_subexpr_str?);
 
     let connector = if fits_single_line && !parent_rewrite_contains_newline {
         // Yay, we can put everything on one line.
@@ -274,63 +253,48 @@ pub fn rewrite_chain(expr: &ast::Expr, context: &RewriteContext, shape: Shape) -
         format!("\n{}", nested_shape.indent.to_string(context.config))
     };
 
-    let first_connector = choose_first_connector(
-        context,
-        &parent_rewrite,
-        &rewrites[0],
-        &connector,
-        &subexpr_list,
-        extend,
-    );
+    let first_connector = if is_small_parent || fits_single_line
+        || last_line_extendable(&parent_rewrite)
+        || context.config.chain_indent() == IndentStyle::Visual
+    {
+        ""
+    } else {
+        connector.as_str()
+    };
 
-    if is_small_parent && rewrites.len() > 1 {
-        let second_connector = choose_first_connector(
-            context,
-            &rewrites[0],
-            &rewrites[1],
-            &connector,
-            &subexpr_list[0..subexpr_list.len() - 1],
-            false,
-        );
-        wrap_str(
-            format!(
-                "{}{}{}{}{}",
-                parent_rewrite,
-                first_connector,
-                rewrites[0],
-                second_connector,
-                join_rewrites(
-                    &rewrites[1..],
-                    &subexpr_list[0..subexpr_list.len() - 1],
-                    &connector,
-                )
-            ),
-            context.config.max_width(),
-            shape,
+    let result = if is_small_parent && rewrites.len() > 1 {
+        let second_connector = if fits_single_line || rewrites[1] == "?"
+            || last_line_extendable(&rewrites[0])
+            || context.config.chain_indent() == IndentStyle::Visual
+        {
+            ""
+        } else {
+            &connector
+        };
+        format!(
+            "{}{}{}{}{}",
+            parent_rewrite,
+            first_connector,
+            rewrites[0],
+            second_connector,
+            join_rewrites(&rewrites[1..], &connector)
         )
     } else {
-        wrap_str(
-            format!(
-                "{}{}{}",
-                parent_rewrite,
-                first_connector,
-                join_rewrites(&rewrites, &subexpr_list, &connector)
-            ),
-            context.config.max_width(),
-            shape,
+        format!(
+            "{}{}{}",
+            parent_rewrite,
+            first_connector,
+            join_rewrites(&rewrites, &connector)
         )
+    };
+    let result = format!("{}{}", result, repeat_try(suffix_try_num));
+    if context.config.chain_indent() == IndentStyle::Visual {
+        wrap_str(result, context.config.max_width(), shape)
+    } else {
+        Some(result)
     }
 }
 
-fn is_extendable_parent(context: &RewriteContext, parent_str: &str) -> bool {
-    context.config.chain_indent() == IndentStyle::Block &&
-        parent_str.lines().last().map_or(false, |s| {
-            s.trim()
-                .chars()
-                .all(|c| c == ')' || c == ']' || c == '}' || c == '?')
-        })
-}
-
 // True if the chain is only `?`s.
 fn chain_only_try(exprs: &[ast::Expr]) -> bool {
     exprs.iter().all(|e| if let ast::ExprKind::Try(_) = e.node {
@@ -342,51 +306,28 @@ fn chain_only_try(exprs: &[ast::Expr]) -> bool {
 
 // Try to rewrite and replace the last non-try child. Return `true` if
 // replacing succeeds.
-fn rewrite_last_child_with_overflow(
-    context: &RewriteContext,
-    expr: &ast::Expr,
-    shape: Shape,
-    span: Span,
-    almost_total: usize,
-    one_line_budget: usize,
-    last_child: &mut String,
-) -> bool {
-    if let Some(shape) = shape.shrink_left(almost_total) {
-        if let Some(ref mut rw) = rewrite_chain_subexpr(expr, span, context, shape) {
-            if almost_total + first_line_width(rw) <= one_line_budget && rw.lines().count() > 3 {
-                ::std::mem::swap(last_child, rw);
-                return true;
-            }
-        }
-    }
-    false
+fn repeat_try(try_count: usize) -> String {
+    iter::repeat("?").take(try_count).collect::<String>()
 }
 
-pub fn rewrite_try(
+fn rewrite_try(
     expr: &ast::Expr,
     try_count: usize,
     context: &RewriteContext,
     shape: Shape,
 ) -> Option<String> {
-    let sub_expr = try_opt!(expr.rewrite(context, try_opt!(shape.sub_width(try_count))));
-    Some(format!(
-        "{}{}",
-        sub_expr,
-        iter::repeat("?").take(try_count).collect::<String>()
-    ))
+    let sub_expr = expr.rewrite(context, shape.sub_width(try_count)?)?;
+    Some(format!("{}{}", sub_expr, repeat_try(try_count)))
 }
 
-fn join_rewrites(rewrites: &[String], subexps: &[ast::Expr], connector: &str) -> String {
+fn join_rewrites(rewrites: &[String], connector: &str) -> String {
     let mut rewrite_iter = rewrites.iter();
     let mut result = rewrite_iter.next().unwrap().clone();
-    let mut subexpr_iter = subexps.iter().rev();
-    subexpr_iter.next();
 
-    for (rewrite, expr) in rewrite_iter.zip(subexpr_iter) {
-        match expr.node {
-            ast::ExprKind::Try(_) => (),
-            _ => result.push_str(connector),
-        };
+    for rewrite in rewrite_iter {
+        if rewrite != "?" {
+            result.push_str(connector);
+        }
         result.push_str(&rewrite[..]);
     }
 
@@ -397,8 +338,9 @@ fn join_rewrites(rewrites: &[String], subexps: &[ast::Expr], connector: &str) ->
 // parens, braces, and brackets in its idiomatic formatting.
 fn is_block_expr(context: &RewriteContext, expr: &ast::Expr, repr: &str) -> bool {
     match expr.node {
-        ast::ExprKind::Mac(..) |
-        ast::ExprKind::Call(..) => context.use_block_indent() && repr.contains('\n'),
+        ast::ExprKind::Mac(..) | ast::ExprKind::Call(..) => {
+            context.use_block_indent() && repr.contains('\n')
+        }
         ast::ExprKind::Struct(..) |
         ast::ExprKind::While(..) |
         ast::ExprKind::WhileLet(..) |
@@ -432,40 +374,9 @@ fn make_subexpr_list(expr: &ast::Expr, context: &RewriteContext) -> (ast::Expr,
 fn chain_indent(context: &RewriteContext, shape: Shape) -> Shape {
     match context.config.chain_indent() {
         IndentStyle::Visual => shape.visual_indent(0),
-        IndentStyle::Block => shape.block_indent(context.config.tab_spaces()),
-    }
-}
-
-fn rewrite_method_call_with_overflow(
-    expr_kind: &ast::ExprKind,
-    last: &mut String,
-    almost_total: usize,
-    total_span: Span,
-    context: &RewriteContext,
-    shape: Shape,
-) -> bool {
-    if let &ast::ExprKind::MethodCall(ref method_name, ref types, ref expressions) = expr_kind {
-        let shape = match shape.shrink_left(almost_total) {
-            Some(b) => b,
-            None => return false,
-        };
-        let mut last_rewrite = rewrite_method_call(
-            method_name.node,
-            types,
-            expressions,
-            total_span,
-            context,
-            shape,
-        );
-
-        if let Some(ref mut s) = last_rewrite {
-            ::std::mem::swap(s, last);
-            true
-        } else {
-            false
-        }
-    } else {
-        unreachable!();
+        IndentStyle::Block => shape
+            .block_indent(context.config.tab_spaces())
+            .with_max_width(context.config),
     }
 }
 
@@ -473,7 +384,7 @@ fn rewrite_method_call_with_overflow(
 // is a try! macro, we'll convert it to shorthand when the option is set.
 fn pop_expr_chain(expr: &ast::Expr, context: &RewriteContext) -> Option<ast::Expr> {
     match expr.node {
-        ast::ExprKind::MethodCall(_, _, ref expressions) => {
+        ast::ExprKind::MethodCall(_, ref expressions) => {
             Some(convert_try(&expressions[0], context))
         }
         ast::ExprKind::TupField(ref subexpr, _) |
@@ -511,8 +422,15 @@ fn rewrite_chain_subexpr(
     };
 
     match expr.node {
-        ast::ExprKind::MethodCall(ref method_name, ref types, ref expressions) => {
-            rewrite_method_call(method_name.node, types, expressions, span, context, shape)
+        ast::ExprKind::MethodCall(ref segment, ref expressions) => {
+            let types = match segment.parameters {
+                Some(ref params) => match **params {
+                    ast::PathParameters::AngleBracketed(ref data) => &data.types[..],
+                    _ => &[],
+                },
+                _ => &[],
+            };
+            rewrite_method_call(segment.identifier, types, expressions, span, context, shape)
         }
         ast::ExprKind::Field(_, ref field) => rewrite_element(format!(".{}", field.node)),
         ast::ExprKind::TupField(ref expr, ref field) => {
@@ -542,32 +460,6 @@ fn is_try(expr: &ast::Expr) -> bool {
     }
 }
 
-fn choose_first_connector<'a>(
-    context: &RewriteContext,
-    parent_str: &str,
-    first_child_str: &str,
-    connector: &'a str,
-    subexpr_list: &[ast::Expr],
-    extend: bool,
-) -> &'a str {
-    if subexpr_list.is_empty() {
-        ""
-    } else if extend || subexpr_list.last().map_or(false, is_try) ||
-               is_extendable_parent(context, parent_str)
-    {
-        // 1 = ";", being conservative here.
-        if last_line_width(parent_str) + first_line_width(first_child_str) + 1 <=
-            context.config.max_width()
-        {
-            ""
-        } else {
-            connector
-        }
-    } else {
-        connector
-    }
-}
-
 fn rewrite_method_call(
     method_name: ast::Ident,
     types: &[ptr::P<ast::Ty>],
@@ -577,22 +469,24 @@ fn rewrite_method_call(
     shape: Shape,
 ) -> Option<String> {
     let (lo, type_str) = if types.is_empty() {
-        (args[0].span.hi, String::new())
+        (args[0].span.hi(), String::new())
     } else {
-        let type_list: Vec<_> =
-            try_opt!(types.iter().map(|ty| ty.rewrite(context, shape)).collect());
+        let type_list = types
+            .iter()
+            .map(|ty| ty.rewrite(context, shape))
+            .collect::<Option<Vec<_>>>()?;
 
-        let type_str = if context.config.spaces_within_angle_brackets() && type_list.len() > 0 {
+        let type_str = if context.config.spaces_within_angle_brackets() && !type_list.is_empty() {
             format!("::< {} >", type_list.join(", "))
         } else {
             format!("::<{}>", type_list.join(", "))
         };
 
-        (types.last().unwrap().span.hi, type_str)
+        (types.last().unwrap().span.hi(), type_str)
     };
 
     let callee_str = format!(".{}{}", method_name, type_str);
-    let span = mk_sp(lo, span.hi);
+    let span = mk_sp(lo, span.hi());
 
     rewrite_call(context, &callee_str, &args[1..], span, shape)
 }