X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=src%2Fchains.rs;h=c7acf19ee0cea6e42bb41891bb371a394b829b23;hb=66c27c9161b2aa70c2902807be12952bd4a0a62b;hp=52999635cf125ce4fa1dc1a6f2b8550e9056a740;hpb=0877517eb214dced599c41292571de9abc9dd6ca;p=rust.git diff --git a/src/chains.rs b/src/chains.rs index 52999635cf1..c7acf19ee0c 100644 --- a/src/chains.rs +++ b/src/chains.rs @@ -1,15 +1,5 @@ -// Copyright 2015 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! Formatting of chained expressions, i.e. expressions which are chained by -//! dots: struct and enum field access, method calls, and try shorthand (?). +//! Formatting of chained expressions, i.e., expressions that are chained by +//! dots: struct and enum field access, method calls, and try shorthand (`?`). //! //! Instead of walking these subexpressions one-by-one, as is our usual strategy //! for expression formatting, we collect maximal sequences of these expressions @@ -26,7 +16,7 @@ //! following values of `chain_indent`: //! Block: //! -//! ```ignore +//! ```text //! let foo = { //! aaaa; //! bbb; @@ -37,7 +27,7 @@ //! //! Visual: //! -//! ```ignore +//! ```text //! let foo = { //! aaaa; //! bbb; @@ -51,7 +41,7 @@ //! the braces. //! Block: //! -//! ```ignore +//! ```text //! let a = foo.bar //! .baz() //! .qux @@ -59,138 +49,474 @@ //! //! Visual: //! -//! ```ignore +//! ```text //! let a = foo.bar //! .baz() //! .qux //! ``` -use config::IndentStyle; -use expr::rewrite_call; -use macros::convert_try_mac; -use rewrite::{Rewrite, RewriteContext}; -use shape::Shape; -use spanned::Spanned; -use utils::{ - first_line_width, last_line_extendable, last_line_width, mk_sp, trimmed_last_line_width, - wrap_str, -}; - use std::borrow::Cow; use std::cmp::min; -use std::iter; -use syntax::codemap::Span; +use syntax::source_map::{BytePos, Span}; use syntax::{ast, ptr}; -pub fn rewrite_chain(expr: &ast::Expr, context: &RewriteContext, shape: Shape) -> Option { - debug!("rewrite_chain {:?}", shape); - let total_span = expr.span; - let (parent, subexpr_list) = make_subexpr_list(expr, context); +use crate::comment::{rewrite_comment, CharClasses, FullCodeCharKind, RichChar}; +use crate::config::IndentStyle; +use crate::expr::rewrite_call; +use crate::lists::extract_pre_comment; +use crate::macros::convert_try_mac; +use crate::rewrite::{Rewrite, RewriteContext}; +use crate::shape::Shape; +use crate::source_map::SpanUtils; +use crate::utils::{ + self, first_line_width, last_line_extendable, last_line_width, mk_sp, rewrite_ident, + trimmed_last_line_width, wrap_str, +}; + +pub(crate) fn rewrite_chain( + expr: &ast::Expr, + context: &RewriteContext<'_>, + shape: Shape, +) -> Option { + let chain = Chain::from_ast(expr, context); + debug!("rewrite_chain {:?} {:?}", chain, shape); - // Bail out if the chain is just try sugar, i.e., an expression followed by - // any number of `?`s. - if chain_only_try(&subexpr_list) { - return rewrite_try(&parent, subexpr_list.len(), context, shape); + // If this is just an expression with some `?`s, then format it trivially and + // return early. + if chain.children.is_empty() { + return chain.parent.rewrite(context, shape); } - 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.indent_style() { - IndentStyle::Visual => shape.visual_indent(0), - IndentStyle::Block => shape, + chain.rewrite(context, shape) +} + +#[derive(Debug)] +enum CommentPosition { + Back, + Top, +} + +// An expression plus trailing `?`s to be formatted together. +#[derive(Debug)] +struct ChainItem { + kind: ChainItemKind, + tries: usize, + span: Span, +} + +// FIXME: we can't use a reference here because to convert `try!` to `?` we +// synthesise the AST node. However, I think we could use `Cow` and that +// would remove a lot of cloning. +#[derive(Debug)] +enum ChainItemKind { + Parent(ast::Expr), + MethodCall( + ast::PathSegment, + Vec, + Vec>, + ), + StructField(ast::Ident), + TupleField(ast::Ident, bool), + Await, + Comment(String, CommentPosition), +} + +impl ChainItemKind { + fn is_block_like(&self, context: &RewriteContext<'_>, reps: &str) -> bool { + match self { + ChainItemKind::Parent(ref expr) => utils::is_block_expr(context, expr, reps), + ChainItemKind::MethodCall(..) + | ChainItemKind::StructField(..) + | ChainItemKind::TupleField(..) + | ChainItemKind::Await + | ChainItemKind::Comment(..) => false, } - } else { - shape - }; - let parent_rewrite = parent - .rewrite(context, parent_shape) - .map(|parent_rw| parent_rw + &"?".repeat(prefix_try_num))?; - let parent_rewrite_contains_newline = parent_rewrite.contains('\n'); - let is_small_parent = shape.offset + 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 (nested_shape, extend) = if !parent_rewrite_contains_newline && is_continuable(&parent) { - ( - chain_indent(context, shape.add_offset(parent_rewrite.len())), - context.config.indent_style() == IndentStyle::Visual || is_small_parent, - ) - } else if is_block_expr(context, &parent, &parent_rewrite) { - match context.config.indent_style() { - // Try to put the first child on the same line with parent's last line - IndentStyle::Block => (parent_shape.block_indent(context.config.tab_spaces()), true), - // The parent is a block, so align the rest of the chain with the closing - // brace. - IndentStyle::Visual => (parent_shape, false), + } + + fn is_tup_field_access(expr: &ast::Expr) -> bool { + match expr.node { + ast::ExprKind::Field(_, ref field) => { + field.name.to_string().chars().all(|c| c.is_digit(10)) + } + _ => false, } - } else { - ( - chain_indent(context, shape.add_offset(parent_rewrite.len())), - false, - ) - }; + } + + fn from_ast(context: &RewriteContext<'_>, expr: &ast::Expr) -> (ChainItemKind, Span) { + let (kind, span) = match expr.node { + ast::ExprKind::MethodCall(ref segment, ref expressions) => { + let types = if let Some(ref generic_args) = segment.args { + if let ast::GenericArgs::AngleBracketed(ref data) = **generic_args { + data.args.clone() + } else { + vec![] + } + } else { + vec![] + }; + let span = mk_sp(expressions[0].span.hi(), expr.span.hi()); + let kind = ChainItemKind::MethodCall(segment.clone(), types, expressions.clone()); + (kind, span) + } + ast::ExprKind::Field(ref nested, field) => { + let kind = if Self::is_tup_field_access(expr) { + ChainItemKind::TupleField(field, Self::is_tup_field_access(nested)) + } else { + ChainItemKind::StructField(field) + }; + let span = mk_sp(nested.span.hi(), field.span.hi()); + (kind, span) + } + ast::ExprKind::Await(ast::AwaitOrigin::FieldLike, ref nested) => { + let span = mk_sp(nested.span.hi(), expr.span.hi()); + (ChainItemKind::Await, span) + } + _ => return (ChainItemKind::Parent(expr.clone()), expr.span), + }; + + // Remove comments from the span. + let lo = context.snippet_provider.span_before(span, "."); + (kind, mk_sp(lo, span.hi())) + } +} + +impl Rewrite for ChainItem { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + let shape = shape.sub_width(self.tries)?; + let rewrite = match self.kind { + ChainItemKind::Parent(ref expr) => expr.rewrite(context, shape)?, + ChainItemKind::MethodCall(ref segment, ref types, ref exprs) => { + Self::rewrite_method_call(segment.ident, types, exprs, self.span, context, shape)? + } + ChainItemKind::StructField(ident) => format!(".{}", rewrite_ident(context, ident)), + ChainItemKind::TupleField(ident, nested) => format!( + "{}.{}", + if nested { " " } else { "" }, + rewrite_ident(context, ident) + ), + ChainItemKind::Await => ".await".to_owned(), + ChainItemKind::Comment(ref comment, _) => { + rewrite_comment(comment, false, shape, context.config)? + } + }; + Some(format!("{}{}", rewrite, "?".repeat(self.tries))) + } +} + +impl ChainItem { + fn new(context: &RewriteContext<'_>, expr: &ast::Expr, tries: usize) -> ChainItem { + let (kind, span) = ChainItemKind::from_ast(context, expr); + ChainItem { kind, tries, span } + } + + fn comment(span: Span, comment: String, pos: CommentPosition) -> ChainItem { + ChainItem { + kind: ChainItemKind::Comment(comment, pos), + tries: 0, + span, + } + } + + fn is_comment(&self) -> bool { + match self.kind { + ChainItemKind::Comment(..) => true, + _ => false, + } + } + + fn rewrite_method_call( + method_name: ast::Ident, + types: &[ast::GenericArg], + args: &[ptr::P], + span: Span, + context: &RewriteContext<'_>, + shape: Shape, + ) -> Option { + let type_str = if types.is_empty() { + String::new() + } else { + let type_list = types + .iter() + .map(|ty| ty.rewrite(context, shape)) + .collect::>>()?; + + format!("::<{}>", type_list.join(", ")) + }; + let callee_str = format!(".{}{}", rewrite_ident(context, method_name), type_str); + rewrite_call(context, &callee_str, &args[1..], span, shape) + } +} - let other_child_shape = nested_shape.with_max_width(context.config); +#[derive(Debug)] +struct Chain { + parent: ChainItem, + children: Vec, +} + +impl Chain { + fn from_ast(expr: &ast::Expr, context: &RewriteContext<'_>) -> Chain { + let subexpr_list = Self::make_subexpr_list(expr, context); + + // Un-parse the expression tree into ChainItems + let mut rev_children = vec![]; + let mut sub_tries = 0; + for subexpr in &subexpr_list { + match subexpr.node { + ast::ExprKind::Try(_) => sub_tries += 1, + _ => { + rev_children.push(ChainItem::new(context, subexpr, sub_tries)); + sub_tries = 0; + } + } + } + + fn is_tries(s: &str) -> bool { + s.chars().all(|c| c == '?') + } + + fn is_post_comment(s: &str) -> bool { + let comment_start_index = s.chars().position(|c| c == '/'); + if comment_start_index.is_none() { + return false; + } + + let newline_index = s.chars().position(|c| c == '\n'); + if newline_index.is_none() { + return true; + } + + comment_start_index.unwrap() < newline_index.unwrap() + } + + fn handle_post_comment( + post_comment_span: Span, + post_comment_snippet: &str, + prev_span_end: &mut BytePos, + children: &mut Vec, + ) { + let white_spaces: &[_] = &[' ', '\t']; + if post_comment_snippet + .trim_matches(white_spaces) + .starts_with('\n') + { + // No post comment. + return; + } + let trimmed_snippet = trim_tries(post_comment_snippet); + if is_post_comment(&trimmed_snippet) { + children.push(ChainItem::comment( + post_comment_span, + trimmed_snippet.trim().to_owned(), + CommentPosition::Back, + )); + *prev_span_end = post_comment_span.hi(); + } + } + + let parent = rev_children.pop().unwrap(); + let mut children = vec![]; + let mut prev_span_end = parent.span.hi(); + let mut iter = rev_children.into_iter().rev().peekable(); + if let Some(first_chain_item) = iter.peek() { + let comment_span = mk_sp(prev_span_end, first_chain_item.span.lo()); + let comment_snippet = context.snippet(comment_span); + if !is_tries(comment_snippet.trim()) { + handle_post_comment( + comment_span, + comment_snippet, + &mut prev_span_end, + &mut children, + ); + } + } + while let Some(chain_item) = iter.next() { + let comment_snippet = context.snippet(chain_item.span); + // FIXME: Figure out the way to get a correct span when converting `try!` to `?`. + let handle_comment = + !(context.config.use_try_shorthand() || is_tries(comment_snippet.trim())); + + // Pre-comment + if handle_comment { + let pre_comment_span = mk_sp(prev_span_end, chain_item.span.lo()); + let pre_comment_snippet = trim_tries(context.snippet(pre_comment_span)); + let (pre_comment, _) = extract_pre_comment(&pre_comment_snippet); + match pre_comment { + Some(ref comment) if !comment.is_empty() => { + children.push(ChainItem::comment( + pre_comment_span, + comment.to_owned(), + CommentPosition::Top, + )); + } + _ => (), + } + } - let first_child_shape = if extend { - let overhead = last_line_width(&parent_rewrite); - let offset = trimmed_last_line_width(&parent_rewrite) + prefix_try_num; - match context.config.indent_style() { - IndentStyle::Visual => parent_shape.offset_left(overhead)?, - IndentStyle::Block => parent_shape.offset_left(offset)?, + prev_span_end = chain_item.span.hi(); + children.push(chain_item); + + // Post-comment + if !handle_comment || iter.peek().is_none() { + continue; + } + + let next_lo = iter.peek().unwrap().span.lo(); + let post_comment_span = mk_sp(prev_span_end, next_lo); + let post_comment_snippet = context.snippet(post_comment_span); + handle_post_comment( + post_comment_span, + post_comment_snippet, + &mut prev_span_end, + &mut children, + ); } - } else { - other_child_shape - }; - debug!( - "child_shapes {:?} {:?}", - first_child_shape, other_child_shape - ); - - 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::>>()?; - - // Total of all items excluding the last. - let extend_last_subexpr = if is_small_parent { - rewrites.len() == 1 && last_line_extendable(&rewrites[0]) - } else { - rewrites.is_empty() && last_line_extendable(&parent_rewrite) - }; - let almost_total = if extend_last_subexpr { - last_line_width(&parent_rewrite) - } else { - rewrites.iter().fold(0, |a, b| a + b.len()) + parent_rewrite.len() - } + suffix_try_num; - let one_line_budget = if rewrites.is_empty() { - shape.width - } else { - min(shape.width, context.config.width_heuristics().chain_width) - }; - let all_in_one_line = !parent_rewrite_contains_newline - && rewrites.iter().all(|s| !s.contains('\n')) - && almost_total < one_line_budget; - let last_shape = if rewrites.is_empty() { - first_child_shape - } else { - other_child_shape - }.sub_width(shape.rhs_overhead(context.config) + suffix_try_num)?; + + Chain { parent, children } + } + + // Returns a Vec of the prefixes of the chain. + // E.g., for input `a.b.c` we return [`a.b.c`, `a.b`, 'a'] + fn make_subexpr_list(expr: &ast::Expr, context: &RewriteContext<'_>) -> Vec { + let mut subexpr_list = vec![expr.clone()]; + + while let Some(subexpr) = Self::pop_expr_chain(subexpr_list.last().unwrap(), context) { + subexpr_list.push(subexpr.clone()); + } + + subexpr_list + } + + // Returns the expression's subexpression, if it exists. When the subexpr + // 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 { + match expr.node { + ast::ExprKind::MethodCall(_, ref expressions) => { + Some(Self::convert_try(&expressions[0], context)) + } + ast::ExprKind::Field(ref subexpr, _) + | ast::ExprKind::Try(ref subexpr) + | ast::ExprKind::Await(ast::AwaitOrigin::FieldLike, ref subexpr) => { + Some(Self::convert_try(subexpr, context)) + } + _ => None, + } + } + + fn convert_try(expr: &ast::Expr, context: &RewriteContext<'_>) -> ast::Expr { + match expr.node { + ast::ExprKind::Mac(ref mac) if context.config.use_try_shorthand() => { + if let Some(subexpr) = convert_try_mac(mac, context) { + subexpr + } else { + expr.clone() + } + } + _ => expr.clone(), + } + } +} + +impl Rewrite for Chain { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + debug!("rewrite chain {:?} {:?}", self, shape); + + let mut formatter = match context.config.indent_style() { + IndentStyle::Block => { + Box::new(ChainFormatterBlock::new(self)) as Box + } + IndentStyle::Visual => { + Box::new(ChainFormatterVisual::new(self)) as Box + } + }; + + formatter.format_root(&self.parent, context, shape)?; + if let Some(result) = formatter.pure_root() { + return wrap_str(result, context.config.max_width(), shape); + } + + // Decide how to layout the rest of the chain. + let child_shape = formatter.child_shape(context, shape)?; + + formatter.format_children(context, child_shape)?; + formatter.format_last_child(context, shape, child_shape)?; + + let result = formatter.join_rewrites(context, child_shape)?; + wrap_str(result, context.config.max_width(), shape) + } +} + +// There are a few types for formatting chains. This is because there is a lot +// in common between formatting with block vs visual indent, but they are +// different enough that branching on the indent all over the place gets ugly. +// Anything that can format a chain is a ChainFormatter. +trait ChainFormatter { + // Parent is the first item in the chain, e.g., `foo` in `foo.bar.baz()`. + // Root is the parent plus any other chain items placed on the first line to + // avoid an orphan. E.g., + // ```text + // foo.bar + // .baz() + // ``` + // If `bar` were not part of the root, then foo would be orphaned and 'float'. + fn format_root( + &mut self, + parent: &ChainItem, + context: &RewriteContext<'_>, + shape: Shape, + ) -> Option<()>; + fn child_shape(&self, context: &RewriteContext<'_>, shape: Shape) -> Option; + fn format_children(&mut self, context: &RewriteContext<'_>, child_shape: Shape) -> Option<()>; + fn format_last_child( + &mut self, + context: &RewriteContext<'_>, + shape: Shape, + child_shape: Shape, + ) -> Option<()>; + fn join_rewrites(&self, context: &RewriteContext<'_>, child_shape: Shape) -> Option; + // Returns `Some` if the chain is only a root, None otherwise. + fn pure_root(&mut self) -> Option; +} + +// Data and behaviour that is shared by both chain formatters. The concrete +// formatters can delegate much behaviour to `ChainFormatterShared`. +struct ChainFormatterShared<'a> { + // The current working set of child items. + children: &'a [ChainItem], + // The current rewrites of items (includes trailing `?`s, but not any way to + // connect the rewrites together). + rewrites: Vec, + // Whether the chain can fit on one line. + fits_single_line: bool, + // The number of children in the chain. This is not equal to `self.children.len()` + // because `self.children` will change size as we process the chain. + child_count: usize, +} + +impl<'a> ChainFormatterShared<'a> { + fn new(chain: &'a Chain) -> ChainFormatterShared<'a> { + ChainFormatterShared { + children: &chain.children, + rewrites: Vec::with_capacity(chain.children.len() + 1), + fits_single_line: false, + child_count: chain.children.len(), + } + } + + fn pure_root(&mut self) -> Option { + if self.children.is_empty() { + assert_eq!(self.rewrites.len(), 1); + Some(self.rewrites.pop().unwrap()) + } else { + None + } + } // 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: - // ``` + // ```text // parent.child1.child2.last_child( // a, // b, @@ -199,7 +525,7 @@ pub fn rewrite_chain(expr: &ast::Expr, context: &RewriteContext, shape: Shape) - // ``` // // A chain without overflowing the last child (in vertical layout): - // ``` + // ```text // parent // .child1 // .child2 @@ -207,8 +533,8 @@ pub fn rewrite_chain(expr: &ast::Expr, context: &RewriteContext, shape: Shape) - // ``` // // In particular, overflowing is effective when the last child is a method with a multi-lined - // block-like argument (e.g. closure): - // ``` + // block-like argument (e.g., closure): + // ```text // parent.child1.child2.last_child(|a, b, c| { // let x = foo(a, b, c); // let y = bar(a, b, c); @@ -218,294 +544,338 @@ pub fn rewrite_chain(expr: &ast::Expr, context: &RewriteContext, shape: Shape) - // result // }) // ``` + fn format_last_child( + &mut self, + may_extend: bool, + context: &RewriteContext<'_>, + shape: Shape, + child_shape: Shape, + ) -> Option<()> { + let last = self.children.last()?; + let extendable = may_extend && last_line_extendable(&self.rewrites[0]); + let prev_last_line_width = last_line_width(&self.rewrites[0]); + + // Total of all items excluding the last. + let almost_total = if extendable { + prev_last_line_width + } else { + self.rewrites + .iter() + .map(|rw| utils::unicode_str_width(&rw)) + .sum() + } + last.tries; + let one_line_budget = if self.child_count == 1 { + shape.width + } else { + min(shape.width, context.config.width_heuristics().chain_width) + } + .saturating_sub(almost_total); + + let all_in_one_line = !self.children.iter().any(ChainItem::is_comment) + && self.rewrites.iter().all(|s| !s.contains('\n')) + && one_line_budget > 0; + let last_shape = if all_in_one_line { + shape.sub_width(last.tries)? + } else if extendable { + child_shape.sub_width(last.tries)? + } else { + child_shape.sub_width(shape.rhs_overhead(context.config) + last.tries)? + }; - // `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_subexpr { - // 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_child_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) + let mut last_subexpr_str = None; + if all_in_one_line || extendable { + // First we try to 'overflow' the last child and see if it looks better than using + // vertical layout. + let one_line_shape = if context.use_block_indent() { + last_shape.offset_left(almost_total) + } else { + last_shape + .visual_indent(almost_total) + .sub_width(almost_total) + }; + + if let Some(one_line_shape) = one_line_shape { + if let Some(rw) = last.rewrite(context, one_line_shape) { + // We allow overflowing here only if both of the following conditions match: + // 1. The entire chain fits in a single line except the last child. + // 2. `last_child_str.lines().count() >= 5`. + let line_count = rw.lines().count(); + let could_fit_single_line = first_line_width(&rw) <= one_line_budget; + if could_fit_single_line && line_count >= 5 { + last_subexpr_str = Some(rw); + self.fits_single_line = all_in_one_line; + } 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. + let last_shape = child_shape + .sub_width(shape.rhs_overhead(context.config) + last.tries)?; + match last.rewrite(context, last_shape) { + Some(ref new_rw) if !could_fit_single_line => { + last_subexpr_str = Some(new_rw.clone()); + } + Some(ref new_rw) if new_rw.lines().count() >= line_count => { + last_subexpr_str = Some(rw); + self.fits_single_line = could_fit_single_line && all_in_one_line; + } + new_rw @ Some(..) => { + last_subexpr_str = new_rw; + } + _ => { + last_subexpr_str = Some(rw); + self.fits_single_line = could_fit_single_line && all_in_one_line; + } } - new_rw @ Some(..) => (new_rw, false), - _ => (Some(rw), fits_single_line), } } - } else { - (rewrite_last(), false) } - })? - } 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. - Cow::from("") - } else { - // Use new lines. - if *context.force_one_line_chain.borrow() { - return None; } - nested_shape.indent.to_string_with_newline(context.config) - }; - - let first_connector = if is_small_parent - || fits_single_line - || last_line_extendable(&parent_rewrite) - || context.config.indent_style() == IndentStyle::Visual - { - "" - } else { - &connector - }; - - 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.indent_style() == IndentStyle::Visual - { - "" + + let last_shape = if context.use_block_indent() { + last_shape } else { - &connector + child_shape.sub_width(shape.rhs_overhead(context.config) + last.tries)? }; - format!( - "{}{}{}{}{}", - parent_rewrite, - first_connector, - rewrites[0], - second_connector, - join_rewrites(&rewrites[1..], &connector) - ) - } else { - format!( - "{}{}{}", - parent_rewrite, - first_connector, - join_rewrites(&rewrites, &connector) - ) - }; - let result = format!("{}{}", result, "?".repeat(suffix_try_num)); - if context.config.indent_style() == IndentStyle::Visual { - wrap_str(result, context.config.max_width(), shape) - } else { - Some(result) + + last_subexpr_str = last_subexpr_str.or_else(|| last.rewrite(context, last_shape)); + self.rewrites.push(last_subexpr_str?); + Some(()) } -} -// 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 { - true + fn join_rewrites(&self, context: &RewriteContext<'_>, child_shape: Shape) -> Option { + let connector = if self.fits_single_line { + // Yay, we can put everything on one line. + Cow::from("") } else { - false + // Use new lines. + if *context.force_one_line_chain.borrow() { + return None; + } + child_shape.to_string_with_newline(context.config) + }; + + let mut rewrite_iter = self.rewrites.iter(); + let mut result = rewrite_iter.next().unwrap().clone(); + let children_iter = self.children.iter(); + let iter = rewrite_iter.zip(children_iter); + + for (rewrite, chain_item) in iter { + match chain_item.kind { + ChainItemKind::Comment(_, CommentPosition::Back) => result.push(' '), + ChainItemKind::Comment(_, CommentPosition::Top) => result.push_str(&connector), + _ => result.push_str(&connector), + } + result.push_str(&rewrite); } - }) + + Some(result) + } } -fn rewrite_try( - expr: &ast::Expr, - try_count: usize, - context: &RewriteContext, - shape: Shape, -) -> Option { - let sub_expr = expr.rewrite(context, shape.sub_width(try_count)?)?; - Some(format!("{}{}", sub_expr, "?".repeat(try_count))) +// Formats a chain using block indent. +struct ChainFormatterBlock<'a> { + shared: ChainFormatterShared<'a>, + root_ends_with_block: bool, } -fn join_rewrites(rewrites: &[String], connector: &str) -> String { - let mut rewrite_iter = rewrites.iter(); - let mut result = rewrite_iter.next().unwrap().clone(); +impl<'a> ChainFormatterBlock<'a> { + fn new(chain: &'a Chain) -> ChainFormatterBlock<'a> { + ChainFormatterBlock { + shared: ChainFormatterShared::new(chain), + root_ends_with_block: false, + } + } +} + +impl<'a> ChainFormatter for ChainFormatterBlock<'a> { + fn format_root( + &mut self, + parent: &ChainItem, + context: &RewriteContext<'_>, + shape: Shape, + ) -> Option<()> { + let mut root_rewrite: String = parent.rewrite(context, shape)?; + + let mut root_ends_with_block = parent.kind.is_block_like(context, &root_rewrite); + let tab_width = context.config.tab_spaces().saturating_sub(shape.offset); + + while root_rewrite.len() <= tab_width && !root_rewrite.contains('\n') { + let item = &self.shared.children[0]; + if let ChainItemKind::Comment(..) = item.kind { + break; + } + let shape = shape.offset_left(root_rewrite.len())?; + match &item.rewrite(context, shape) { + Some(rewrite) => root_rewrite.push_str(rewrite), + None => break, + } + + root_ends_with_block = last_line_extendable(&root_rewrite); - for rewrite in rewrite_iter { - if rewrite != "?" { - result.push_str(connector); + self.shared.children = &self.shared.children[1..]; + if self.shared.children.is_empty() { + break; + } } - result.push_str(&rewrite); + self.shared.rewrites.push(root_rewrite); + self.root_ends_with_block = root_ends_with_block; + Some(()) } - result -} + fn child_shape(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + Some( + if self.root_ends_with_block { + shape.block_indent(0) + } else { + shape.block_indent(context.config.tab_spaces()) + } + .with_max_width(context.config), + ) + } -// States whether an expression's last line exclusively consists of closing -// 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') + fn format_children(&mut self, context: &RewriteContext<'_>, child_shape: Shape) -> Option<()> { + for item in &self.shared.children[..self.shared.children.len() - 1] { + let rewrite = item.rewrite(context, child_shape)?; + self.shared.rewrites.push(rewrite); } - ast::ExprKind::Struct(..) - | ast::ExprKind::While(..) - | ast::ExprKind::WhileLet(..) - | ast::ExprKind::If(..) - | ast::ExprKind::IfLet(..) - | ast::ExprKind::Block(..) - | ast::ExprKind::Loop(..) - | ast::ExprKind::ForLoop(..) - | ast::ExprKind::Match(..) => repr.contains('\n'), - ast::ExprKind::Paren(ref expr) - | ast::ExprKind::Binary(_, _, ref expr) - | ast::ExprKind::Index(_, ref expr) - | ast::ExprKind::Unary(_, ref expr) => is_block_expr(context, expr, repr), - _ => false, + Some(()) } -} -// Returns the root of the chain and a Vec of the prefixes of the rest of the chain. -// E.g., for input `a.b.c` we return (`a`, [`a.b.c`, `a.b`]) -fn make_subexpr_list(expr: &ast::Expr, context: &RewriteContext) -> (ast::Expr, Vec) { - let mut subexpr_list = vec![expr.clone()]; + fn format_last_child( + &mut self, + context: &RewriteContext<'_>, + shape: Shape, + child_shape: Shape, + ) -> Option<()> { + self.shared + .format_last_child(true, context, shape, child_shape) + } - while let Some(subexpr) = pop_expr_chain(subexpr_list.last().unwrap(), context) { - subexpr_list.push(subexpr.clone()); + fn join_rewrites(&self, context: &RewriteContext<'_>, child_shape: Shape) -> Option { + self.shared.join_rewrites(context, child_shape) } - let parent = subexpr_list.pop().unwrap(); - (parent, subexpr_list) + fn pure_root(&mut self) -> Option { + self.shared.pure_root() + } } -fn chain_indent(context: &RewriteContext, shape: Shape) -> Shape { - match context.config.indent_style() { - IndentStyle::Visual => shape.visual_indent(0), - IndentStyle::Block => shape - .block_indent(context.config.tab_spaces()) - .with_max_width(context.config), - } +// Format a chain using visual indent. +struct ChainFormatterVisual<'a> { + shared: ChainFormatterShared<'a>, + // The extra offset from the chain's shape to the position of the `.` + offset: usize, } -// Returns the expression's subexpression, if it exists. When the subexpr -// 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 { - match expr.node { - ast::ExprKind::MethodCall(_, ref expressions) => { - Some(convert_try(&expressions[0], context)) - } - ast::ExprKind::Field(ref subexpr, _) | ast::ExprKind::Try(ref subexpr) => { - Some(convert_try(subexpr, context)) +impl<'a> ChainFormatterVisual<'a> { + fn new(chain: &'a Chain) -> ChainFormatterVisual<'a> { + ChainFormatterVisual { + shared: ChainFormatterShared::new(chain), + offset: 0, } - _ => None, } } -fn convert_try(expr: &ast::Expr, context: &RewriteContext) -> ast::Expr { - match expr.node { - ast::ExprKind::Mac(ref mac) if context.config.use_try_shorthand() => { - if let Some(subexpr) = convert_try_mac(mac, context) { - subexpr - } else { - expr.clone() +impl<'a> ChainFormatter for ChainFormatterVisual<'a> { + fn format_root( + &mut self, + parent: &ChainItem, + context: &RewriteContext<'_>, + shape: Shape, + ) -> Option<()> { + let parent_shape = shape.visual_indent(0); + let mut root_rewrite = parent.rewrite(context, parent_shape)?; + let multiline = root_rewrite.contains('\n'); + self.offset = if multiline { + last_line_width(&root_rewrite).saturating_sub(shape.used_width()) + } else { + trimmed_last_line_width(&root_rewrite) + }; + + if !multiline || parent.kind.is_block_like(context, &root_rewrite) { + let item = &self.shared.children[0]; + if let ChainItemKind::Comment(..) = item.kind { + self.shared.rewrites.push(root_rewrite); + return Some(()); } + let child_shape = parent_shape + .visual_indent(self.offset) + .sub_width(self.offset)?; + let rewrite = item.rewrite(context, child_shape)?; + match wrap_str(rewrite, context.config.max_width(), shape) { + Some(rewrite) => root_rewrite.push_str(&rewrite), + None => { + // We couldn't fit in at the visual indent, try the last + // indent. + let rewrite = item.rewrite(context, parent_shape)?; + root_rewrite.push_str(&rewrite); + self.offset = 0; + } + } + + self.shared.children = &self.shared.children[1..]; } - _ => expr.clone(), + + self.shared.rewrites.push(root_rewrite); + Some(()) } -} -// Rewrite the last element in the chain `expr`. E.g., given `a.b.c` we rewrite -// `.c`. -fn rewrite_chain_subexpr( - expr: &ast::Expr, - span: Span, - context: &RewriteContext, - shape: Shape, -) -> Option { - let rewrite_element = |expr_str: String| { - if expr_str.len() <= shape.width { - Some(expr_str) - } else { - None - } - }; - - match expr.node { - ast::ExprKind::MethodCall(ref segment, ref expressions) => { - let types = match segment.args { - Some(ref params) => match **params { - ast::GenericArgs::AngleBracketed(ref data) => &data.args[..], - _ => &[], - }, - _ => &[], - }; - rewrite_method_call(segment.ident, types, expressions, span, context, shape) - } - ast::ExprKind::Field(ref nested, ref field) => { - let space = if is_tup_field_access(expr) && is_tup_field_access(nested) { - " " - } else { - "" - }; - rewrite_element(format!("{}.{}", space, field.name)) - } - ast::ExprKind::Try(_) => rewrite_element(String::from("?")), - _ => unreachable!(), + fn child_shape(&self, context: &RewriteContext<'_>, shape: Shape) -> Option { + shape + .with_max_width(context.config) + .offset_left(self.offset) + .map(|s| s.visual_indent(0)) } -} -fn is_tup_field_access(expr: &ast::Expr) -> bool { - match expr.node { - ast::ExprKind::Field(_, ref field) => { - field.name.to_string().chars().all(|c| c.is_digit(10)) + fn format_children(&mut self, context: &RewriteContext<'_>, child_shape: Shape) -> Option<()> { + for item in &self.shared.children[..self.shared.children.len() - 1] { + let rewrite = item.rewrite(context, child_shape)?; + self.shared.rewrites.push(rewrite); } - _ => false, + Some(()) } -} -// Determines if we can continue formatting a given expression on the same line. -fn is_continuable(expr: &ast::Expr) -> bool { - match expr.node { - ast::ExprKind::Path(..) => true, - _ => false, + fn format_last_child( + &mut self, + context: &RewriteContext<'_>, + shape: Shape, + child_shape: Shape, + ) -> Option<()> { + self.shared + .format_last_child(false, context, shape, child_shape) } -} -fn is_try(expr: &ast::Expr) -> bool { - match expr.node { - ast::ExprKind::Try(..) => true, - _ => false, + fn join_rewrites(&self, context: &RewriteContext<'_>, child_shape: Shape) -> Option { + self.shared.join_rewrites(context, child_shape) } -} -fn rewrite_method_call( - method_name: ast::Ident, - types: &[ast::GenericArg], - args: &[ptr::P], - span: Span, - context: &RewriteContext, - shape: Shape, -) -> Option { - let (lo, type_str) = if types.is_empty() { - (args[0].span.hi(), String::new()) - } else { - let type_list = types - .iter() - .map(|ty| ty.rewrite(context, shape)) - .collect::>>()?; - - let type_str = format!("::<{}>", type_list.join(", ")); - - (types.last().unwrap().span().hi(), type_str) - }; - - let callee_str = format!(".{}{}", method_name, type_str); - let span = mk_sp(lo, span.hi()); + fn pure_root(&mut self) -> Option { + self.shared.pure_root() + } +} - rewrite_call(context, &callee_str, &args[1..], span, shape) +/// Removes try operators (`?`s) that appear in the given string. If removing +/// them leaves an empty line, remove that line as well unless it is the first +/// line (we need the first newline for detecting pre/post comment). +fn trim_tries(s: &str) -> String { + let mut result = String::with_capacity(s.len()); + let mut line_buffer = String::with_capacity(s.len()); + for (kind, rich_char) in CharClasses::new(s.chars()) { + match rich_char.get_char() { + '\n' => { + if result.is_empty() || !line_buffer.trim().is_empty() { + result.push_str(&line_buffer); + result.push('\n') + } + line_buffer.clear(); + } + '?' if kind == FullCodeCharKind::Normal => continue, + c => line_buffer.push(c), + } + } + if !line_buffer.trim().is_empty() { + result.push_str(&line_buffer); + } + result }