//! Rewrite a list some items with overflow. use std::cmp::min; use itertools::Itertools; use rustc_ast::token::Delimiter; use rustc_ast::{ast, ptr}; use rustc_span::Span; use crate::closures; use crate::config::lists::*; use crate::config::Version; use crate::expr::{ can_be_overflowed_expr, is_every_expr_simple, is_method_call, is_nested_call, is_simple_expr, rewrite_cond, }; use crate::lists::{ definitive_tactic, itemize_list, write_list, ListFormatting, ListItem, Separator, }; use crate::macros::MacroArg; use crate::patterns::{can_be_overflowed_pat, TuplePatField}; use crate::rewrite::{Rewrite, RewriteContext}; use crate::shape::Shape; use crate::source_map::SpanUtils; use crate::spanned::Spanned; use crate::types::{can_be_overflowed_type, SegmentParam}; use crate::utils::{count_newlines, extra_offset, first_line_width, last_line_width, mk_sp}; /// 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(crate) enum OverflowableItem<'a> { Expr(&'a ast::Expr), GenericParam(&'a ast::GenericParam), MacroArg(&'a MacroArg), NestedMetaItem(&'a ast::NestedMetaItem), SegmentParam(&'a SegmentParam<'a>), FieldDef(&'a ast::FieldDef), TuplePatField(&'a TuplePatField<'a>), Ty(&'a ast::Ty), Pat(&'a ast::Pat), } impl<'a> Rewrite for OverflowableItem<'a> { fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { 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> { fn has_attrs(&self) -> bool { match self { OverflowableItem::Expr(ast::Expr { attrs, .. }) | OverflowableItem::GenericParam(ast::GenericParam { attrs, .. }) => !attrs.is_empty(), OverflowableItem::FieldDef(ast::FieldDef { attrs, .. }) => !attrs.is_empty(), OverflowableItem::MacroArg(MacroArg::Expr(expr)) => !expr.attrs.is_empty(), OverflowableItem::MacroArg(MacroArg::Item(item)) => !item.attrs.is_empty(), _ => false, } } pub(crate) fn map(&self, f: F) -> T where F: Fn(&dyn 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::FieldDef(sf) => f(*sf), OverflowableItem::TuplePatField(pat) => f(*pat), OverflowableItem::Ty(ty) => f(*ty), OverflowableItem::Pat(pat) => f(*pat), } } pub(crate) fn is_simple(&self) -> bool { match self { OverflowableItem::Expr(expr) => is_simple_expr(expr), OverflowableItem::MacroArg(MacroArg::Keyword(..)) => true, OverflowableItem::MacroArg(MacroArg::Expr(expr)) => is_simple_expr(expr), OverflowableItem::NestedMetaItem(nested_meta_item) => match nested_meta_item { ast::NestedMetaItem::Literal(..) => true, ast::NestedMetaItem::MetaItem(ref meta_item) => { matches!(meta_item.kind, ast::MetaItemKind::Word) } }, _ => false, } } pub(crate) fn is_expr(&self) -> bool { matches!( self, OverflowableItem::Expr(..) | OverflowableItem::MacroArg(MacroArg::Expr(..)) ) } pub(crate) 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(crate) fn to_expr(&self) -> Option<&'a ast::Expr> { match self { OverflowableItem::Expr(expr) => Some(expr), OverflowableItem::MacroArg(MacroArg::Expr(ref expr)) => Some(expr), _ => None, } } pub(crate) 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, MacroArg::Keyword(..) => false, }, OverflowableItem::NestedMetaItem(nested_meta_item) if len == 1 => { match nested_meta_item { ast::NestedMetaItem::Literal(..) => false, ast::NestedMetaItem::MetaItem(..) => true, } } OverflowableItem::SegmentParam(SegmentParam::Type(ty)) => { can_be_overflowed_type(context, ty, len) } 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(crate) trait IntoOverflowableItem<'a>: Rewrite + Spanned { fn into_overflowable_item(&'a self) -> OverflowableItem<'a>; } impl<'a, T: 'a + IntoOverflowableItem<'a>> IntoOverflowableItem<'a> for ptr::P { 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, FieldDef, Ty, Pat); impl_into_overflowable_item_for_rustfmt_types!([MacroArg], [SegmentParam, TuplePatField]); pub(crate) fn into_overflowable_list<'a, T>( iter: impl Iterator, ) -> impl Iterator> where T: 'a + IntoOverflowableItem<'a>, { iter.map(|x| IntoOverflowableItem::into_overflowable_item(x)) } pub(crate) fn rewrite_with_parens<'a, T: 'a + IntoOverflowableItem<'a>>( context: &'a RewriteContext<'_>, ident: &'a str, items: impl Iterator, shape: Shape, span: Span, item_max_width: usize, force_separator_tactic: Option, ) -> Option { Context::new( context, items, ident, shape, span, "(", ")", item_max_width, force_separator_tactic, None, ) .rewrite(shape) } pub(crate) fn rewrite_with_angle_brackets<'a, T: 'a + IntoOverflowableItem<'a>>( context: &'a RewriteContext<'_>, ident: &'a str, items: impl Iterator, shape: Shape, span: Span, ) -> Option { Context::new( context, items, ident, shape, span, "<", ">", context.config.max_width(), None, None, ) .rewrite(shape) } pub(crate) fn rewrite_with_square_brackets<'a, T: 'a + IntoOverflowableItem<'a>>( context: &'a RewriteContext<'_>, name: &'a str, items: impl Iterator, shape: Shape, span: Span, force_separator_tactic: Option, delim_token: Option, ) -> Option { let (lhs, rhs) = match delim_token { Some(Delimiter::Parenthesis) => ("(", ")"), Some(Delimiter::Brace) => ("{", "}"), _ => ("[", "]"), }; Context::new( context, items, name, shape, span, lhs, rhs, context.config.array_width(), force_separator_tactic, Some(("[", "]")), ) .rewrite(shape) } struct Context<'a> { context: &'a RewriteContext<'a>, items: Vec>, ident: &'a str, prefix: &'static str, suffix: &'static str, one_line_shape: Shape, nested_shape: Shape, span: Span, item_max_width: usize, one_line_width: usize, force_separator_tactic: Option, custom_delims: Option<(&'a str, &'a str)>, } impl<'a> Context<'a> { fn new>( context: &'a RewriteContext<'_>, items: impl Iterator, ident: &'a str, shape: Shape, span: Span, prefix: &'static str, suffix: &'static str, item_max_width: usize, force_separator_tactic: Option, custom_delims: Option<(&'a str, &'a str)>, ) -> Context<'a> { let used_width = extra_offset(ident, shape); // 1 = `()` let one_line_width = shape.width.saturating_sub(used_width + 2); // 1 = "(" or ")" let one_line_shape = shape .offset_left(last_line_width(ident) + 1) .and_then(|shape| shape.sub_width(1)) .unwrap_or(Shape { width: 0, ..shape }); let nested_shape = shape_from_indent_style(context, shape, used_width + 2, used_width + 1); Context { context, items: into_overflowable_list(items).collect(), ident, one_line_shape, nested_shape, span, prefix, suffix, item_max_width, one_line_width, force_separator_tactic, custom_delims, } } fn last_item(&self) -> Option<&OverflowableItem<'_>> { self.items.last() } fn items_span(&self) -> Span { let span_lo = self .context .snippet_provider .span_after(self.span, self.prefix); mk_sp(span_lo, self.span.hi()) } fn rewrite_last_item_with_overflow( &self, last_list_item: &mut ListItem, shape: Shape, ) -> Option { let last_item = self.last_item()?; let rewrite = match last_item { OverflowableItem::Expr(expr) => { match expr.kind { // 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::ForLoop(..) | ast::ExprKind::Loop(..) | ast::ExprKind::While(..) | 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), } } item => item.rewrite(self.context, shape), }; if let Some(rewrite) = rewrite { // splitn(2, *).next().unwrap() is always safe. let rewrite_first_line = Some(rewrite.splitn(2, '\n').next().unwrap().to_owned()); last_list_item.item = rewrite_first_line; Some(rewrite) } else { None } } fn default_tactic(&self, list_items: &[ListItem]) -> DefinitiveListTactic { definitive_tactic( list_items, ListTactic::LimitedHorizontalVertical(self.item_max_width), Separator::Comma, self.one_line_width, ) } fn try_overflow_last_item(&self, list_items: &mut Vec) -> DefinitiveListTactic { // 1 = "(" let combine_arg_with_callee = self.items.len() == 1 && self.items[0].is_expr() && !self.items[0].has_attrs() && 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.get(); 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); } Some(OverflowableItem::MacroArg(MacroArg::Expr(expr))) if !combine_arg_with_callee && is_method_call(expr) && self.context.config.version() == Version::Two => { self.context.force_one_line_chain.replace(true); } _ => (), } let result = last_item_shape( &self.items, list_items, self.one_line_shape, self.item_max_width, ) .and_then(|arg_shape| { self.rewrite_last_item_with_overflow( &mut list_items[self.items.len() - 1], arg_shape, ) }); self.context.force_one_line_chain.replace(old_value); result } else { None }; let mut tactic = definitive_tactic( &*list_items, ListTactic::LimitedHorizontalVertical(self.item_max_width), Separator::Comma, self.one_line_width, ); // Replace the stub with the full overflowing last argument if the rewrite // succeeded and its first line fits with the other arguments. match (overflow_last, tactic, placeholder) { (true, DefinitiveListTactic::Horizontal, Some(ref overflowed)) if self.items.len() == 1 => { // When we are rewriting a nested function call, we restrict the // budget for the inner function to avoid them being deeply nested. // However, when the inner function has a prefix or a suffix // (e.g., `foo() as u32`), this budget reduction may produce poorly // formatted code, where a prefix or a suffix being left on its own // line. Here we explicitlly check those cases. if count_newlines(overflowed) == 1 { let rw = self .items .last() .and_then(|last_item| last_item.rewrite(self.context, self.nested_shape)); let no_newline = rw.as_ref().map_or(false, |s| !s.contains('\n')); if no_newline { list_items[self.items.len() - 1].item = rw; } else { list_items[self.items.len() - 1].item = Some(overflowed.to_owned()); } } else { list_items[self.items.len() - 1].item = Some(overflowed.to_owned()); } } (true, DefinitiveListTactic::Horizontal, placeholder @ Some(..)) => { list_items[self.items.len() - 1].item = placeholder; } _ if !self.items.is_empty() => { list_items[self.items.len() - 1].item = self .items .last() .and_then(|last_item| last_item.rewrite(self.context, self.nested_shape)); // Use horizontal layout for a function with a single argument as long as // everything fits in a single line. // `self.one_line_width == 0` means vertical layout is forced. if self.items.len() == 1 && self.one_line_width != 0 && !list_items[0].has_comment() && !list_items[0].inner_as_ref().contains('\n') && crate::lists::total_item_width(&list_items[0]) <= self.one_line_width { tactic = DefinitiveListTactic::Horizontal; } else { tactic = self.default_tactic(list_items); if tactic == DefinitiveListTactic::Vertical { if let Some((all_simple, num_args_before)) = maybe_get_args_offset(self.ident, &self.items) { let one_line = all_simple && definitive_tactic( &list_items[..num_args_before], ListTactic::HorizontalVertical, Separator::Comma, self.nested_shape.width, ) == DefinitiveListTactic::Horizontal && definitive_tactic( &list_items[num_args_before + 1..], ListTactic::HorizontalVertical, Separator::Comma, self.nested_shape.width, ) == DefinitiveListTactic::Horizontal; if one_line { tactic = DefinitiveListTactic::SpecialMacro(num_args_before); }; } else if is_every_expr_simple(&self.items) && no_long_items( list_items, self.context.config.short_array_element_width_threshold(), ) { tactic = DefinitiveListTactic::Mixed; } } } } _ => (), } tactic } fn rewrite_items(&self) -> Option<(bool, String)> { let span = self.items_span(); let items = itemize_list( self.context.snippet_provider, self.items.iter(), self.suffix, ",", |item| item.span().lo(), |item| item.span().hi(), |item| item.rewrite(self.context, self.nested_shape), span.lo(), span.hi(), true, ); let mut list_items: Vec<_> = items.collect(); // Try letting the last argument overflow to the next line with block // 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 trailing_separator = if let Some(tactic) = self.force_separator_tactic { tactic } else if !self.context.use_block_indent() { SeparatorTactic::Never } 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)) } fn wrap_items(&self, items_str: &str, shape: Shape, is_extendable: bool) -> String { let shape = Shape { width: shape.width.saturating_sub(last_line_width(self.ident)), ..shape }; let (prefix, suffix) = match self.custom_delims { Some((lhs, rhs)) => (lhs, rhs), _ => (self.prefix, self.suffix), }; let extend_width = if items_str.is_empty() { 2 } else { first_line_width(items_str) + 1 }; let nested_indent_str = self .nested_shape .indent .to_string_with_newline(self.context.config); let indent_str = shape .block() .indent .to_string_with_newline(self.context.config); let mut result = String::with_capacity( self.ident.len() + items_str.len() + 2 + indent_str.len() + nested_indent_str.len(), ); result.push_str(self.ident); result.push_str(prefix); let force_single_line = if self.context.config.version() == Version::Two { !self.context.use_block_indent() || (is_extendable && extend_width <= shape.width) } else { // 2 = `()` let fits_one_line = items_str.len() + 2 <= shape.width; !self.context.use_block_indent() || (self.context.inside_macro() && !items_str.contains('\n') && fits_one_line) || (is_extendable && extend_width <= shape.width) }; if force_single_line { result.push_str(items_str); } else { if !items_str.is_empty() { result.push_str(&nested_indent_str); result.push_str(items_str); } result.push_str(&indent_str); } result.push_str(suffix); result } fn rewrite(&self, shape: Shape) -> Option { let (extendable, items_str) = self.rewrite_items()?; // If we are using visual indent style and failed to format, retry with block indent. if !self.context.use_block_indent() && need_block_indent(&items_str, self.nested_shape) && !extendable { self.context.use_block.replace(true); let result = self.rewrite(shape); self.context.use_block.replace(false); return result; } Some(self.wrap_items(&items_str, shape, extendable)) } } fn need_block_indent(s: &str, shape: Shape) -> bool { s.lines().skip(1).any(|s| { s.find(|c| !char::is_whitespace(c)) .map_or(false, |w| w + 1 < shape.indent.width()) }) } 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( lists: &[OverflowableItem<'_>], items: &[ListItem], shape: Shape, args_max_width: usize, ) -> Option { if items.len() == 1 && !lists.get(0)?.is_nested_call() { return Some(shape); } let offset = items .iter() .dropping_back(1) .map(|i| { // 2 = ", " 2 + i.inner_as_ref().len() }) .sum(); Shape { width: min(args_max_width, shape.width), ..shape } .offset_left(offset) } fn shape_from_indent_style( context: &RewriteContext<'_>, shape: Shape, overhead: usize, offset: usize, ) -> Shape { let (shape, overhead) = if context.use_block_indent() { let shape = shape .block() .block_indent(context.config.tab_spaces()) .with_max_width(context.config); (shape, 1) // 1 = "," } else { (shape.visual_indent(offset), overhead) }; Shape { width: shape.width.saturating_sub(overhead), ..shape } } fn no_long_items(list: &[ListItem], short_array_element_width_threshold: usize) -> bool { list.iter() .all(|item| item.inner_as_ref().len() <= short_array_element_width_threshold) } /// In case special-case style is required, returns an offset from which we start horizontal layout. pub(crate) 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 } }