Merge pull request #2838 from nrc/chains

Refactor chain formatting and fix some bugs
This commit is contained in:
Nick Cameron 2018-07-24 15:50:49 +12:00 committed by GitHub
commit a24df1397e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 714 additions and 468 deletions

View file

@ -422,8 +422,7 @@ fn determine_operation(matches: &Matches) -> Result<Operation, ErrorKind> {
// we will do comparison later, so here tries to canonicalize first
// to get the expected behavior.
p.canonicalize().unwrap_or(p)
})
.collect();
}).collect();
Ok(Operation::Format {
files,

View file

@ -163,8 +163,7 @@ fn format_crate(
if verbosity == Verbosity::Verbose {
println!("[{}] {:?}", t.kind, t.path)
}
})
.map(|t| t.path)
}).map(|t| t.path)
.collect();
run_rustfmt(&files, &rustfmt_args, verbosity)

View file

@ -84,107 +84,277 @@ use syntax::codemap::Span;
use syntax::{ast, ptr};
pub fn rewrite_chain(expr: &ast::Expr, context: &RewriteContext, shape: Shape) -> Option<String> {
debug!("rewrite_chain {:?}", shape);
let total_span = expr.span;
let (parent, subexpr_list) = make_subexpr_list(expr, context);
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();
chain.rewrite(context, shape)
}
// 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,
tries: usize,
}
impl Rewrite for ChainItem {
fn rewrite(&self, context: &RewriteContext, shape: Shape) -> Option<String> {
let rewrite = self.expr.rewrite(context, shape.sub_width(self.tries)?)?;
Some(format!("{}{}", rewrite, "?".repeat(self.tries)))
}
}
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<String> {
let shape = shape.sub_width(self.tries)?;
let mut rewrite = match self.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[..],
_ => &[],
},
_ => &[],
};
Self::rewrite_method_call(
segment.ident,
types,
expressions,
self.expr.span,
context,
shape,
)?
}
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
} else {
return None;
}
}
_ => unreachable!(),
};
rewrite.push_str(&"?".repeat(self.tries));
Some(rewrite)
}
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 rewrite_method_call(
method_name: ast::Ident,
types: &[ast::GenericArg],
args: &[ptr::P<ast::Expr>],
span: Span,
context: &RewriteContext,
shape: Shape,
) -> Option<String> {
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::<Option<Vec<_>>>()?;
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());
rewrite_call(context, &callee_str, &args[1..], span, shape)
}
}
#[derive(Debug)]
struct Chain {
parent: ChainItem,
children: Vec<ChainItem>,
}
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 children = vec![];
let mut sub_tries = 0;
for subexpr in subexpr_list {
match subexpr.node {
ast::ExprKind::Try(_) => sub_tries += 1,
_ => {
children.push(ChainItem {
expr: subexpr,
tries: sub_tries,
});
sub_tries = 0;
}
}
}
Chain {
parent: children.pop().unwrap(),
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<ast::Expr> {
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<ast::Expr> {
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) => {
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<String> {
debug!("rewrite chain {:?} {:?}", self, shape);
let mut formatter = match context.config.indent_style() {
IndentStyle::Block => Box::new(ChainFormatterBlock::new(self)) as Box<ChainFormatter>,
IndentStyle::Visual => Box::new(ChainFormatterVisual::new(self)) as Box<ChainFormatter>,
};
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()`.
let parent_shape = if is_block_expr(context, &parent, "\n") {
match context.config.indent_style() {
IndentStyle::Visual => shape.visual_indent(0),
IndentStyle::Block => shape,
// Root is the parent plus any other chain items placed on the first line to
// avoid an orphan. E.g.,
// ```
// 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<Shape>;
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<String>;
// Returns `Some` if the chain is only a root, None otherwise.
fn pure_root(&mut self) -> Option<String>;
}
// 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<String>,
// 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(),
}
} 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 pure_root(&mut self) -> Option<String> {
if self.children.is_empty() {
assert_eq!(self.rewrites.len(), 1);
Some(self.rewrites.pop().unwrap())
} else {
None
}
} else {
(
chain_indent(context, shape.add_offset(parent_rewrite.len())),
false,
)
};
let other_child_shape = nested_shape.with_max_width(context.config);
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)?,
}
} 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::<Option<Vec<_>>>()?;
// 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)?;
}
// 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:
@ -218,142 +388,305 @@ 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[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]);
// `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)
// 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())
} + 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.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)?
};
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.
if let Some(one_line_shape) = last_shape.offset_left(almost_total) {
if let Some(rw) = last.rewrite_postfix(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_postfix(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
};
last_subexpr_str = last_subexpr_str.or_else(|| last.rewrite_postfix(context, last_shape));
self.rewrites.push(last_subexpr_str?);
Some(())
}
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
{
""
fn join_rewrites(
&self,
context: &RewriteContext,
child_shape: Shape,
block_like_iter: impl Iterator<Item = bool>,
) -> Option<String> {
let connector = if self.fits_single_line {
// Yay, we can put everything on one line.
Cow::from("")
} else {
&connector
// Use new lines.
if *context.force_one_line_chain.borrow() {
return None;
}
child_shape.to_string_with_newline(context.config)
};
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 {
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);
}
result.push_str(&rewrite);
}
Some(result)
}
}
// 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
} else {
false
}
})
// 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<bool>,
}
fn rewrite_try(
expr: &ast::Expr,
try_count: usize,
context: &RewriteContext,
shape: Shape,
) -> Option<String> {
let sub_expr = expr.rewrite(context, shape.sub_width(try_count)?)?;
Some(format!("{}{}", sub_expr, "?".repeat(try_count)))
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),
}
}
}
fn join_rewrites(rewrites: &[String], connector: &str) -> String {
let mut rewrite_iter = rewrites.iter();
let mut result = rewrite_iter.next().unwrap().clone();
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)?;
for rewrite in rewrite_iter {
if rewrite != "?" {
result.push_str(connector);
let mut root_ends_with_block = is_block_expr(context, &parent.expr, &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 shape = shape.offset_left(root_rewrite.len())?;
match &item.rewrite_postfix(context, shape) {
Some(rewrite) => root_rewrite.push_str(rewrite),
None => break,
}
root_ends_with_block = is_block_expr(context, &item.expr, &root_rewrite);
self.shared.children = &self.shared.children[..self.shared.children.len() - 1];
if self.shared.children.is_empty() {
break;
}
}
result.push_str(&rewrite);
self.is_block_like.push(root_ends_with_block);
self.shared.rewrites.push(root_rewrite);
Some(())
}
result
fn child_shape(&self, context: &RewriteContext, shape: Shape) -> Option<Shape> {
Some(
if self.is_block_like[0] {
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(is_block_expr(context, &item.expr, &rewrite));
self.shared.rewrites.push(rewrite);
}
Some(())
}
fn format_last_child(
&mut self,
context: &RewriteContext,
shape: Shape,
child_shape: Shape,
) -> Option<()> {
self.shared
.format_last_child(true, context, shape, child_shape)
}
fn join_rewrites(&self, context: &RewriteContext, child_shape: Shape) -> Option<String> {
self.shared
.join_rewrites(context, child_shape, self.is_block_like.iter().cloned())
}
fn pure_root(&mut self) -> Option<String> {
self.shared.pure_root()
}
}
// 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,
}
}
}
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 || is_block_expr(context, &parent.expr, &root_rewrite) {
let item = &self.shared.children[self.shared.children.len() - 1];
let child_shape = parent_shape
.visual_indent(self.offset)
.sub_width(self.offset)?;
let rewrite = item.rewrite_postfix(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)?;
root_rewrite.push_str(&rewrite);
self.offset = 0;
}
}
self.shared.children = &self.shared.children[..self.shared.children.len() - 1];
}
self.shared.rewrites.push(root_rewrite);
Some(())
}
fn child_shape(&self, context: &RewriteContext, shape: Shape) -> Option<Shape> {
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)?;
self.shared.rewrites.push(rewrite);
}
Some(())
}
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 join_rewrites(&self, context: &RewriteContext, child_shape: Shape) -> Option<String> {
self.shared
.join_rewrites(context, child_shape, iter::repeat(false))
}
fn pure_root(&mut self) -> Option<String> {
self.shared.pure_root()
}
}
// 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')
}
ast::ExprKind::Struct(..)
ast::ExprKind::Mac(..)
| ast::ExprKind::Call(..)
| ast::ExprKind::MethodCall(..)
| ast::ExprKind::Struct(..)
| ast::ExprKind::While(..)
| ast::ExprKind::WhileLet(..)
| ast::ExprKind::If(..)
@ -365,147 +698,14 @@ fn is_block_expr(context: &RewriteContext, expr: &ast::Expr, repr: &str) -> bool
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,
}
}
// 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<ast::Expr>) {
let mut subexpr_list = vec![expr.clone()];
while let Some(subexpr) = pop_expr_chain(subexpr_list.last().unwrap(), context) {
subexpr_list.push(subexpr.clone());
}
let parent = subexpr_list.pop().unwrap();
(parent, subexpr_list)
}
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),
}
}
// 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<ast::Expr> {
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))
}
_ => 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(),
}
}
// 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<String> {
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 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))
| ast::ExprKind::Unary(_, ref expr)
| ast::ExprKind::Closure(_, _, _, _, ref expr, _)
| ast::ExprKind::Try(ref expr)
| ast::ExprKind::Yield(Some(ref expr)) => 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,
}
}
// 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 is_try(expr: &ast::Expr) -> bool {
match expr.node {
ast::ExprKind::Try(..) => true,
_ => false,
}
}
fn rewrite_method_call(
method_name: ast::Ident,
types: &[ast::GenericArg],
args: &[ptr::P<ast::Expr>],
span: Span,
context: &RewriteContext,
shape: Shape,
) -> Option<String> {
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::<Option<Vec<_>>>()?;
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());
rewrite_call(context, &callee_str, &args[1..], span, shape)
}

View file

@ -198,8 +198,7 @@ fn rewrite_closure_expr(
} else {
Some(rw)
}
})
.map(|rw| format!("{} {}", prefix, rw))
}).map(|rw| format!("{} {}", prefix, rw))
}
// Rewrite closure whose body is block.
@ -376,10 +375,8 @@ where
.map(|e| match e.node {
ast::ExprKind::Closure(..) => true,
_ => false,
})
.unwrap_or(false)
})
.count()
}).unwrap_or(false)
}).count()
> 1
}

View file

@ -348,8 +348,7 @@ fn rewrite_comment_inner(
}
line
})
.map(|s| left_trim_comment_line(s, &style))
}).map(|s| left_trim_comment_line(s, &style))
.map(|(line, has_leading_whitespace)| {
if orig.starts_with("/*") && line_breaks == 0 {
(
@ -517,8 +516,7 @@ fn trim_custom_comment_prefix(s: &str) -> String {
} else {
line
}
})
.collect::<Vec<_>>()
}).collect::<Vec<_>>()
.join("\n")
}
@ -606,8 +604,7 @@ fn light_rewrite_comment(
};
// Preserve markdown's double-space line break syntax in doc comment.
trim_right_unless_two_whitespaces(left_trimmed, is_doc_comment)
})
.collect();
}).collect();
Some(lines.join(&format!("\n{}", offset.to_string(config))))
}
@ -1341,8 +1338,7 @@ mod test {
.filter_map(|(s, c)| match s {
FullCodeCharKind::Normal | FullCodeCharKind::InString => Some(c),
_ => None,
})
.collect()
}).collect()
}
#[test]

View file

@ -322,8 +322,7 @@ impl IgnoreList {
path.push(s);
path
}
})
.collect();
}).collect();
}
fn skip_file_inner(&self, file: &Path) -> bool {

View file

@ -39,9 +39,9 @@ use spanned::Spanned;
use string::{rewrite_string, StringFormat};
use types::{can_be_overflowed_type, rewrite_path, PathContext};
use utils::{
colon_spaces, contains_skip, count_newlines, first_line_width, inner_attributes,
last_line_extendable, last_line_width, mk_sp, outer_attributes, ptr_vec_to_ref_vec,
semicolon_for_stmt, wrap_str,
colon_spaces, contains_skip, count_newlines, first_line_ends_with, first_line_width,
inner_attributes, last_line_extendable, last_line_width, mk_sp, outer_attributes,
ptr_vec_to_ref_vec, semicolon_for_stmt, wrap_str,
};
use vertical::rewrite_with_alignment;
use visitor::FmtVisitor;
@ -1212,8 +1212,7 @@ fn rewrite_string_lit(context: &RewriteContext, span: Span, shape: Shape) -> Opt
new_indent.to_string(context.config),
line.trim_left()
)
})
.collect::<Vec<_>>()
}).collect::<Vec<_>>()
.join("\n")
.trim_left(),
);
@ -1953,6 +1952,9 @@ pub fn prefer_next_line(orig_rhs: &str, next_line_rhs: &str, rhs_tactics: RhsTac
rhs_tactics == RhsTactics::ForceNextLineWithoutIndent
|| !next_line_rhs.contains('\n')
|| count_newlines(orig_rhs) > count_newlines(next_line_rhs) + 1
|| first_line_ends_with(orig_rhs, '(') && !first_line_ends_with(next_line_rhs, '(')
|| first_line_ends_with(orig_rhs, '{') && !first_line_ends_with(next_line_rhs, '{')
|| first_line_ends_with(orig_rhs, '[') && !first_line_ends_with(next_line_rhs, '[')
}
fn rewrite_expr_addrof(

View file

@ -246,8 +246,7 @@ impl FormattingError {
fl.file
.get_line(fl.lines[0].line_index)
.map(|l| l.into_owned())
})
.unwrap_or_else(|| String::new()),
}).unwrap_or_else(|| String::new()),
}
}

View file

@ -46,8 +46,7 @@ fn prune_files(files: Vec<&str>) -> Vec<&str> {
return true;
}
pruned_prefixes.iter().all(|pp| !f.starts_with(pp))
})
.collect()
}).collect()
}
fn git_diff(commits: &str) -> String {

View file

@ -344,8 +344,7 @@ impl UseTree {
.zip(items.into_iter())
.map(|(t, list_item)| {
Self::from_ast(context, &t.0, Some(list_item), None, None, None)
})
.collect(),
}).collect(),
));
}
UseTreeKind::Simple(ref rename, ..) => {

View file

@ -1669,7 +1669,7 @@ fn rewrite_static(
&**expr,
Shape::legacy(remaining_width, offset.block_only()),
).and_then(|res| recover_comment_removed(res, static_parts.span, context))
.map(|s| if s.ends_with(';') { s } else { s + ";" })
.map(|s| if s.ends_with(';') { s } else { s + ";" })
} else {
Some(format!("{}{};", prefix, ty_str))
}
@ -2783,17 +2783,15 @@ impl Rewrite for ast::ForeignItem {
let span = mk_sp(self.span.lo(), self.span.hi() - BytePos(1));
let item_str = match self.node {
ast::ForeignItemKind::Fn(ref fn_decl, ref generics) => {
rewrite_fn_base(
context,
shape.indent,
self.ident,
&FnSig::new(fn_decl, generics, self.vis.clone()),
span,
false,
false,
).map(|(s, _)| format!("{};", s))
}
ast::ForeignItemKind::Fn(ref fn_decl, ref generics) => rewrite_fn_base(
context,
shape.indent,
self.ident,
&FnSig::new(fn_decl, generics, self.vis.clone()),
span,
false,
false,
).map(|(s, _)| format!("{};", s)),
ast::ForeignItemKind::Static(ref ty, is_mutable) => {
// FIXME(#21): we're dropping potential comments in between the
// function keywords here.

View file

@ -1115,8 +1115,7 @@ fn indent_macro_snippet(
};
trimmed_lines.push((trimmed, line, prefix_space_width));
prefix_space_width
})
.min()?;
}).min()?;
Some(
first_line + "\n" + &trimmed_lines
@ -1132,8 +1131,7 @@ fn indent_macro_snippet(
}
None => String::new(),
},
)
.collect::<Vec<_>>()
).collect::<Vec<_>>()
.join("\n"),
)
}
@ -1296,8 +1294,7 @@ impl MacroBranch {
}
(s + l + "\n", !kind.is_string() || l.ends_with('\\'))
},
)
.0;
).0;
// Undo our replacement of macro variables.
// FIXME: this could be *much* more efficient.

View file

@ -125,7 +125,7 @@ fn rewrite_pairs_multiline<T: Rewrite>(
IndentStyle::Visual => shape.visual_indent(0),
IndentStyle::Block => shape.block_indent(context.config.tab_spaces()),
}).with_max_width(&context.config)
.sub_width(rhs_offset)?;
.sub_width(rhs_offset)?;
let indent_str = nested_shape.indent.to_string_with_newline(context.config);
let mut result = String::new();

View file

@ -151,8 +151,7 @@ fn rewrite_reorderable_items(
.map(|use_tree| ListItem {
item: use_tree.rewrite_top_level(context, nested_shape),
..use_tree.list_item.unwrap_or_else(ListItem::empty)
})
.collect();
}).collect();
wrap_reorderable_items(context, &item_vec, nested_shape)
}
@ -249,8 +248,7 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
last = current;
in_same_group
})
})
.count();
}).count();
let items = &items[..item_length];
let at_least_one_in_file_lines = items

View file

@ -193,13 +193,15 @@ where
W: Write,
{
for mismatch in diff {
let (num_removed, num_added) = mismatch.lines.iter().fold((0, 0), |(rem, add), line| {
match *line {
DiffLine::Context(_) => panic!("No Context expected"),
DiffLine::Expected(_) => (rem, add + 1),
DiffLine::Resulting(_) => (rem + 1, add),
}
});
let (num_removed, num_added) =
mismatch
.lines
.iter()
.fold((0, 0), |(rem, add), line| match *line {
DiffLine::Context(_) => panic!("No Context expected"),
DiffLine::Expected(_) => (rem, add + 1),
DiffLine::Resulting(_) => (rem + 1, add),
});
// Write a header with enough information to separate the modified lines.
writeln!(
out,

View file

@ -274,6 +274,12 @@ impl Shape {
);
Shape { width, ..*self }
}
pub fn to_string_with_newline(&self, config: &Config) -> Cow<'static, str> {
let mut offset_indent = self.indent;
offset_indent.alignment = self.offset;
offset_indent.to_string_inner(config, 0)
}
}
#[cfg(test)]

View file

@ -514,8 +514,7 @@ fn read_significant_comments(file_name: &Path) -> HashMap<String, String> {
.to_owned(),
)
})
})
.collect()
}).collect()
}
// Compare output to input.
@ -884,8 +883,8 @@ fn configuration_snippet_tests() {
fs::File::open(Path::new(CONFIGURATIONS_FILE_NAME))
.expect(&format!("Couldn't read file {}", CONFIGURATIONS_FILE_NAME)),
).lines()
.map(|l| l.unwrap())
.enumerate();
.map(|l| l.unwrap())
.enumerate();
let mut code_blocks: Vec<ConfigCodeBlock> = Vec::new();
let mut hash_set = Config::hash_set();
@ -961,5 +960,5 @@ fn verify_check_works() {
"--check",
temp_file.path.to_str().unwrap(),
]).succeeds()
.unwrap();
.unwrap();
}

View file

@ -548,7 +548,8 @@ impl Rewrite for ast::GenericParam {
};
result.push_str(eq_str);
let budget = shape.width.checked_sub(result.len())?;
let rewrite = def.rewrite(context, Shape::legacy(budget, shape.indent + result.len()))?;
let rewrite =
def.rewrite(context, Shape::legacy(budget, shape.indent + result.len()))?;
result.push_str(&rewrite);
}
@ -793,8 +794,7 @@ fn rewrite_lifetime_param(
.filter(|p| match p.kind {
ast::GenericParamKind::Lifetime => true,
_ => false,
})
.map(|lt| lt.rewrite(context, shape))
}).map(|lt| lt.rewrite(context, shape))
.collect::<Option<Vec<_>>>()?
.join(", ");
if result.is_empty() {

View file

@ -382,6 +382,7 @@ pub fn colon_spaces(before: bool, after: bool) -> &'static str {
}
}
#[inline]
pub fn left_most_sub_expr(e: &ast::Expr) -> &ast::Expr {
match e.node {
ast::ExprKind::Call(ref e, _)
@ -398,6 +399,12 @@ pub fn left_most_sub_expr(e: &ast::Expr) -> &ast::Expr {
}
}
#[inline]
pub fn starts_with_newline(s: &str) -> bool {
s.starts_with('\n') || s.starts_with("\r\n")
}
#[inline]
pub fn first_line_ends_with(s: &str, c: char) -> bool {
s.lines().next().map_or(false, |l| l.ends_with(c))
}

View file

@ -200,14 +200,12 @@ fn struct_field_prefix_max_min_width<T: AlignedItem>(
Some(field_str.len())
}
})
})
.fold(Some((0, ::std::usize::MAX)), |acc, len| match (acc, len) {
}).fold(Some((0, ::std::usize::MAX)), |acc, len| match (acc, len) {
(Some((max_len, min_len)), Some(len)) => {
Some((cmp::max(max_len, len), cmp::min(min_len, len)))
}
_ => None,
})
.unwrap_or((0, 0))
}).unwrap_or((0, 0))
}
fn rewrite_aligned_items_inner<T: AlignedItem>(

View file

@ -228,3 +228,34 @@ fn issue2415() {
})()
.unwrap_or_else(|_: Box<::std::error::Error>| String::from(""));
}
impl issue_2786 {
fn thing(&self) {
foo(|a| {
println!("a");
println!("b");
}).bar(|c| {
println!("a");
println!("b");
})
.baz(|c| {
println!("a");
println!("b");
})
}
}
fn issue_2773() {
let bar = Some(0);
bar.or_else(|| {
// do stuff
None
}).or_else(|| {
// do other stuff
None
})
.and_then(|val| {
// do this stuff
None
});
}

View file

@ -15,16 +15,16 @@ fn main() {
// Test case where first chain element isn't a path, but is shorter than
// the size of a tab.
x().y(|| match cond() {
true => (),
false => (),
});
true => (),
false => (),
});
loong_func().quux(move || if true { 1 } else { 2 });
some_fuuuuuuuuunction().method_call_a(aaaaa, bbbbb, |c| {
let x = c;
x
});
let x = c;
x
});
some_fuuuuuuuuunction().method_call_a(aaaaa, bbbbb, |c| {
let x = c;
@ -59,7 +59,7 @@ fn floaters() {
let x = Foo { field1: val1,
field2: val2, }.method_call()
.method_call();
.method_call();
let y = if cond { val1 } else { val2 }.method_call();
@ -80,7 +80,7 @@ fn floaters() {
} else {
none();
}.bar()
.baz();
.baz();
Foo { x: val }.baz(|| {
force();
@ -90,10 +90,10 @@ fn floaters() {
Foo { y: i_am_multi_line,
z: ok, }.baz(|| {
force();
multiline();
})
.quux();
force();
multiline();
})
.quux();
a + match x {
true => "yay!",
@ -137,9 +137,9 @@ fn issue1434() {
for _ in 0..100 {
let prototype_id =
PrototypeIdData::from_reader::<_, B>(&mut self.file_cursor).chain_err(|| {
format!("could not read prototype ID at offset {:#010x}",
current_offset)
})?;
format!("could not read prototype ID at offset {:#010x}",
current_offset)
})?;
}
}

View file

@ -38,8 +38,7 @@ fn main() {
.method_call_a(aaaaa, bbbbb, |c| {
let x = c;
x
})
.method_call_b(aaaaa, bbbbb, |c| {
}).method_call_b(aaaaa, bbbbb, |c| {
let x = c;
x
});
@ -65,8 +64,7 @@ fn main() {
.map(|x| {
x += 1;
x
})
.filter(some_mod::some_filter)
}).filter(some_mod::some_filter)
}
fn floaters() {
@ -79,7 +77,7 @@ fn floaters() {
field1: val1,
field2: val2,
}.method_call()
.method_call();
.method_call();
let y = if cond {
val1
@ -106,15 +104,14 @@ fn floaters() {
} else {
none();
}.bar()
.baz();
.baz();
Foo {
x: val,
}.baz(|| {
force();
multiline();
})
.quux();
}).quux();
Foo {
y: i_am_multi_line,
@ -122,8 +119,7 @@ fn floaters() {
}.baz(|| {
force();
multiline();
})
.quux();
}).quux();
a + match x {
true => "yay!",
@ -238,8 +234,7 @@ impl Foo {
}
}
})
})
.collect();
}).collect();
}
}
@ -255,3 +250,32 @@ fn issue2415() {
})().ok_or("")?)
})().unwrap_or_else(|_: Box<::std::error::Error>| String::from(""));
}
impl issue_2786 {
fn thing(&self) {
foo(|a| {
println!("a");
println!("b");
}).bar(|c| {
println!("a");
println!("b");
}).baz(|c| {
println!("a");
println!("b");
})
}
}
fn issue_2773() {
let bar = Some(0);
bar.or_else(|| {
// do stuff
None
}).or_else(|| {
// do other stuff
None
}).and_then(|val| {
// do this stuff
None
});
}

View file

@ -138,18 +138,20 @@ fn issue470() {
{
{
{
let explicit_arg_decls = explicit_arguments.into_iter().enumerate().map(
|(index, (ty, pattern))| {
let lvalue = Lvalue::Arg(index as u32);
block = this.pattern(
block,
argument_extent,
hair::PatternRef::Hair(pattern),
&lvalue,
);
ArgDecl { ty: ty }
},
);
let explicit_arg_decls =
explicit_arguments
.into_iter()
.enumerate()
.map(|(index, (ty, pattern))| {
let lvalue = Lvalue::Arg(index as u32);
block = this.pattern(
block,
argument_extent,
hair::PatternRef::Hair(pattern),
&lvalue,
);
ArgDecl { ty: ty }
});
}
}
}
@ -169,8 +171,7 @@ fn issue1329() {
.map(|x| {
x += 1;
x
})
.filter
}).filter
}
fn issue325() {

View file

@ -117,8 +117,7 @@ impl Cursor {
debug_assert_eq!(n, -1);
None
}
})
.or_else(|| {
}).or_else(|| {
let canonical = self.canonical();
if canonical != *self {
canonical.num_template_args()

View file

@ -141,8 +141,7 @@ fn issue_1450() {
Relaxed,
Release,
Relaxed,
)
.is_ok()
).is_ok()
{
return;
}

View file

@ -5,7 +5,7 @@ fn floaters() {
field1: val1,
field2: val2,
}.method_call()
.method_call();
.method_call();
let y = if cond {
val1

View file

@ -5,7 +5,7 @@ fn floaters() {
field1: val1,
field2: val2,
}.method_call()
.method_call();
.method_call();
let y = if cond { val1 } else { val2 }.method_call();

View file

@ -56,8 +56,8 @@ fn bar() {}
/// .boxed(),
/// ]
/// }).bind("127.0.0.1:8080")
/// .unwrap()
/// .run()
/// .unwrap()
/// .run()
/// # });
/// }
/// ```

View file

@ -183,8 +183,7 @@ fn issue_1885() {
chan_select! {
rx.recv() => {}
}
})
.collect::<Vec<_>>();
}).collect::<Vec<_>>();
}
fn issue_1917() {

View file

@ -381,8 +381,7 @@ fn issue1456() {
.iter()
.map(|node| {
XPathNodeReader::new(node, &context).and_then(|r| ArtistRef::from_xml(&r))
})
.collect();
}).collect();
res?
}
_ => Vec::new(),