| use syntax::source_map::Span; |
| use syntax::{ast, ptr}; |
| |
| use crate::config::lists::*; |
| use crate::config::Version; |
| use crate::expr::{block_contains_comment, is_simple_block, is_unsafe_block, rewrite_cond}; |
| use crate::items::{span_hi_for_param, span_lo_for_param}; |
| use crate::lists::{definitive_tactic, itemize_list, write_list, ListFormatting, Separator}; |
| use crate::overflow::OverflowableItem; |
| use crate::rewrite::{Rewrite, RewriteContext}; |
| use crate::shape::Shape; |
| use crate::source_map::SpanUtils; |
| use crate::utils::{last_line_width, left_most_sub_expr, stmt_expr, NodeIdExt}; |
| |
| // This module is pretty messy because of the rules around closures and blocks: |
| // 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. |
| |
| pub(crate) fn rewrite_closure( |
| capture: ast::CaptureBy, |
| is_async: &ast::IsAsync, |
| movability: ast::Movability, |
| fn_decl: &ast::FnDecl, |
| body: &ast::Expr, |
| span: Span, |
| context: &RewriteContext<'_>, |
| shape: Shape, |
| ) -> Option<String> { |
| debug!("rewrite_closure {:?}", body); |
| |
| let (prefix, extra_offset) = rewrite_closure_fn_decl( |
| capture, is_async, movability, fn_decl, body, span, context, shape, |
| )?; |
| // 1 = space between `|...|` and body. |
| let body_shape = shape.offset_left(extra_offset)?; |
| |
| if let ast::ExprKind::Block(ref block, _) = body.kind { |
| // The body of the closure is an empty block. |
| if block.stmts.is_empty() && !block_contains_comment(block, context.source_map) { |
| return body |
| .rewrite(context, shape) |
| .map(|s| format!("{} {}", prefix, s)); |
| } |
| |
| let result = match fn_decl.output { |
| ast::FunctionRetTy::Default(_) if !context.inside_macro() => { |
| try_rewrite_without_block(body, &prefix, context, shape, body_shape) |
| } |
| _ => None, |
| }; |
| |
| result.or_else(|| { |
| // Either we require a block, or tried without and failed. |
| rewrite_closure_block(block, &prefix, context, body_shape) |
| }) |
| } 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) |
| }) |
| } |
| } |
| |
| fn try_rewrite_without_block( |
| expr: &ast::Expr, |
| prefix: &str, |
| context: &RewriteContext<'_>, |
| shape: Shape, |
| body_shape: Shape, |
| ) -> Option<String> { |
| let expr = get_inner_expr(expr, prefix, context); |
| |
| if is_block_closure_forced(context, expr) { |
| rewrite_closure_with_block(expr, prefix, context, shape) |
| } else { |
| rewrite_closure_expr(expr, prefix, context, body_shape) |
| } |
| } |
| |
| fn get_inner_expr<'a>( |
| expr: &'a ast::Expr, |
| prefix: &str, |
| context: &RewriteContext<'_>, |
| ) -> &'a ast::Expr { |
| if let ast::ExprKind::Block(ref block, _) = expr.kind { |
| 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); |
| } |
| } |
| } |
| |
| expr |
| } |
| |
| // 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 |
| || block_contains_comment(block, context.source_map) |
| || prefix.contains('\n') |
| } |
| |
| fn veto_block(e: &ast::Expr) -> bool { |
| match e.kind { |
| ast::ExprKind::Call(..) |
| | ast::ExprKind::Binary(..) |
| | ast::ExprKind::Cast(..) |
| | ast::ExprKind::Type(..) |
| | ast::ExprKind::Assign(..) |
| | ast::ExprKind::AssignOp(..) |
| | ast::ExprKind::Field(..) |
| | ast::ExprKind::Index(..) |
| | ast::ExprKind::Range(..) |
| | ast::ExprKind::Try(..) => true, |
| _ => false, |
| } |
| } |
| |
| // 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 = veto_block(body) && !expr_requires_semi_to_be_stmt(left_most); |
| if veto_block { |
| return None; |
| } |
| |
| let block = ast::Block { |
| stmts: vec![ast::Stmt { |
| id: ast::NodeId::root(), |
| kind: ast::StmtKind::Expr(ptr::P(body.clone())), |
| span: body.span, |
| }], |
| id: ast::NodeId::root(), |
| rules: ast::BlockCheckMode::Default, |
| span: body.span, |
| }; |
| let block = |
| crate::expr::rewrite_block_with_visitor(context, "", &block, None, None, shape, false)?; |
| Some(format!("{} {}", prefix, block)) |
| } |
| |
| // 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.kind { |
| ast::ExprKind::Match(..) |
| | ast::ExprKind::Block(..) |
| | ast::ExprKind::TryBlock(..) |
| | 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, |
| } |
| } |
| |
| // 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)) |
| } |
| |
| // 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)?)) |
| } |
| |
| // Return type is (prefix, extra_offset) |
| fn rewrite_closure_fn_decl( |
| capture: ast::CaptureBy, |
| asyncness: &ast::IsAsync, |
| movability: ast::Movability, |
| fn_decl: &ast::FnDecl, |
| body: &ast::Expr, |
| span: Span, |
| context: &RewriteContext<'_>, |
| shape: Shape, |
| ) -> Option<(String, usize)> { |
| let is_async = if asyncness.is_async() { "async " } else { "" }; |
| let mover = if capture == ast::CaptureBy::Value { |
| "move " |
| } else { |
| "" |
| }; |
| let immovable = if movability == ast::Movability::Static { |
| "static " |
| } else { |
| "" |
| }; |
| // 4 = "|| {".len(), which is overconservative when the closure consists of |
| // a single expression. |
| let nested_shape = shape |
| .shrink_left(is_async.len() + mover.len() + immovable.len())? |
| .sub_width(4)?; |
| |
| // 1 = | |
| let param_offset = nested_shape.indent + 1; |
| let param_shape = nested_shape.offset_left(1)?.visual_indent(0); |
| let ret_str = fn_decl.output.rewrite(context, param_shape)?; |
| |
| let param_items = itemize_list( |
| context.snippet_provider, |
| fn_decl.inputs.iter(), |
| "|", |
| ",", |
| |param| span_lo_for_param(param), |
| |param| span_hi_for_param(context, param), |
| |param| param.rewrite(context, param_shape), |
| context.snippet_provider.span_after(span, "|"), |
| body.span.lo(), |
| false, |
| ); |
| let item_vec = param_items.collect::<Vec<_>>(); |
| // 1 = space between parameters and return type. |
| let horizontal_budget = nested_shape.width.saturating_sub(ret_str.len() + 1); |
| let tactic = definitive_tactic( |
| &item_vec, |
| ListTactic::HorizontalVertical, |
| Separator::Comma, |
| horizontal_budget, |
| ); |
| let param_shape = match tactic { |
| DefinitiveListTactic::Horizontal => param_shape.sub_width(ret_str.len() + 1)?, |
| _ => param_shape, |
| }; |
| |
| let fmt = ListFormatting::new(param_shape, context.config) |
| .tactic(tactic) |
| .preserve_newline(true); |
| let list_str = write_list(&item_vec, &fmt)?; |
| let mut prefix = format!("{}{}{}|{}|", is_async, immovable, mover, list_str); |
| |
| if !ret_str.is_empty() { |
| if prefix.contains('\n') { |
| prefix.push('\n'); |
| prefix.push_str(¶m_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(crate) fn rewrite_last_closure( |
| context: &RewriteContext<'_>, |
| expr: &ast::Expr, |
| shape: Shape, |
| ) -> Option<String> { |
| if let ast::ExprKind::Closure(capture, ref is_async, movability, ref fn_decl, ref body, _) = |
| expr.kind |
| { |
| let body = match body.kind { |
| ast::ExprKind::Block(ref block, _) |
| if !is_unsafe_block(block) |
| && !context.inside_macro() |
| && is_simple_block(block, Some(&body.attrs), context.source_map) => |
| { |
| stmt_expr(&block.stmts[0]).unwrap_or(body) |
| } |
| _ => body, |
| }; |
| let (prefix, extra_offset) = rewrite_closure_fn_decl( |
| capture, is_async, movability, fn_decl, body, expr.span, context, shape, |
| )?; |
| // 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) { |
| 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_or(false, |cond| { |
| cond.contains('\n') || cond.len() > body_shape.width |
| }); |
| 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(crate) fn args_have_many_closure(args: &[OverflowableItem<'_>]) -> bool { |
| args.iter() |
| .filter_map(OverflowableItem::to_expr) |
| .filter(|expr| match expr.kind { |
| ast::ExprKind::Closure(..) => true, |
| _ => 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, context.config.version()) |
| } |
| } |
| |
| fn is_block_closure_forced_inner(expr: &ast::Expr, version: Version) -> bool { |
| match expr.kind { |
| ast::ExprKind::If(..) | ast::ExprKind::While(..) | ast::ExprKind::ForLoop(..) => true, |
| ast::ExprKind::Loop(..) if version == Version::Two => 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, version), |
| _ => false, |
| } |
| } |
| |
| /// Does this expression require a semicolon to be treated |
| /// as a statement? The negation of this: 'can this expression |
| /// be used as a statement without a semicolon' -- is used |
| /// as an early-bail-out in the parser so that, for instance, |
| /// if true {...} else {...} |
| /// |x| 5 |
| /// isn't parsed as (if true {...} else {...} | x) | 5 |
| // From https://github.com/rust-lang/rust/blob/master/src/libsyntax/parse/classify.rs. |
| fn expr_requires_semi_to_be_stmt(e: &ast::Expr) -> bool { |
| match e.kind { |
| ast::ExprKind::If(..) |
| | ast::ExprKind::Match(..) |
| | ast::ExprKind::Block(..) |
| | ast::ExprKind::While(..) |
| | ast::ExprKind::Loop(..) |
| | ast::ExprKind::ForLoop(..) |
| | ast::ExprKind::TryBlock(..) => false, |
| _ => true, |
| } |
| } |