X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=src%2Fchains.rs;h=c7acf19ee0cea6e42bb41891bb371a394b829b23;hb=66c27c9161b2aa70c2902807be12952bd4a0a62b;hp=67390bc6b7cfabb690e172b0c2d6be8d36f23618;hpb=8618a558342e98b57f5b2e4697c615b1d0f9b735;p=rust.git diff --git a/src/chains.rs b/src/chains.rs index 67390bc6b7c..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,31 +49,36 @@ //! //! 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 { +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); @@ -96,71 +91,136 @@ pub fn rewrite_chain(expr: &ast::Expr, context: &RewriteContext, shape: Shape) - chain.rewrite(context, shape) } +#[derive(Debug)] +enum CommentPosition { + Back, + Top, +} + // An expression plus trailing `?`s to be formatted together. #[derive(Debug)] struct ChainItem { - // 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. - expr: ast::Expr, + kind: ChainItemKind, tries: usize, + span: Span, } -impl Rewrite for ChainItem { - fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option { - let rewrite = self.expr.rewrite(context, shape.sub_width(self.tries)?)?; - Some(format!("{}{}", rewrite, "?".repeat(self.tries))) - } +// 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 ChainItem { - // Rewrite the last element in the chain `expr`. E.g., given `a.b.c` we rewrite - // `.c` and any trailing `?`s. - fn rewrite_postfix(&self, context: &RewriteContext, shape: Shape) -> Option { - let shape = shape.sub_width(self.tries)?; - let mut rewrite = match self.expr.node { +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, + } + } + + 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, + } + } + + 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 = match segment.args { - Some(ref params) => match **params { - ast::GenericArgs::AngleBracketed(ref data) => &data.args[..], - _ => &[], - }, - _ => &[], + 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![] }; - Self::rewrite_method_call( - segment.ident, - types, - expressions, - self.expr.span, - context, - shape, - )? + 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, ref field) => { - let space = - if Self::is_tup_field_access(&self.expr) && Self::is_tup_field_access(nested) { - " " - } else { - "" - }; - let result = format!("{}.{}", space, field.name); - if result.len() <= shape.width { - result + 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 { - return None; - } + ChainItemKind::StructField(field) + }; + let span = mk_sp(nested.span.hi(), field.span.hi()); + (kind, span) } - _ => unreachable!(), + 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), }; - rewrite.push_str(&"?".repeat(self.tries)); - Some(rewrite) + + // Remove comments from the span. + let lo = context.snippet_provider.span_before(span, "."); + (kind, mk_sp(lo, span.hi())) } +} - 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)) +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, } } @@ -170,25 +230,20 @@ fn rewrite_method_call( types: &[ast::GenericArg], args: &[ptr::P], span: Span, - context: &RewriteContext, + context: &RewriteContext<'_>, shape: Shape, ) -> Option { - let (lo, type_str) = if types.is_empty() { - (args[0].span.hi(), String::new()) + let type_str = if types.is_empty() { + 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) + format!("::<{}>", type_list.join(", ")) }; - - let callee_str = format!(".{}{}", method_name, type_str); - let span = mk_sp(lo, span.hi()); - + let callee_str = format!(".{}{}", rewrite_ident(context, method_name), type_str); rewrite_call(context, &callee_str, &args[1..], span, shape) } } @@ -200,34 +255,129 @@ struct Chain { } impl Chain { - fn from_ast(expr: &ast::Expr, context: &RewriteContext) -> 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 children = vec![]; + let mut rev_children = vec![]; let mut sub_tries = 0; - for subexpr in subexpr_list { + for subexpr in &subexpr_list { match subexpr.node { ast::ExprKind::Try(_) => sub_tries += 1, _ => { - children.push(ChainItem { - expr: subexpr, - tries: sub_tries, - }); + rev_children.push(ChainItem::new(context, subexpr, sub_tries)); sub_tries = 0; } } } - Chain { - parent: children.pop().unwrap(), - children, + 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, + )); + } + _ => (), + } + } + + 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, + ); + } + + 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 { + 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) { @@ -239,19 +389,21 @@ fn make_subexpr_list(expr: &ast::Expr, context: &RewriteContext) -> Vec Option { + 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::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 { + 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) { @@ -266,21 +418,25 @@ fn convert_try(expr: &ast::Expr, context: &RewriteContext) -> ast::Expr { } impl Rewrite for Chain { - fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option { + 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, + 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 result @ Some(_) = formatter.pure_root() { - return result; + 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); + let child_shape = formatter.child_shape(context, shape)?; formatter.format_children(context, child_shape)?; formatter.format_last_child(context, shape, child_shape)?; @@ -298,26 +454,26 @@ 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 baz would be orphaned and 'float'. + // If `bar` were not part of the root, then foo would be orphaned and 'float'. fn format_root( &mut self, parent: &ChainItem, - context: &RewriteContext, + context: &RewriteContext<'_>, shape: Shape, ) -> Option<()>; - fn child_shape(&self, context: &RewriteContext, shape: Shape) -> Shape; - fn format_children(&mut self, context: &RewriteContext, child_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, + context: &RewriteContext<'_>, shape: Shape, child_shape: Shape, ) -> Option<()>; - fn join_rewrites(&self, context: &RewriteContext, 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; } @@ -360,7 +516,7 @@ fn pure_root(&mut self) -> Option { // 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, @@ -369,7 +525,7 @@ fn pure_root(&mut self) -> Option { // ``` // // A chain without overflowing the last child (in vertical layout): - // ``` + // ```text // parent // .child1 // .child2 @@ -377,8 +533,8 @@ fn pure_root(&mut self) -> Option { // ``` // // 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); @@ -391,31 +547,37 @@ fn pure_root(&mut self) -> Option { fn format_last_child( &mut self, may_extend: bool, - context: &RewriteContext, + context: &RewriteContext<'_>, shape: Shape, child_shape: Shape, ) -> Option<()> { - let last = &self.children[0]; - let extendable = - may_extend && last_line_extendable(&self.rewrites[self.rewrites.len() - 1]); - let prev_last_line_width = last_line_width(&self.rewrites[self.rewrites.len() - 1]); + 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().fold(0, |a, b| a + b.len()) + 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); + } + .saturating_sub(almost_total); - let all_in_one_line = - self.rewrites.iter().all(|s| !s.contains('\n')) && one_line_budget > 0; - let last_shape = if all_in_one_line || extendable { + 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)? }; @@ -424,8 +586,16 @@ fn format_last_child( if all_in_one_line || extendable { // First we try to 'overflow' the last child and see if it looks better than using // vertical layout. - if let Some(one_line_shape) = last_shape.offset_left(almost_total) { - if let Some(rw) = last.rewrite_postfix(context, one_line_shape) { + 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`. @@ -441,7 +611,7 @@ fn format_last_child( // better. let last_shape = child_shape .sub_width(shape.rhs_overhead(context.config) + last.tries)?; - match last.rewrite_postfix(context, last_shape) { + match last.rewrite(context, last_shape) { Some(ref new_rw) if !could_fit_single_line => { last_subexpr_str = Some(new_rw.clone()); } @@ -462,17 +632,18 @@ fn format_last_child( } } - last_subexpr_str = last_subexpr_str.or_else(|| last.rewrite_postfix(context, last_shape)); + let last_shape = if context.use_block_indent() { + last_shape + } else { + child_shape.sub_width(shape.rhs_overhead(context.config) + last.tries)? + }; + + last_subexpr_str = last_subexpr_str.or_else(|| last.rewrite(context, last_shape)); self.rewrites.push(last_subexpr_str?); Some(()) } - fn join_rewrites( - &self, - context: &RewriteContext, - child_shape: Shape, - block_like_iter: impl Iterator, - ) -> Option { + 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("") @@ -481,15 +652,19 @@ fn join_rewrites( if *context.force_one_line_chain.borrow() { return None; } - child_shape.indent.to_string_with_newline(context.config) + child_shape.to_string_with_newline(context.config) }; let mut rewrite_iter = self.rewrites.iter(); let mut result = rewrite_iter.next().unwrap().clone(); - - for (rewrite, prev_is_block_like) in rewrite_iter.zip(block_like_iter) { - if !prev_is_block_like { - result.push_str(&connector); + 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); } @@ -501,46 +676,14 @@ fn join_rewrites( // Formats a chain using block indent. struct ChainFormatterBlock<'a> { shared: ChainFormatterShared<'a>, - // For each rewrite, whether the corresponding item is block-like. - is_block_like: Vec, + root_ends_with_block: bool, } impl<'a> ChainFormatterBlock<'a> { fn new(chain: &'a Chain) -> ChainFormatterBlock<'a> { ChainFormatterBlock { shared: ChainFormatterShared::new(chain), - is_block_like: Vec::with_capacity(chain.children.len() + 1), - } - } - - // 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(..) - | ast::ExprKind::MethodCall(..) - | 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) - | ast::ExprKind::Closure(_, _, _, _, ref expr, _) - | ast::ExprKind::Try(ref expr) - | ast::ExprKind::Yield(Some(ref expr)) => Self::is_block_expr(context, expr, repr), - // This can only be a string lit - ast::ExprKind::Lit(_) => { - repr.contains('\n') && trimmed_last_line_width(repr) <= context.config.tab_spaces() - } - _ => false, + root_ends_with_block: false, } } } @@ -549,47 +692,51 @@ impl<'a> ChainFormatter for ChainFormatterBlock<'a> { fn format_root( &mut self, parent: &ChainItem, - context: &RewriteContext, + context: &RewriteContext<'_>, shape: Shape, ) -> Option<()> { let mut root_rewrite: String = parent.rewrite(context, shape)?; - let mut root_ends_with_block = Self::is_block_expr(context, &parent.expr, &root_rewrite); + 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[self.shared.children.len() - 1]; + let item = &self.shared.children[0]; + if let ChainItemKind::Comment(..) = item.kind { + break; + } let shape = shape.offset_left(root_rewrite.len())?; - match &item.rewrite_postfix(context, shape) { + match &item.rewrite(context, shape) { Some(rewrite) => root_rewrite.push_str(rewrite), None => break, } - root_ends_with_block = Self::is_block_expr(context, &item.expr, &root_rewrite); + root_ends_with_block = last_line_extendable(&root_rewrite); - self.shared.children = &self.shared.children[..self.shared.children.len() - 1]; + self.shared.children = &self.shared.children[1..]; if self.shared.children.is_empty() { break; } } - self.is_block_like.push(root_ends_with_block); self.shared.rewrites.push(root_rewrite); + self.root_ends_with_block = root_ends_with_block; Some(()) } - fn child_shape(&self, context: &RewriteContext, shape: Shape) -> Shape { - if self.is_block_like[0] { - shape - } else { - shape.block_indent(context.config.tab_spaces()) - }.with_max_width(context.config) + 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), + ) } - fn format_children(&mut self, context: &RewriteContext, child_shape: Shape) -> Option<()> { - for item in self.shared.children[1..].iter().rev() { - let rewrite = item.rewrite_postfix(context, child_shape)?; - self.is_block_like - .push(Self::is_block_expr(context, &item.expr, &rewrite)); + 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); } Some(()) @@ -597,7 +744,7 @@ fn format_children(&mut self, context: &RewriteContext, child_shape: Shape) -> O fn format_last_child( &mut self, - context: &RewriteContext, + context: &RewriteContext<'_>, shape: Shape, child_shape: Shape, ) -> Option<()> { @@ -605,9 +752,8 @@ fn format_last_child( .format_last_child(true, context, shape, child_shape) } - fn join_rewrites(&self, context: &RewriteContext, child_shape: Shape) -> Option { - self.shared - .join_rewrites(context, child_shape, self.is_block_like.iter().cloned()) + fn join_rewrites(&self, context: &RewriteContext<'_>, child_shape: Shape) -> Option { + self.shared.join_rewrites(context, child_shape) } fn pure_root(&mut self) -> Option { @@ -618,12 +764,15 @@ fn pure_root(&mut self) -> Option { // 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, } impl<'a> ChainFormatterVisual<'a> { fn new(chain: &'a Chain) -> ChainFormatterVisual<'a> { ChainFormatterVisual { shared: ChainFormatterShared::new(chain), + offset: 0, } } } @@ -632,41 +781,56 @@ impl<'a> ChainFormatter for ChainFormatterVisual<'a> { fn format_root( &mut self, parent: &ChainItem, - context: &RewriteContext, + context: &RewriteContext<'_>, shape: Shape, ) -> Option<()> { - // 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, - } - } - 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 !root_rewrite.contains('\n') && is_continuable(&parent.expr) { - let item = &self.shared.children[self.shared.children.len() - 1]; - let overhead = last_line_width(&root_rewrite); - let shape = parent_shape.offset_left(overhead)?; - let rewrite = item.rewrite_postfix(context, shape)?; - root_rewrite.push_str(&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[..self.shared.children.len() - 1]; + self.shared.children = &self.shared.children[1..]; } self.shared.rewrites.push(root_rewrite); Some(()) } - fn child_shape(&self, context: &RewriteContext, shape: Shape) -> Shape { - shape.visual_indent(0).with_max_width(context.config) + 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 format_children(&mut self, context: &RewriteContext, child_shape: Shape) -> Option<()> { - for item in self.shared.children[1..].iter().rev() { - let rewrite = item.rewrite_postfix(context, child_shape)?; + 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); } Some(()) @@ -674,7 +838,7 @@ fn format_children(&mut self, context: &RewriteContext, child_shape: Shape) -> O fn format_last_child( &mut self, - context: &RewriteContext, + context: &RewriteContext<'_>, shape: Shape, child_shape: Shape, ) -> Option<()> { @@ -682,12 +846,36 @@ fn format_last_child( .format_last_child(false, context, shape, child_shape) } - fn join_rewrites(&self, context: &RewriteContext, child_shape: Shape) -> Option { - self.shared - .join_rewrites(context, child_shape, iter::repeat(false)) + fn join_rewrites(&self, context: &RewriteContext<'_>, child_shape: Shape) -> Option { + self.shared.join_rewrites(context, child_shape) } fn pure_root(&mut self) -> Option { self.shared.pure_root() } } + +/// 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 +}