X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=src%2Fchains.rs;h=b7619c0a18af49a65bee7bc253211cad1d9817fe;hb=bb7442802abe354a0ed844ec237cd20969789224;hp=7acb9b9126f471daae475e447f2d75fc7b8fa935;hpb=df4fb8a05b95f770304dae63c0bd90bb7408a232;p=rust.git diff --git a/src/chains.rs b/src/chains.rs index 7acb9b9126f..b7619c0a18a 100644 --- a/src/chains.rs +++ b/src/chains.rs @@ -65,22 +65,23 @@ //! .qux //! ``` +use comment::{rewrite_comment, CharClasses, FullCodeCharKind, RichChar}; use config::IndentStyle; use expr::rewrite_call; +use lists::extract_pre_comment; use macros::convert_try_mac; use rewrite::{Rewrite, RewriteContext}; use shape::Shape; -use spanned::Spanned; +use source_map::SpanUtils; use utils::{ - first_line_width, last_line_extendable, last_line_width, mk_sp, trimmed_last_line_width, - wrap_str, + first_line_width, last_line_extendable, last_line_width, mk_sp, rewrite_ident, + 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 { @@ -96,71 +97,129 @@ 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), + 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) => is_block_expr(context, expr, reps), + ChainItemKind::MethodCall(..) + | ChainItemKind::StructField(..) + | ChainItemKind::TupleField(..) + | 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!(), + _ => 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::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, } } @@ -173,22 +232,17 @@ fn rewrite_method_call( 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) } } @@ -204,25 +258,120 @@ 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. @@ -395,10 +544,9 @@ fn format_last_child( 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 { @@ -410,10 +558,12 @@ fn format_last_child( 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 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 { @@ -426,8 +576,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`. @@ -443,7 +601,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()); } @@ -464,17 +622,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("") @@ -488,10 +647,14 @@ fn join_rewrites( 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); } @@ -503,15 +666,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), + root_ends_with_block: false, } } } @@ -525,44 +687,46 @@ fn format_root( ) -> Option<()> { let mut root_rewrite: String = parent.rewrite(context, shape)?; - let mut root_ends_with_block = 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 = 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) -> Option { Some( - if self.is_block_like[0] { + if self.root_ends_with_block { shape.block_indent(0) } else { shape.block_indent(context.config.tab_spaces()) - }.with_max_width(context.config), + } + .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(is_block_expr(context, &item.expr, &rewrite)); + for item in &self.shared.children[..self.shared.children.len() - 1] { + let rewrite = item.rewrite(context, child_shape)?; self.shared.rewrites.push(rewrite); } Some(()) @@ -579,8 +743,7 @@ fn format_last_child( } fn join_rewrites(&self, context: &RewriteContext, child_shape: Shape) -> Option { - self.shared - .join_rewrites(context, child_shape, self.is_block_like.iter().cloned()) + self.shared.join_rewrites(context, child_shape) } fn pure_root(&mut self) -> Option { @@ -620,24 +783,28 @@ fn format_root( trimmed_last_line_width(&root_rewrite) }; - if !multiline || is_block_expr(context, &parent.expr, &root_rewrite) { - let item = &self.shared.children[self.shared.children.len() - 1]; + 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_postfix(context, child_shape)?; + 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_postfix(context, parent_shape)?; + 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); @@ -652,8 +819,8 @@ fn child_shape(&self, context: &RewriteContext, shape: Shape) -> Option { } 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)?; + for item in &self.shared.children[..self.shared.children.len() - 1] { + let rewrite = item.rewrite(context, child_shape)?; self.shared.rewrites.push(rewrite); } Some(()) @@ -670,8 +837,7 @@ fn format_last_child( } fn join_rewrites(&self, context: &RewriteContext, child_shape: Shape) -> Option { - self.shared - .join_rewrites(context, child_shape, iter::repeat(false)) + self.shared.join_rewrites(context, child_shape) } fn pure_root(&mut self) -> Option { @@ -686,6 +852,7 @@ fn is_block_expr(context: &RewriteContext, expr: &ast::Expr, repr: &str) -> bool ast::ExprKind::Mac(..) | ast::ExprKind::Call(..) | ast::ExprKind::MethodCall(..) + | ast::ExprKind::Array(..) | ast::ExprKind::Struct(..) | ast::ExprKind::While(..) | ast::ExprKind::WhileLet(..) @@ -709,3 +876,28 @@ fn is_block_expr(context: &RewriteContext, expr: &ast::Expr, repr: &str) -> bool _ => false, } } + +/// Remove 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 +}