use rustc_ast::{ast, ptr};
use rustc_span::Span;
use thin_vec::thin_vec;
use tracing::debug;
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_bound_params;
use crate::utils::{last_line_width, left_most_sub_expr, stmt_expr, NodeIdExt};
pub(crate) fn rewrite_closure(
binder: &ast::ClosureBinder,
constness: ast::Const,
capture: ast::CaptureBy,
coroutine_kind: &Option<ast::CoroutineKind>,
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(
binder,
constness,
capture,
coroutine_kind,
movability,
fn_decl,
body,
span,
context,
shape,
)?;
let body_shape = shape.offset_left(extra_offset)?;
if let ast::ExprKind::Block(ref block, _) = body.kind {
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(|| {
rewrite_closure_block(block, &prefix, context, body_shape)
})
} else {
rewrite_closure_expr(body, &prefix, context, body_shape).or_else(|| {
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) {
if let Some(expr) = block.stmts.first().and_then(stmt_expr) {
return get_inner_expr(expr, prefix, context);
}
}
}
expr
}
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,
}
}
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: 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}"))
}
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::Gen(..)
| ast::ExprKind::Block(..)
| ast::ExprKind::TryBlock(..)
| ast::ExprKind::Loop(..)
| ast::ExprKind::Struct(..) => true,
ast::ExprKind::AddrOf(_, _, ref expr)
| ast::ExprKind::Try(ref expr)
| ast::ExprKind::Unary(_, ref expr)
| ast::ExprKind::Cast(ref expr, _) => allow_multi_line(expr),
_ => false,
}
}
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))
}
fn rewrite_closure_block(
block: &ast::Block,
prefix: &str,
context: &RewriteContext<'_>,
shape: Shape,
) -> Option<String> {
Some(format!("{} {}", prefix, block.rewrite(context, shape)?))
}
fn rewrite_closure_fn_decl(
binder: &ast::ClosureBinder,
constness: ast::Const,
capture: ast::CaptureBy,
coroutine_kind: &Option<ast::CoroutineKind>,
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_bound_params(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 coro = match coroutine_kind {
Some(ast::CoroutineKind::Async { .. }) => "async ",
Some(ast::CoroutineKind::Gen { .. }) => "gen ",
Some(ast::CoroutineKind::AsyncGen { .. }) => "async gen ",
None => "",
};
let mover = if matches!(capture, ast::CaptureBy::Value { .. }) {
"move "
} else {
""
};
let nested_shape = shape
.shrink_left(binder.len() + const_.len() + immovable.len() + coro.len() + mover.len())?
.sub_width(4)?;
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<_>>();
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}{coro}{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);
}
let extra_offset = last_line_width(&prefix) + 1;
Some((prefix, extra_offset))
}
pub(crate) fn rewrite_last_closure(
context: &RewriteContext<'_>,
expr: &ast::Expr,
shape: Shape,
) -> Option<String> {
if let ast::ExprKind::Closure(ref closure) = expr.kind {
let ast::Closure {
ref binder,
constness,
capture_clause,
ref coroutine_kind,
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,
coroutine_kind,
movability,
fn_decl,
body,
expr.span,
context,
shape,
)?;
if prefix.contains('\n') {
return None;
}
let body_shape = shape.offset_left(extra_offset)?;
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 => {
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,
}
},
);
}
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);
}
return expr.rewrite(context, shape);
}
None
}
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 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::Try(ref expr)
| ast::ExprKind::Unary(_, ref expr)
| ast::ExprKind::Cast(ref expr, _) => is_block_closure_forced_inner(expr, version),
_ => false,
}
}
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,
}
}