]> git.lizzy.rs Git - rust.git/blobdiff - src/overflow.rs
Merge pull request #3266 from wada314/fix-2973
[rust.git] / src / overflow.rs
index 54e594d814f8a210d4beac2f518ca3093f3b3778..4a583fc1de838773582e434dbe6b292cb1e3ffcd 100644 (file)
 // except according to those terms.
 
 //! Rewrite a list some items with overflow.
-// FIXME: Replace `ToExpr` with some enum.
 
 use config::lists::*;
-use syntax::ast;
-use syntax::codemap::Span;
 use syntax::parse::token::DelimToken;
+use syntax::source_map::Span;
+use syntax::{ast, ptr};
 
 use closures;
-use codemap::SpanUtils;
-use expr::{is_every_expr_simple, is_method_call, is_nested_call, maybe_get_args_offset, ToExpr};
+use expr::{
+    can_be_overflowed_expr, is_every_expr_simple, is_method_call, is_nested_call, is_simple_expr,
+    rewrite_cond,
+};
 use lists::{definitive_tactic, itemize_list, write_list, ListFormatting, ListItem, Separator};
+use macros::MacroArg;
+use patterns::{can_be_overflowed_pat, TuplePatField};
 use rewrite::{Rewrite, RewriteContext};
 use shape::Shape;
+use source_map::SpanUtils;
 use spanned::Spanned;
+use types::{can_be_overflowed_type, SegmentParam};
 use utils::{count_newlines, extra_offset, first_line_width, last_line_width, mk_sp};
 
 use std::cmp::min;
 
 const SHORT_ITEM_THRESHOLD: usize = 10;
 
-pub fn rewrite_with_parens<T>(
-    context: &RewriteContext,
-    ident: &str,
-    items: &[&T],
+/// A list of `format!`-like macros, that take a long format string and a list of arguments to
+/// format.
+///
+/// Organized as a list of `(&str, usize)` tuples, giving the name of the macro and the number of
+/// arguments before the format string (none for `format!("format", ...)`, one for `assert!(result,
+/// "format", ...)`, two for `assert_eq!(left, right, "format", ...)`).
+const SPECIAL_MACRO_WHITELIST: &[(&str, usize)] = &[
+    // format! like macros
+    // From the Rust Standard Library.
+    ("eprint!", 0),
+    ("eprintln!", 0),
+    ("format!", 0),
+    ("format_args!", 0),
+    ("print!", 0),
+    ("println!", 0),
+    ("panic!", 0),
+    ("unreachable!", 0),
+    // From the `log` crate.
+    ("debug!", 0),
+    ("error!", 0),
+    ("info!", 0),
+    ("warn!", 0),
+    // write! like macros
+    ("assert!", 1),
+    ("debug_assert!", 1),
+    ("write!", 1),
+    ("writeln!", 1),
+    // assert_eq! like macros
+    ("assert_eq!", 2),
+    ("assert_ne!", 2),
+    ("debug_assert_eq!", 2),
+    ("debug_assert_ne!", 2),
+];
+
+const SPECIAL_ATTR_WHITELIST: &[(&str, usize)] = &[
+    // From the `failure` crate.
+    ("fail", 0),
+];
+
+#[derive(Debug)]
+pub enum OverflowableItem<'a> {
+    Expr(&'a ast::Expr),
+    GenericParam(&'a ast::GenericParam),
+    MacroArg(&'a MacroArg),
+    NestedMetaItem(&'a ast::NestedMetaItem),
+    SegmentParam(&'a SegmentParam<'a>),
+    StructField(&'a ast::StructField),
+    TuplePatField(&'a TuplePatField<'a>),
+    Ty(&'a ast::Ty),
+}
+
+impl<'a> Rewrite for OverflowableItem<'a> {
+    fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> {
+        self.map(|item| item.rewrite(context, shape))
+    }
+}
+
+impl<'a> Spanned for OverflowableItem<'a> {
+    fn span(&self) -> Span {
+        self.map(|item| item.span())
+    }
+}
+
+impl<'a> OverflowableItem<'a> {
+    pub fn map<F, T>(&self, f: F) -> T
+    where
+        F: Fn(&IntoOverflowableItem<'a>) -> T,
+    {
+        match self {
+            OverflowableItem::Expr(expr) => f(*expr),
+            OverflowableItem::GenericParam(gp) => f(*gp),
+            OverflowableItem::MacroArg(macro_arg) => f(*macro_arg),
+            OverflowableItem::NestedMetaItem(nmi) => f(*nmi),
+            OverflowableItem::SegmentParam(sp) => f(*sp),
+            OverflowableItem::StructField(sf) => f(*sf),
+            OverflowableItem::TuplePatField(pat) => f(*pat),
+            OverflowableItem::Ty(ty) => f(*ty),
+        }
+    }
+
+    pub fn is_simple(&self) -> bool {
+        match self {
+            OverflowableItem::Expr(expr) => is_simple_expr(expr),
+            OverflowableItem::MacroArg(MacroArg::Expr(expr)) => is_simple_expr(expr),
+            OverflowableItem::NestedMetaItem(nested_meta_item) => match nested_meta_item.node {
+                ast::NestedMetaItemKind::Literal(..) => true,
+                ast::NestedMetaItemKind::MetaItem(ref meta_item) => match meta_item.node {
+                    ast::MetaItemKind::Word => true,
+                    _ => false,
+                },
+            },
+            _ => false,
+        }
+    }
+
+    pub fn is_expr(&self) -> bool {
+        match self {
+            OverflowableItem::Expr(..) => true,
+            OverflowableItem::MacroArg(MacroArg::Expr(..)) => true,
+            _ => false,
+        }
+    }
+
+    pub fn is_nested_call(&self) -> bool {
+        match self {
+            OverflowableItem::Expr(expr) => is_nested_call(expr),
+            OverflowableItem::MacroArg(MacroArg::Expr(expr)) => is_nested_call(expr),
+            _ => false,
+        }
+    }
+
+    pub fn to_expr(&self) -> Option<&'a ast::Expr> {
+        match self {
+            OverflowableItem::Expr(expr) => Some(expr),
+            OverflowableItem::MacroArg(macro_arg) => match macro_arg {
+                MacroArg::Expr(ref expr) => Some(expr),
+                _ => None,
+            },
+            _ => None,
+        }
+    }
+
+    pub fn can_be_overflowed(&self, context: &RewriteContext, len: usize) -> bool {
+        match self {
+            OverflowableItem::Expr(expr) => can_be_overflowed_expr(context, expr, len),
+            OverflowableItem::MacroArg(macro_arg) => match macro_arg {
+                MacroArg::Expr(ref expr) => can_be_overflowed_expr(context, expr, len),
+                MacroArg::Ty(ref ty) => can_be_overflowed_type(context, ty, len),
+                MacroArg::Pat(..) => false,
+                MacroArg::Item(..) => len == 1,
+            },
+            OverflowableItem::NestedMetaItem(nested_meta_item) if len == 1 => {
+                match nested_meta_item.node {
+                    ast::NestedMetaItemKind::Literal(..) => false,
+                    ast::NestedMetaItemKind::MetaItem(..) => true,
+                }
+            }
+            OverflowableItem::SegmentParam(seg) => match seg {
+                SegmentParam::Type(ty) => can_be_overflowed_type(context, ty, len),
+                _ => false,
+            },
+            OverflowableItem::TuplePatField(pat) => can_be_overflowed_pat(context, pat, len),
+            OverflowableItem::Ty(ty) => can_be_overflowed_type(context, ty, len),
+            _ => false,
+        }
+    }
+
+    fn whitelist(&self) -> &'static [(&'static str, usize)] {
+        match self {
+            OverflowableItem::MacroArg(..) => SPECIAL_MACRO_WHITELIST,
+            OverflowableItem::NestedMetaItem(..) => SPECIAL_ATTR_WHITELIST,
+            _ => &[],
+        }
+    }
+}
+
+pub trait IntoOverflowableItem<'a>: Rewrite + Spanned {
+    fn into_overflowable_item(&'a self) -> OverflowableItem<'a>;
+}
+
+impl<'a, T: 'a + IntoOverflowableItem<'a>> IntoOverflowableItem<'a> for ptr::P<T> {
+    fn into_overflowable_item(&'a self) -> OverflowableItem<'a> {
+        (**self).into_overflowable_item()
+    }
+}
+
+macro_rules! impl_into_overflowable_item_for_ast_node {
+    ($($ast_node:ident),*) => {
+        $(
+            impl<'a> IntoOverflowableItem<'a> for ast::$ast_node {
+                fn into_overflowable_item(&'a self) -> OverflowableItem<'a> {
+                    OverflowableItem::$ast_node(self)
+                }
+            }
+        )*
+    }
+}
+
+macro_rules! impl_into_overflowable_item_for_rustfmt_types {
+    ([$($ty:ident),*], [$($ty_with_lifetime:ident),*]) => {
+        $(
+            impl<'a> IntoOverflowableItem<'a> for $ty {
+                fn into_overflowable_item(&'a self) -> OverflowableItem<'a> {
+                    OverflowableItem::$ty(self)
+                }
+            }
+        )*
+        $(
+            impl<'a> IntoOverflowableItem<'a> for $ty_with_lifetime<'a> {
+                fn into_overflowable_item(&'a self) -> OverflowableItem<'a> {
+                    OverflowableItem::$ty_with_lifetime(self)
+                }
+            }
+        )*
+    }
+}
+
+impl_into_overflowable_item_for_ast_node!(Expr, GenericParam, NestedMetaItem, StructField, Ty);
+impl_into_overflowable_item_for_rustfmt_types!([MacroArg], [SegmentParam, TuplePatField]);
+
+pub fn into_overflowable_list<'a, T>(
+    iter: impl Iterator<Item = &'a T>,
+) -> impl Iterator<Item = OverflowableItem<'a>>
+where
+    T: 'a + IntoOverflowableItem<'a>,
+{
+    iter.map(|x| IntoOverflowableItem::into_overflowable_item(x))
+}
+
+pub fn rewrite_with_parens<'a, T: 'a + IntoOverflowableItem<'a>>(
+    context: &'a RewriteContext,
+    ident: &'a str,
+    items: impl Iterator<Item = &'a T>,
     shape: Shape,
     span: Span,
     item_max_width: usize,
     force_separator_tactic: Option<SeparatorTactic>,
-) -> Option<String>
-where
-    T: Rewrite + ToExpr + Spanned,
-{
+) -> Option<String> {
     Context::new(
         context,
         items,
@@ -52,19 +263,17 @@ pub fn rewrite_with_parens<T>(
         item_max_width,
         force_separator_tactic,
         None,
-    ).rewrite(shape)
+    )
+    .rewrite(shape)
 }
 
-pub fn rewrite_with_angle_brackets<T>(
-    context: &RewriteContext,
-    ident: &str,
-    items: &[&T],
+pub fn rewrite_with_angle_brackets<'a, T: 'a + IntoOverflowableItem<'a>>(
+    context: &'a RewriteContext,
+    ident: &'a str,
+    items: impl Iterator<Item = &'a T>,
     shape: Shape,
     span: Span,
-) -> Option<String>
-where
-    T: Rewrite + ToExpr + Spanned,
-{
+) -> Option<String> {
     Context::new(
         context,
         items,
@@ -76,21 +285,19 @@ pub fn rewrite_with_angle_brackets<T>(
         context.config.max_width(),
         None,
         None,
-    ).rewrite(shape)
+    )
+    .rewrite(shape)
 }
 
-pub fn rewrite_with_square_brackets<T>(
-    context: &RewriteContext,
-    name: &str,
-    items: &[&T],
+pub fn rewrite_with_square_brackets<'a, T: 'a + IntoOverflowableItem<'a>>(
+    context: &'a RewriteContext,
+    name: &'a str,
+    items: impl Iterator<Item = &'a T>,
     shape: Shape,
     span: Span,
     force_separator_tactic: Option<SeparatorTactic>,
     delim_token: Option<DelimToken>,
-) -> Option<String>
-where
-    T: Rewrite + ToExpr + Spanned,
-{
+) -> Option<String> {
     let (lhs, rhs) = match delim_token {
         Some(DelimToken::Paren) => ("(", ")"),
         Some(DelimToken::Brace) => ("{", "}"),
@@ -107,12 +314,13 @@ pub fn rewrite_with_square_brackets<T>(
         context.config.width_heuristics().array_width,
         force_separator_tactic,
         Some(("[", "]")),
-    ).rewrite(shape)
+    )
+    .rewrite(shape)
 }
 
-struct Context<'a, T: 'a> {
+struct Context<'a> {
     context: &'a RewriteContext<'a>,
-    items: &'a [&'a T],
+    items: Vec<OverflowableItem<'a>>,
     ident: &'a str,
     prefix: &'static str,
     suffix: &'static str,
@@ -125,10 +333,10 @@ struct Context<'a, T: 'a> {
     custom_delims: Option<(&'a str, &'a str)>,
 }
 
-impl<'a, T: 'a + Rewrite + ToExpr + Spanned> Context<'a, T> {
-    pub fn new(
+impl<'a> Context<'a> {
+    pub fn new<T: 'a + IntoOverflowableItem<'a>>(
         context: &'a RewriteContext,
-        items: &'a [&'a T],
+        items: impl Iterator<Item = &'a T>,
         ident: &'a str,
         shape: Shape,
         span: Span,
@@ -137,7 +345,7 @@ pub fn new(
         item_max_width: usize,
         force_separator_tactic: Option<SeparatorTactic>,
         custom_delims: Option<(&'a str, &'a str)>,
-    ) -> Context<'a, T> {
+    ) -> Context<'a> {
         let used_width = extra_offset(ident, shape);
         // 1 = `()`
         let one_line_width = shape.width.saturating_sub(used_width + 2);
@@ -150,7 +358,7 @@ pub fn new(
         let nested_shape = shape_from_indent_style(context, shape, used_width + 2, used_width + 1);
         Context {
             context,
-            items,
+            items: into_overflowable_list(items).collect(),
             ident,
             one_line_shape,
             nested_shape,
@@ -164,7 +372,7 @@ pub fn new(
         }
     }
 
-    fn last_item(&self) -> Option<&&T> {
+    fn last_item(&self) -> Option<&OverflowableItem> {
         self.items.last()
     }
 
@@ -182,23 +390,44 @@ fn rewrite_last_item_with_overflow(
         shape: Shape,
     ) -> Option<String> {
         let last_item = self.last_item()?;
-        let rewrite = if let Some(expr) = last_item.to_expr() {
-            match expr.node {
-                // When overflowing the closure which consists of a single control flow expression,
-                // force to use block if its condition uses multi line.
-                ast::ExprKind::Closure(..) => {
-                    // If the argument consists of multiple closures, we do not overflow
-                    // the last closure.
-                    if closures::args_have_many_closure(self.items) {
-                        None
-                    } else {
-                        closures::rewrite_last_closure(self.context, expr, shape)
+        let rewrite = match last_item {
+            OverflowableItem::Expr(ref expr) => {
+                match expr.node {
+                    // When overflowing the closure which consists of a single control flow
+                    // expression, force to use block if its condition uses multi line.
+                    ast::ExprKind::Closure(..) => {
+                        // If the argument consists of multiple closures, we do not overflow
+                        // the last closure.
+                        if closures::args_have_many_closure(&self.items) {
+                            None
+                        } else {
+                            closures::rewrite_last_closure(self.context, expr, shape)
+                        }
+                    }
+
+                    // When overflowing the expressions which consists of a control flow
+                    // expression, avoid condition to use multi line.
+                    ast::ExprKind::If(..)
+                    | ast::ExprKind::IfLet(..)
+                    | ast::ExprKind::ForLoop(..)
+                    | ast::ExprKind::Loop(..)
+                    | ast::ExprKind::While(..)
+                    | ast::ExprKind::WhileLet(..)
+                    | ast::ExprKind::Match(..) => {
+                        let multi_line = rewrite_cond(self.context, expr, shape)
+                            .map_or(false, |cond| cond.contains('\n'));
+
+                        if multi_line {
+                            None
+                        } else {
+                            expr.rewrite(self.context, shape)
+                        }
                     }
+
+                    _ => expr.rewrite(self.context, shape),
                 }
-                _ => expr.rewrite(self.context, shape),
             }
-        } else {
-            last_item.rewrite(self.context, shape)
+            item => item.rewrite(self.context, shape),
         };
 
         if let Some(rewrite) = rewrite {
@@ -222,27 +451,29 @@ fn default_tactic(&self, list_items: &[ListItem]) -> DefinitiveListTactic {
     fn try_overflow_last_item(&self, list_items: &mut Vec<ListItem>) -> DefinitiveListTactic {
         // 1 = "("
         let combine_arg_with_callee = self.items.len() == 1
-            && self.items[0].to_expr().is_some()
-            && self.ident.len() + 1 <= self.context.config.tab_spaces();
-        let overflow_last = combine_arg_with_callee || can_be_overflowed(self.context, self.items);
+            && self.items[0].is_expr()
+            && self.ident.len() < self.context.config.tab_spaces();
+        let overflow_last = combine_arg_with_callee || can_be_overflowed(self.context, &self.items);
 
         // Replace the last item with its first line to see if it fits with
         // first arguments.
         let placeholder = if overflow_last {
             let old_value = *self.context.force_one_line_chain.borrow();
-            if !combine_arg_with_callee {
-                if let Some(ref expr) = self.last_item().and_then(|item| item.to_expr()) {
-                    if is_method_call(expr) {
-                        self.context.force_one_line_chain.replace(true);
-                    }
+            match self.last_item() {
+                Some(OverflowableItem::Expr(expr))
+                    if !combine_arg_with_callee && is_method_call(expr) =>
+                {
+                    self.context.force_one_line_chain.replace(true);
                 }
+                _ => (),
             }
             let result = last_item_shape(
-                self.items,
+                &self.items,
                 list_items,
                 self.one_line_shape,
                 self.item_max_width,
-            ).and_then(|arg_shape| {
+            )
+            .and_then(|arg_shape| {
                 self.rewrite_last_item_with_overflow(
                     &mut list_items[self.items.len() - 1],
                     arg_shape,
@@ -291,7 +522,7 @@ fn try_overflow_last_item(&self, list_items: &mut Vec<ListItem>) -> DefinitiveLi
             (true, DefinitiveListTactic::Horizontal, placeholder @ Some(..)) => {
                 list_items[self.items.len() - 1].item = placeholder;
             }
-            _ if self.items.len() >= 1 => {
+            _ if !self.items.is_empty() => {
                 list_items[self.items.len() - 1].item = self
                     .items
                     .last()
@@ -312,7 +543,7 @@ fn try_overflow_last_item(&self, list_items: &mut Vec<ListItem>) -> DefinitiveLi
 
                     if tactic == DefinitiveListTactic::Vertical {
                         if let Some((all_simple, num_args_before)) =
-                            maybe_get_args_offset(self.ident, self.items)
+                            maybe_get_args_offset(self.ident, &self.items)
                         {
                             let one_line = all_simple
                                 && definitive_tactic(
@@ -331,7 +562,7 @@ fn try_overflow_last_item(&self, list_items: &mut Vec<ListItem>) -> DefinitiveLi
                             if one_line {
                                 tactic = DefinitiveListTactic::SpecialMacro(num_args_before);
                             };
-                        } else if is_every_expr_simple(self.items) && no_long_items(list_items) {
+                        } else if is_every_expr_simple(&self.items) && no_long_items(list_items) {
                             tactic = DefinitiveListTactic::Mixed;
                         }
                     }
@@ -363,32 +594,27 @@ fn rewrite_items(&self) -> Option<(bool, String)> {
         // indentation. If its first line fits on one line with the other arguments,
         // we format the function arguments horizontally.
         let tactic = self.try_overflow_last_item(&mut list_items);
-
-        let fmt = ListFormatting {
-            tactic,
-            separator: ",",
-            trailing_separator: if let Some(tactic) = self.force_separator_tactic {
-                tactic
-            } else if !self.context.use_block_indent() {
-                SeparatorTactic::Never
-            } else if tactic == DefinitiveListTactic::Mixed {
-                // We are using mixed layout because everything did not fit within a single line.
-                SeparatorTactic::Always
-            } else {
-                self.context.config.trailing_comma()
-            },
-            separator_place: SeparatorPlace::Back,
-            shape: self.nested_shape,
-            ends_with_newline: match tactic {
-                DefinitiveListTactic::Vertical | DefinitiveListTactic::Mixed => {
-                    self.context.use_block_indent()
-                }
-                _ => false,
-            },
-            preserve_newline: false,
-            nested: false,
-            config: self.context.config,
+        let trailing_separator = if let Some(tactic) = self.force_separator_tactic {
+            tactic
+        } else if !self.context.use_block_indent() {
+            SeparatorTactic::Never
+        } else if tactic == DefinitiveListTactic::Mixed {
+            // We are using mixed layout because everything did not fit within a single line.
+            SeparatorTactic::Always
+        } else {
+            self.context.config.trailing_comma()
         };
+        let ends_with_newline = match tactic {
+            DefinitiveListTactic::Vertical | DefinitiveListTactic::Mixed => {
+                self.context.use_block_indent()
+            }
+            _ => false,
+        };
+
+        let fmt = ListFormatting::new(self.nested_shape, self.context.config)
+            .tactic(tactic)
+            .trailing_separator(trailing_separator)
+            .ends_with_newline(ends_with_newline);
 
         write_list(&list_items, &fmt)
             .map(|items_str| (tactic == DefinitiveListTactic::Horizontal, items_str))
@@ -466,31 +692,20 @@ fn need_block_indent(s: &str, shape: Shape) -> bool {
     })
 }
 
-fn can_be_overflowed<'a, T>(context: &RewriteContext, items: &[&T]) -> bool
-where
-    T: Rewrite + Spanned + ToExpr + 'a,
-{
+fn can_be_overflowed(context: &RewriteContext, items: &[OverflowableItem]) -> bool {
     items
         .last()
         .map_or(false, |x| x.can_be_overflowed(context, items.len()))
 }
 
 /// Returns a shape for the last argument which is going to be overflowed.
-fn last_item_shape<T>(
-    lists: &[&T],
+fn last_item_shape(
+    lists: &[OverflowableItem],
     items: &[ListItem],
     shape: Shape,
     args_max_width: usize,
-) -> Option<Shape>
-where
-    T: Rewrite + Spanned + ToExpr,
-{
-    let is_nested_call = lists
-        .iter()
-        .next()
-        .and_then(|item| item.to_expr())
-        .map_or(false, is_nested_call);
-    if items.len() == 1 && !is_nested_call {
+) -> Option<Shape> {
+    if items.len() == 1 && !lists.get(0)?.is_nested_call() {
         return Some(shape);
     }
     let offset = items.iter().rev().skip(1).fold(0, |acc, i| {
@@ -500,7 +715,8 @@ fn last_item_shape<T>(
     Shape {
         width: min(args_max_width, shape.width),
         ..shape
-    }.offset_left(offset)
+    }
+    .offset_left(offset)
 }
 
 fn shape_from_indent_style(
@@ -528,3 +744,21 @@ fn no_long_items(list: &[ListItem]) -> bool {
     list.iter()
         .all(|item| item.inner_as_ref().len() <= SHORT_ITEM_THRESHOLD)
 }
+
+/// In case special-case style is required, returns an offset from which we start horizontal layout.
+pub fn maybe_get_args_offset(callee_str: &str, args: &[OverflowableItem]) -> Option<(bool, usize)> {
+    if let Some(&(_, num_args_before)) = args
+        .get(0)?
+        .whitelist()
+        .iter()
+        .find(|&&(s, _)| s == callee_str)
+    {
+        let all_simple = args.len() > num_args_before
+            && is_every_expr_simple(&args[0..num_args_before])
+            && is_every_expr_simple(&args[num_args_before + 1..]);
+
+        Some((all_simple, num_args_before))
+    } else {
+        None
+    }
+}