rust/src/closures.rs

393 lines
13 KiB
Rust
Raw Normal View History

2017-11-13 15:26:33 +13:00
// Copyright 2017 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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use syntax::{ast, ptr};
use syntax::codemap::Span;
use syntax::parse::classify;
use codemap::SpanUtils;
use expr::{block_contains_comment, is_simple_block, is_unsafe_block, rewrite_cond, ToExpr};
2017-11-13 15:26:33 +13:00
use items::{span_hi_for_arg, span_lo_for_arg};
use lists::{definitive_tactic, itemize_list, write_list, DefinitiveListTactic, ListFormatting,
ListTactic, Separator, SeparatorPlace, SeparatorTactic};
use rewrite::{Rewrite, RewriteContext};
use shape::Shape;
use utils::{last_line_width, left_most_sub_expr, stmt_expr};
// This module is pretty messy because of the rules around closures and blocks:
2017-11-13 15:26:33 +13:00
// FIXME - the below is probably no longer true in full.
// * if there is a return type, then there must be braces,
// * given a closure with braces, whether that is parsed to give an inner block
// or not depends on if there is a return type and if there are statements
// in that block,
// * 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.
2017-11-13 15:26:33 +13:00
pub fn rewrite_closure(
capture: ast::CaptureBy,
2018-01-30 22:14:33 +08:00
movability: ast::Movability,
2017-11-13 15:26:33 +13:00
fn_decl: &ast::FnDecl,
body: &ast::Expr,
span: Span,
context: &RewriteContext,
shape: Shape,
) -> Option<String> {
debug!("rewrite_closure {:?}", body);
let (prefix, extra_offset) =
2018-01-30 22:14:33 +08:00
rewrite_closure_fn_decl(capture, movability, fn_decl, body, span, context, shape)?;
2017-11-13 15:26:33 +13:00
// 1 = space between `|...|` and body.
let body_shape = shape.offset_left(extra_offset)?;
if let ast::ExprKind::Block(ref block) = body.node {
// The body of the closure is an empty block.
if block.stmts.is_empty() && !block_contains_comment(block, context.codemap) {
return Some(format!("{} {{}}", prefix));
}
let result = match fn_decl.output {
ast::FunctionRetTy::Default(_) => {
try_rewrite_without_block(body, &prefix, context, shape, body_shape)
}
_ => None,
};
result.or_else(|| {
2017-11-13 16:07:34 +13:00
// Either we require a block, or tried without and failed.
rewrite_closure_block(block, &prefix, context, body_shape)
})
2017-11-13 15:26:33 +13:00
} else {
rewrite_closure_expr(body, &prefix, context, body_shape).or_else(|| {
// The closure originally had a non-block expression, but we can't fit on
// one line, so we'll insert a block.
rewrite_closure_with_block(body, &prefix, context, body_shape)
})
}
}
2017-11-13 16:07:34 +13:00
fn try_rewrite_without_block(
expr: &ast::Expr,
2017-11-13 16:07:34 +13:00
prefix: &str,
context: &RewriteContext,
shape: Shape,
body_shape: Shape,
) -> Option<String> {
let expr = get_inner_expr(expr, prefix, context);
2017-11-13 16:07:34 +13:00
if is_block_closure_forced(context, expr) {
rewrite_closure_with_block(expr, prefix, context, shape)
} else {
rewrite_closure_expr(expr, prefix, context, body_shape)
2017-11-13 16:07:34 +13:00
}
}
fn get_inner_expr<'a>(
expr: &'a ast::Expr,
2017-11-13 16:07:34 +13:00
prefix: &str,
context: &RewriteContext,
) -> &'a ast::Expr {
if let ast::ExprKind::Block(ref block) = expr.node {
if !needs_block(block, prefix, context) {
// block.stmts.len() == 1
if let Some(expr) = stmt_expr(&block.stmts[0]) {
return get_inner_expr(expr, prefix, context);
}
}
2017-11-13 16:07:34 +13:00
}
expr
2017-11-13 16:07:34 +13:00
}
// Figure out if a block is necessary.
fn needs_block(block: &ast::Block, prefix: &str, context: &RewriteContext) -> bool {
is_unsafe_block(block) || block.stmts.len() > 1
2017-11-13 16:07:34 +13:00
|| block_contains_comment(block, context.codemap) || prefix.contains('\n')
}
2017-11-13 15:26:33 +13:00
// Rewrite closure with a single expression wrapping its body with block.
fn rewrite_closure_with_block(
body: &ast::Expr,
prefix: &str,
context: &RewriteContext,
shape: Shape,
) -> Option<String> {
let left_most = left_most_sub_expr(body);
let veto_block = left_most != body && !classify::expr_requires_semi_to_be_stmt(left_most);
if veto_block {
return None;
}
2017-11-13 15:26:33 +13:00
let block = ast::Block {
stmts: vec![
ast::Stmt {
id: ast::NodeId::new(0),
node: ast::StmtKind::Expr(ptr::P(body.clone())),
span: body.span,
},
],
id: ast::NodeId::new(0),
rules: ast::BlockCheckMode::Default,
span: body.span,
recovered: false,
2017-11-13 15:26:33 +13:00
};
2017-11-30 06:40:29 +09:00
let block = ::expr::rewrite_block_with_visitor(context, "", &block, shape, false)?;
Some(format!("{} {}", prefix, block))
2017-11-13 15:26:33 +13:00
}
// Rewrite closure with a single expression without wrapping its body with block.
fn rewrite_closure_expr(
expr: &ast::Expr,
prefix: &str,
context: &RewriteContext,
shape: Shape,
) -> Option<String> {
fn allow_multi_line(expr: &ast::Expr) -> bool {
match expr.node {
ast::ExprKind::Match(..)
| ast::ExprKind::Block(..)
| ast::ExprKind::Catch(..)
| ast::ExprKind::Loop(..)
| ast::ExprKind::Struct(..) => true,
ast::ExprKind::AddrOf(_, ref expr)
| ast::ExprKind::Box(ref expr)
| ast::ExprKind::Try(ref expr)
| ast::ExprKind::Unary(_, ref expr)
| ast::ExprKind::Cast(ref expr, _) => allow_multi_line(expr),
_ => false,
2017-11-13 15:26:33 +13:00
}
}
// When rewriting closure's body without block, we require it to fit in a single line
// unless it is a block-like expression or we are inside macro call.
let veto_multiline = (!allow_multi_line(expr) && !context.inside_macro)
|| context.config.force_multiline_blocks();
expr.rewrite(context, shape)
.and_then(|rw| {
if veto_multiline && rw.contains('\n') {
None
} else {
Some(rw)
}
})
.map(|rw| format!("{} {}", prefix, rw))
2017-11-13 15:26:33 +13:00
}
// Rewrite closure whose body is block.
fn rewrite_closure_block(
block: &ast::Block,
prefix: &str,
context: &RewriteContext,
shape: Shape,
) -> Option<String> {
Some(format!("{} {}", prefix, block.rewrite(context, shape)?))
2017-11-13 15:26:33 +13:00
}
// Return type is (prefix, extra_offset)
fn rewrite_closure_fn_decl(
capture: ast::CaptureBy,
2018-01-30 22:14:33 +08:00
movability: ast::Movability,
2017-11-13 15:26:33 +13:00
fn_decl: &ast::FnDecl,
body: &ast::Expr,
span: Span,
context: &RewriteContext,
shape: Shape,
) -> Option<(String, usize)> {
let mover = if capture == ast::CaptureBy::Value {
"move "
} else {
""
};
2018-01-30 22:14:33 +08:00
let immovable = if movability == ast::Movability::Static {
"static "
} else {
""
};
2017-11-13 15:26:33 +13:00
// 4 = "|| {".len(), which is overconservative when the closure consists of
// a single expression.
2018-01-30 22:14:33 +08:00
let nested_shape = shape
.shrink_left(mover.len() + immovable.len())?
.sub_width(4)?;
2017-11-13 15:26:33 +13:00
// 1 = |
let argument_offset = nested_shape.indent + 1;
let arg_shape = nested_shape.offset_left(1)?.visual_indent(0);
let ret_str = fn_decl.output.rewrite(context, arg_shape)?;
let arg_items = itemize_list(
context.codemap,
fn_decl.inputs.iter(),
"|",
",",
2017-11-13 15:26:33 +13:00
|arg| span_lo_for_arg(arg),
|arg| span_hi_for_arg(context, arg),
|arg| arg.rewrite(context, arg_shape),
context.codemap.span_after(span, "|"),
body.span.lo(),
false,
);
let item_vec = arg_items.collect::<Vec<_>>();
// 1 = space between arguments and return type.
let horizontal_budget = nested_shape
.width
.checked_sub(ret_str.len() + 1)
.unwrap_or(0);
let tactic = definitive_tactic(
&item_vec,
ListTactic::HorizontalVertical,
Separator::Comma,
horizontal_budget,
);
let arg_shape = match tactic {
DefinitiveListTactic::Horizontal => arg_shape.sub_width(ret_str.len() + 1)?,
_ => arg_shape,
};
let fmt = ListFormatting {
2018-01-22 13:05:18 +09:00
tactic,
2017-11-13 15:26:33 +13:00
separator: ",",
trailing_separator: SeparatorTactic::Never,
separator_place: SeparatorPlace::Back,
shape: arg_shape,
ends_with_newline: false,
preserve_newline: true,
config: context.config,
};
let list_str = write_list(&item_vec, &fmt)?;
2018-01-30 22:14:33 +08:00
let mut prefix = format!("{}{}|{}|", immovable, mover, list_str);
2017-11-13 15:26:33 +13:00
if !ret_str.is_empty() {
if prefix.contains('\n') {
prefix.push('\n');
prefix.push_str(&argument_offset.to_string(context.config));
} else {
prefix.push(' ');
}
prefix.push_str(&ret_str);
}
// 1 = space between `|...|` and body.
let extra_offset = last_line_width(&prefix) + 1;
Some((prefix, extra_offset))
}
// Rewriting closure which is placed at the end of the function call's arg.
// Returns `None` if the reformatted closure 'looks bad'.
pub fn rewrite_last_closure(
context: &RewriteContext,
expr: &ast::Expr,
shape: Shape,
) -> Option<String> {
2018-01-30 22:14:33 +08:00
if let ast::ExprKind::Closure(capture, movability, ref fn_decl, ref body, _) = expr.node {
2017-11-13 15:26:33 +13:00
let body = match body.node {
2017-11-20 16:38:29 +09:00
ast::ExprKind::Block(ref block)
if !is_unsafe_block(block) && is_simple_block(block, context.codemap) =>
{
2017-11-13 15:26:33 +13:00
stmt_expr(&block.stmts[0]).unwrap_or(body)
}
_ => body,
};
2018-01-30 22:14:33 +08:00
let (prefix, extra_offset) = rewrite_closure_fn_decl(
capture,
movability,
fn_decl,
body,
expr.span,
context,
shape,
)?;
2017-11-13 15:26:33 +13:00
// If the closure goes multi line before its body, do not overflow the closure.
if prefix.contains('\n') {
return None;
}
let body_shape = shape.offset_left(extra_offset)?;
// We force to use block for the body of the closure for certain kinds of expressions.
if is_block_closure_forced(context, body) {
2017-11-13 15:26:33 +13:00
return rewrite_closure_with_block(body, &prefix, context, body_shape).and_then(
|body_str| {
// If the expression can fit in a single line, we need not force block closure.
if body_str.lines().count() <= 7 {
match rewrite_closure_expr(body, &prefix, context, shape) {
Some(ref single_line_body_str)
if !single_line_body_str.contains('\n') =>
{
Some(single_line_body_str.clone())
}
_ => Some(body_str),
}
} else {
Some(body_str)
}
},
);
}
// When overflowing the closure which consists of a single control flow expression,
// force to use block if its condition uses multi line.
let is_multi_lined_cond = rewrite_cond(context, body, body_shape)
.map(|cond| cond.contains('\n') || cond.len() > body_shape.width)
.unwrap_or(false);
if is_multi_lined_cond {
return rewrite_closure_with_block(body, &prefix, context, body_shape);
}
// Seems fine, just format the closure in usual manner.
return expr.rewrite(context, shape);
}
None
}
/// Returns true if the given vector of arguments has more than one `ast::ExprKind::Closure`.
pub fn args_have_many_closure<T>(args: &[&T]) -> bool
where
T: ToExpr,
{
args.iter()
.filter(|arg| {
arg.to_expr()
.map(|e| match e.node {
ast::ExprKind::Closure(..) => true,
_ => false,
})
.unwrap_or(false)
})
.count() > 1
}
fn is_block_closure_forced(context: &RewriteContext, expr: &ast::Expr) -> bool {
// If we are inside macro, we do not want to add or remove block from closure body.
if context.inside_macro {
false
} else {
is_block_closure_forced_inner(expr)
}
}
fn is_block_closure_forced_inner(expr: &ast::Expr) -> bool {
2017-11-13 15:26:33 +13:00
match expr.node {
2017-11-16 16:42:07 +09:00
ast::ExprKind::If(..)
| ast::ExprKind::IfLet(..)
| ast::ExprKind::While(..)
| ast::ExprKind::WhileLet(..)
| ast::ExprKind::ForLoop(..) => true,
ast::ExprKind::AddrOf(_, ref expr)
| ast::ExprKind::Box(ref expr)
| ast::ExprKind::Try(ref expr)
| ast::ExprKind::Unary(_, ref expr)
| ast::ExprKind::Cast(ref expr, _) => is_block_closure_forced_inner(expr),
2017-11-13 15:26:33 +13:00
_ => false,
}
}