use rustc_ast::{ast, ptr}; use rustc_span::Span; use thin_vec::thin_vec; use crate::attr::get_attrs_from_stmt; 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::types::rewrite_lifetime_param; 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( binder: &ast::ClosureBinder, constness: ast::Const, capture: ast::CaptureBy, is_async: &ast::Async, movability: ast::Movability, fn_decl: &ast::FnDecl, body: &ast::Expr, span: Span, context: &RewriteContext<'_>, shape: Shape, ) -> Option { debug!("rewrite_closure {:?}", body); let (prefix, extra_offset) = rewrite_closure_fn_decl( binder, constness, 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(context, block) { return body .rewrite(context, shape) .map(|s| format!("{} {}", prefix, s)); } let result = match fn_decl.output { ast::FnRetTy::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 { 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 except with `|| {{}}`; // https://github.com/rust-lang/rustfmt/issues/3844 if let Some(expr) = block.stmts.first().and_then(stmt_expr) { 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 { let has_attributes = block.stmts.first().map_or(false, |first_stmt| { !get_attrs_from_stmt(first_stmt).is_empty() }); is_unsafe_block(block) || block.stmts.len() > 1 || has_attributes || block_contains_comment(context, block) || 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. // || { #[attr] foo() } -> Block { #[attr] foo() } fn rewrite_closure_with_block( body: &ast::Expr, prefix: &str, context: &RewriteContext<'_>, shape: Shape, ) -> Option { 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: thin_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, tokens: None, span: body .attrs .first() .map(|attr| attr.span.to(body.span)) .unwrap_or(body.span), could_be_bare_literal: false, }; let block = crate::expr::rewrite_block_with_visitor( context, "", &block, Some(&body.attrs), 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 { fn allow_multi_line(expr: &ast::Expr) -> bool { match expr.kind { ast::ExprKind::Match(..) | ast::ExprKind::Async(..) | 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 { Some(format!("{} {}", prefix, block.rewrite(context, shape)?)) } // Return type is (prefix, extra_offset) fn rewrite_closure_fn_decl( binder: &ast::ClosureBinder, constness: ast::Const, capture: ast::CaptureBy, asyncness: &ast::Async, movability: ast::Movability, fn_decl: &ast::FnDecl, body: &ast::Expr, span: Span, context: &RewriteContext<'_>, shape: Shape, ) -> Option<(String, usize)> { let binder = match binder { ast::ClosureBinder::For { generic_params, .. } if generic_params.is_empty() => { "for<> ".to_owned() } ast::ClosureBinder::For { generic_params, .. } => { let lifetime_str = rewrite_lifetime_param(context, shape, generic_params)?; format!("for<{lifetime_str}> ") } ast::ClosureBinder::NotPresent => "".to_owned(), }; let const_ = if matches!(constness, ast::Const::Yes(_)) { "const " } else { "" }; let immovable = if movability == ast::Movability::Static { "static " } else { "" }; let is_async = if asyncness.is_async() { "async " } else { "" }; let mover = if capture == ast::CaptureBy::Value { "move " } else { "" }; // 4 = "|| {".len(), which is overconservative when the closure consists of // a single expression. let nested_shape = shape .shrink_left(binder.len() + const_.len() + immovable.len() + is_async.len() + mover.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::>(); // 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!( "{}{}{}{}{}|{}|", binder, const_, immovable, is_async, 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 { if let ast::ExprKind::Closure(ref closure) = expr.kind { let ast::Closure { ref binder, constness, capture_clause, ref asyncness, movability, ref fn_decl, ref body, fn_decl_span: _, fn_arg_span: _, } = **closure; let body = match body.kind { ast::ExprKind::Block(ref block, _) if !is_unsafe_block(block) && !context.inside_macro() && is_simple_block(context, block, Some(&body.attrs)) => { stmt_expr(&block.stmts[0]).unwrap_or(body) } _ => body, }; let (prefix, extra_offset) = rewrite_closure_fn_decl( binder, constness, capture_clause, asyncness, 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).map( |body_str| { match fn_decl.output { ast::FnRetTy::Default(..) if body_str.lines().count() <= 7 => { // If the expression can fit in a single line, we need not force block // closure. However, if the closure has a return type, then we must // keep the blocks. match rewrite_closure_expr(body, &prefix, context, shape) { Some(single_line_body_str) if !single_line_body_str.contains('\n') => { single_line_body_str } _ => body_str, } } _ => 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| matches!(expr.kind, ast::ExprKind::Closure(..))) .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, } }