// 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::parse::token::DelimToken;
use syntax::source_map::Span;
+use syntax::{ast, ptr};
use closures;
-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,
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,
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) => ("{", "}"),
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,
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,
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);
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,
}
}
- fn last_item(&self) -> Option<&&T> {
+ fn last_item(&self) -> Option<&OverflowableItem> {
self.items.last()
}
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 {
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,
(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()
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(
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;
}
}
})
}
-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| {
Shape {
width: min(args_max_width, shape.width),
..shape
- }.offset_left(offset)
+ }
+ .offset_left(offset)
}
fn shape_from_indent_style(
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
+ }
+}