Merge pull request #926 from rust-lang-nursery/closures

Be careful about where we change braces in closures
This commit is contained in:
Nick Cameron 2016-04-14 09:32:30 +12:00
commit 14775eb046
6 changed files with 134 additions and 63 deletions

View File

@ -21,7 +21,7 @@ use lists::{write_list, itemize_list, ListFormatting, SeparatorTactic, ListTacti
DefinitiveListTactic, definitive_tactic, ListItem, format_item_list};
use string::{StringFormat, rewrite_string};
use utils::{CodeMapSpanUtils, extra_offset, last_line_width, wrap_str, binary_search,
first_line_width, semicolon_for_stmt, trimmed_last_line_width};
first_line_width, semicolon_for_stmt, trimmed_last_line_width, left_most_sub_expr};
use visitor::FmtVisitor;
use config::{Config, StructLitStyle, MultilineStyle};
use comment::{FindUncommented, rewrite_comment, contains_comment, recover_comment_removed};
@ -32,6 +32,7 @@ use macros::rewrite_macro;
use syntax::{ast, ptr};
use syntax::codemap::{CodeMap, Span, BytePos, mk_sp};
use syntax::parse::classify;
use syntax::visit::Visitor;
impl Rewrite for ast::Expr {
@ -308,8 +309,13 @@ pub fn rewrite_array<'a, I>(expr_iter: I,
Some(format!("[{}]", list_str))
}
// This functions is pretty messy because of the wrapping and unwrapping of
// expressions into and from blocks. See rust issue #27872.
// This functions is pretty messy because of the rules around closures and blocks:
// * the body of a closure is represented by an ast::Block, but that does not
// imply there are `{}` (unless the block is empty) (see rust issue #27872),
// * if there is a return type, then there must be braces,
// * if the first expression in the body ends with a block (i.e., is a
// statement without needing a semi-colon), then adding or removing braces
// can change whether it is treated as an expression or statement.
fn rewrite_closure(capture: ast::CaptureBy,
fn_decl: &ast::FnDecl,
body: &ast::Block,
@ -331,6 +337,8 @@ fn rewrite_closure(capture: ast::CaptureBy,
// 1 = |
let argument_offset = offset + 1;
let ret_str = try_opt!(fn_decl.output.rewrite(context, budget, argument_offset));
let force_block = !ret_str.is_empty();
// 1 = space between arguments and return type.
let horizontal_budget = budget.checked_sub(ret_str.len() + 1).unwrap_or(0);
@ -371,51 +379,91 @@ fn rewrite_closure(capture: ast::CaptureBy,
prefix.push_str(&ret_str);
}
// Try to format closure body as a single line expression without braces.
if is_simple_block(body, context.codemap) && !prefix.contains('\n') {
let (spacer, closer) = if ret_str.is_empty() {
(" ", "")
} else {
(" { ", " }")
};
let expr = body.expr.as_ref().unwrap();
// All closure bodies are blocks in the eyes of the AST, but we may not
// want to unwrap them when they only contain a single expression.
let inner_expr = match expr.node {
ast::ExprKind::Block(ref inner) if inner.stmts.is_empty() && inner.expr.is_some() &&
inner.rules == ast::BlockCheckMode::Default => {
inner.expr.as_ref().unwrap()
assert!(body.stmts.is_empty(),
"unexpected statements in closure: `{}`",
context.snippet(span));
if body.expr.is_none() {
return Some(format!("{} {{}}", prefix));
}
// 1 = space between `|...|` and body.
let extra_offset = extra_offset(&prefix, offset) + 1;
let budget = try_opt!(width.checked_sub(extra_offset));
// This is where we figure out whether to use braces or not.
let mut had_braces = false;
let mut inner_block = body;
if let ast::ExprKind::Block(ref inner) = inner_block.expr.as_ref().unwrap().node {
had_braces = true;
inner_block = inner;
};
assert!(!force_block || !had_braces,
"Closure requires braces, but they weren't present. How did this parse? `{}`",
context.snippet(span));
let try_single_line = is_simple_block(inner_block, context.codemap) &&
inner_block.rules == ast::BlockCheckMode::Default;
if try_single_line && !force_block {
let must_preserve_braces =
!classify::expr_requires_semi_to_be_stmt(left_most_sub_expr(inner_block.expr
.as_ref()
.unwrap()));
if !(must_preserve_braces && had_braces) &&
(must_preserve_braces || !prefix.contains('\n')) {
// If we got here, then we can try to format without braces.
let inner_expr = inner_block.expr.as_ref().unwrap();
let mut rewrite = inner_expr.rewrite(context, budget, offset + extra_offset);
if must_preserve_braces {
// If we are here, then failure to rewrite is unacceptable.
if rewrite.is_none() {
return None;
}
} else {
// Checks if rewrite succeeded and fits on a single line.
rewrite = and_one_line(rewrite);
}
_ => expr,
};
let extra_offset = extra_offset(&prefix, offset) + spacer.len();
let budget = try_opt!(width.checked_sub(extra_offset + closer.len()));
let rewrite = inner_expr.rewrite(context, budget, offset + extra_offset);
if let Some(rewrite) = rewrite {
return Some(format!("{} {}", prefix, rewrite));
}
}
}
// If we fell through the above block, then we need braces, but we might
// still prefer a one-liner (we might also have fallen through because of
// lack of space).
if try_single_line && !prefix.contains('\n') {
let inner_expr = inner_block.expr.as_ref().unwrap();
// 4 = braces and spaces.
let mut rewrite = inner_expr.rewrite(context, budget - 4, offset + extra_offset);
// Checks if rewrite succeeded and fits on a single line.
let accept_rewrite = rewrite.as_ref().map_or(false, |result| !result.contains('\n'));
rewrite = and_one_line(rewrite);
if accept_rewrite {
return Some(format!("{}{}{}{}", prefix, spacer, rewrite.unwrap(), closer));
if let Some(rewrite) = rewrite {
return Some(format!("{} {{ {} }}", prefix, rewrite));
}
}
// We couldn't format the closure body as a single line expression; fall
// back to block formatting.
let body_rewrite = body.expr
.as_ref()
.and_then(|body_expr| {
if let ast::ExprKind::Block(ref inner) = body_expr.node {
Some(inner.rewrite(&context, 2, Indent::empty()))
} else {
None
}
})
.unwrap_or_else(|| body.rewrite(&context, 2, Indent::empty()));
let body_rewrite = inner_block.rewrite(&context, budget, Indent::empty());
Some(format!("{} {}", prefix, try_opt!(body_rewrite)))
}
fn and_one_line(x: Option<String>) -> Option<String> {
x.and_then(|x| if x.contains('\n') {
None
} else {
Some(x)
})
}
fn nop_block_collapse(block_str: Option<String>, budget: usize) -> Option<String> {
block_str.map(|block_str| {
if block_str.starts_with("{") && budget >= 2 &&

View File

@ -334,3 +334,22 @@ fn bin_search_test() {
assert_eq!(Some(()), binary_search(4, 125, &closure));
assert_eq!(None, binary_search(6, 100, &closure));
}
pub fn left_most_sub_expr(e: &ast::Expr) -> &ast::Expr {
match e.node {
ast::ExprKind::InPlace(ref e, _) |
ast::ExprKind::Call(ref e, _) |
ast::ExprKind::Binary(_, ref e, _) |
ast::ExprKind::Cast(ref e, _) |
ast::ExprKind::Type(ref e, _) |
ast::ExprKind::Assign(ref e, _) |
ast::ExprKind::AssignOp(_, ref e, _) |
ast::ExprKind::Field(ref e, _) |
ast::ExprKind::TupField(ref e, _) |
ast::ExprKind::Index(ref e, _) |
ast::ExprKind::Range(Some(ref e), _, _) => left_most_sub_expr(e),
// FIXME needs Try in Syntex
// ast::ExprKind::Try(ref f) => left_most_sub_expr(e),
_ => e,
}
}

View File

@ -50,3 +50,10 @@ fn issue311() {
(func)(0.0);
}
fn issue863() {
let closure = |x| match x {
0 => true,
_ => false,
} == true;
}

View File

@ -9,20 +9,16 @@ fn main() {
.ddddddddddddddddddddddddddd
.eeeeeeee();
x().y(|| {
match cond() {
true => (),
false => (),
}
x().y(|| match cond() {
true => (),
false => (),
});
loong_func()
.quux(move || {
if true {
1
} else {
2
}
.quux(move || if true {
1
} else {
2
});
fffffffffffffffffffffffffffffffffff(a,

View File

@ -16,19 +16,15 @@ 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 => (),
}
x().y(|| match cond() {
true => (),
false => (),
});
loong_func().quux(move || {
if true {
1
} else {
2
}
loong_func().quux(move || if true {
1
} else {
2
});
some_fuuuuuuuuunction().method_call_a(aaaaa, bbbbb, |c| {

View File

@ -26,12 +26,10 @@ fn main() {
}
};
let block_me = |field| {
if true_story() {
1
} else {
2
}
let block_me = |field| if true_story() {
1
} else {
2
};
let unblock_me = |trivial| closure();
@ -77,3 +75,10 @@ fn issue311() {
(func)(0.0);
}
fn issue863() {
let closure = |x| match x {
0 => true,
_ => false,
} == true;
}