mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-28 11:20:54 +00:00
1267 lines
36 KiB
Rust
1267 lines
36 KiB
Rust
use either::Either;
|
|
use hir::{CaptureKind, ClosureCapture, FileRangeWrapper, HirDisplay};
|
|
use ide_db::{
|
|
FxHashSet, assists::AssistId, base_db::SourceDatabase, defs::Definition,
|
|
search::FileReferenceNode, source_change::SourceChangeBuilder,
|
|
};
|
|
use stdx::format_to;
|
|
use syntax::{
|
|
AstNode, Direction, SyntaxKind, SyntaxNode, T, TextSize, ToSmolStr,
|
|
algo::{skip_trivia_token, skip_whitespace_token},
|
|
ast::{
|
|
self, HasArgList, HasGenericParams, HasName,
|
|
edit::{AstNodeEdit, IndentLevel},
|
|
make,
|
|
},
|
|
hacks::parse_expr_from_str,
|
|
ted,
|
|
};
|
|
|
|
use crate::assist_context::{AssistContext, Assists};
|
|
|
|
// Assist: convert_closure_to_fn
|
|
//
|
|
// This converts a closure to a freestanding function, changing all captures to parameters.
|
|
//
|
|
// ```
|
|
// # //- minicore: copy
|
|
// # struct String;
|
|
// # impl String {
|
|
// # fn new() -> Self {}
|
|
// # fn push_str(&mut self, s: &str) {}
|
|
// # }
|
|
// fn main() {
|
|
// let mut s = String::new();
|
|
// let closure = |$0a| s.push_str(a);
|
|
// closure("abc");
|
|
// }
|
|
// ```
|
|
// ->
|
|
// ```
|
|
// # struct String;
|
|
// # impl String {
|
|
// # fn new() -> Self {}
|
|
// # fn push_str(&mut self, s: &str) {}
|
|
// # }
|
|
// fn main() {
|
|
// let mut s = String::new();
|
|
// fn closure(a: &str, s: &mut String) {
|
|
// s.push_str(a)
|
|
// }
|
|
// closure("abc", &mut s);
|
|
// }
|
|
// ```
|
|
pub(crate) fn convert_closure_to_fn(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
|
|
let closure = ctx.find_node_at_offset::<ast::ClosureExpr>()?;
|
|
if ctx.find_node_at_offset::<ast::Expr>() != Some(ast::Expr::ClosureExpr(closure.clone())) {
|
|
// Not inside the parameter list.
|
|
return None;
|
|
}
|
|
let closure_name = closure.syntax().parent().and_then(|parent| {
|
|
let closure_decl = ast::LetStmt::cast(parent)?;
|
|
match closure_decl.pat()? {
|
|
ast::Pat::IdentPat(pat) => Some((closure_decl, pat.clone(), pat.name()?)),
|
|
_ => None,
|
|
}
|
|
});
|
|
let module = ctx.sema.scope(closure.syntax())?.module();
|
|
let closure_ty = ctx.sema.type_of_expr(&closure.clone().into())?;
|
|
let callable = closure_ty.original.as_callable(ctx.db())?;
|
|
let closure_ty = closure_ty.original.as_closure()?;
|
|
|
|
let mut ret_ty = callable.return_type();
|
|
let mut closure_mentioned_generic_params = ret_ty.generic_params(ctx.db());
|
|
|
|
let mut params = callable
|
|
.params()
|
|
.into_iter()
|
|
.map(|param| {
|
|
let node = ctx.sema.source(param.clone())?.value.right()?;
|
|
let param_ty = param.ty();
|
|
closure_mentioned_generic_params.extend(param_ty.generic_params(ctx.db()));
|
|
match node.ty() {
|
|
Some(_) => Some(node),
|
|
None => {
|
|
let ty = param_ty
|
|
.display_source_code(ctx.db(), module.into(), true)
|
|
.unwrap_or_else(|_| "_".to_owned());
|
|
Some(make::param(node.pat()?, make::ty(&ty)))
|
|
}
|
|
}
|
|
})
|
|
.collect::<Option<Vec<_>>>()?;
|
|
|
|
let mut body = closure.body()?.clone_for_update();
|
|
let mut is_gen = false;
|
|
let mut is_async = closure.async_token().is_some();
|
|
if is_async {
|
|
ret_ty = ret_ty.future_output(ctx.db())?;
|
|
}
|
|
// We defer the wrapping of the body in the block, because `make::block()` will generate a new node,
|
|
// but we need to locate `AstPtr`s inside the body.
|
|
let mut wrap_body_in_block = true;
|
|
if let ast::Expr::BlockExpr(block) = &body {
|
|
if let Some(async_token) = block.async_token() {
|
|
if !is_async {
|
|
is_async = true;
|
|
ret_ty = ret_ty.future_output(ctx.db())?;
|
|
let token_idx = async_token.index();
|
|
let whitespace_tokens_after_count = async_token
|
|
.siblings_with_tokens(Direction::Next)
|
|
.skip(1)
|
|
.take_while(|token| token.kind() == SyntaxKind::WHITESPACE)
|
|
.count();
|
|
body.syntax().splice_children(
|
|
token_idx..token_idx + whitespace_tokens_after_count + 1,
|
|
Vec::new(),
|
|
);
|
|
}
|
|
}
|
|
if let Some(gen_token) = block.gen_token() {
|
|
is_gen = true;
|
|
ret_ty = ret_ty.iterator_item(ctx.db())?;
|
|
let token_idx = gen_token.index();
|
|
let whitespace_tokens_after_count = gen_token
|
|
.siblings_with_tokens(Direction::Next)
|
|
.skip(1)
|
|
.take_while(|token| token.kind() == SyntaxKind::WHITESPACE)
|
|
.count();
|
|
body.syntax().splice_children(
|
|
token_idx..token_idx + whitespace_tokens_after_count + 1,
|
|
Vec::new(),
|
|
);
|
|
}
|
|
|
|
if block.try_token().is_none()
|
|
&& block.unsafe_token().is_none()
|
|
&& block.label().is_none()
|
|
&& block.const_token().is_none()
|
|
&& block.async_token().is_none()
|
|
{
|
|
wrap_body_in_block = false;
|
|
}
|
|
};
|
|
|
|
acc.add(
|
|
AssistId::refactor_rewrite("convert_closure_to_fn"),
|
|
"Convert closure to fn",
|
|
closure.param_list()?.syntax().text_range(),
|
|
|builder| {
|
|
let closure_name_or_default = closure_name
|
|
.as_ref()
|
|
.map(|(_, _, it)| it.clone())
|
|
.unwrap_or_else(|| make::name("fun_name"));
|
|
let captures = closure_ty.captured_items(ctx.db());
|
|
let capture_tys = closure_ty.capture_types(ctx.db());
|
|
|
|
let mut captures_as_args = Vec::with_capacity(captures.len());
|
|
|
|
let body_root = body.syntax().ancestors().last().unwrap();
|
|
// We need to defer this work because otherwise the text range of elements is being messed up, and
|
|
// replacements for the next captures won't work.
|
|
let mut capture_usages_replacement_map = Vec::with_capacity(captures.len());
|
|
|
|
for (capture, capture_ty) in std::iter::zip(&captures, &capture_tys) {
|
|
// FIXME: Allow configuring the replacement of `self`.
|
|
let capture_name =
|
|
if capture.local().is_self(ctx.db()) && !capture.has_field_projections() {
|
|
make::name("this")
|
|
} else {
|
|
make::name(&capture.place_to_name(ctx.db()))
|
|
};
|
|
|
|
closure_mentioned_generic_params.extend(capture_ty.generic_params(ctx.db()));
|
|
|
|
let capture_ty = capture_ty
|
|
.display_source_code(ctx.db(), module.into(), true)
|
|
.unwrap_or_else(|_| "_".to_owned());
|
|
params.push(make::param(
|
|
ast::Pat::IdentPat(make::ident_pat(false, false, capture_name.clone_subtree())),
|
|
make::ty(&capture_ty),
|
|
));
|
|
|
|
for capture_usage in capture.usages().sources(ctx.db()) {
|
|
if capture_usage.file_id() != ctx.file_id() {
|
|
// This is from a macro, don't change it.
|
|
continue;
|
|
}
|
|
|
|
let capture_usage_source = capture_usage.source();
|
|
let capture_usage_source = capture_usage_source.to_node(&body_root);
|
|
let expr = match capture_usage_source {
|
|
Either::Left(expr) => expr,
|
|
Either::Right(pat) => {
|
|
let Some(expr) = expr_of_pat(pat) else { continue };
|
|
expr
|
|
}
|
|
};
|
|
let replacement = wrap_capture_in_deref_if_needed(
|
|
&expr,
|
|
&capture_name,
|
|
capture.kind(),
|
|
capture_usage.is_ref(),
|
|
)
|
|
.clone_for_update();
|
|
capture_usages_replacement_map.push((expr, replacement));
|
|
}
|
|
|
|
captures_as_args.push(capture_as_arg(ctx, capture));
|
|
}
|
|
|
|
let (closure_type_params, closure_where_clause) =
|
|
compute_closure_type_params(ctx, closure_mentioned_generic_params, &closure);
|
|
|
|
for (old, new) in capture_usages_replacement_map {
|
|
if old == body {
|
|
body = new;
|
|
} else {
|
|
ted::replace(old.syntax(), new.syntax());
|
|
}
|
|
}
|
|
|
|
let body = if wrap_body_in_block {
|
|
make::block_expr([], Some(body))
|
|
} else {
|
|
ast::BlockExpr::cast(body.syntax().clone()).unwrap()
|
|
};
|
|
|
|
let params = make::param_list(None, params);
|
|
let ret_ty = if ret_ty.is_unit() {
|
|
None
|
|
} else {
|
|
let ret_ty = ret_ty
|
|
.display_source_code(ctx.db(), module.into(), true)
|
|
.unwrap_or_else(|_| "_".to_owned());
|
|
Some(make::ret_type(make::ty(&ret_ty)))
|
|
};
|
|
let mut fn_ = make::fn_(
|
|
None,
|
|
closure_name_or_default.clone(),
|
|
closure_type_params,
|
|
closure_where_clause,
|
|
params,
|
|
body,
|
|
ret_ty,
|
|
is_async,
|
|
false,
|
|
false,
|
|
is_gen,
|
|
);
|
|
fn_ = fn_.dedent(IndentLevel::from_token(&fn_.syntax().last_token().unwrap()));
|
|
|
|
builder.edit_file(ctx.file_id());
|
|
match &closure_name {
|
|
Some((closure_decl, _, _)) => {
|
|
fn_ = fn_.indent(closure_decl.indent_level());
|
|
builder.replace(closure_decl.syntax().text_range(), fn_.to_string());
|
|
}
|
|
None => {
|
|
let Some(top_stmt) =
|
|
closure.syntax().ancestors().skip(1).find_map(|ancestor| {
|
|
ast::Stmt::cast(ancestor.clone()).map(Either::Left).or_else(|| {
|
|
ast::ClosureExpr::cast(ancestor.clone())
|
|
.map(Either::Left)
|
|
.or_else(|| ast::BlockExpr::cast(ancestor).map(Either::Right))
|
|
.map(Either::Right)
|
|
})
|
|
})
|
|
else {
|
|
return;
|
|
};
|
|
builder.replace(
|
|
closure.syntax().text_range(),
|
|
closure_name_or_default.to_string(),
|
|
);
|
|
match top_stmt {
|
|
Either::Left(stmt) => {
|
|
let indent = stmt.indent_level();
|
|
fn_ = fn_.indent(indent);
|
|
let range = stmt
|
|
.syntax()
|
|
.first_token()
|
|
.and_then(|token| {
|
|
skip_whitespace_token(token.prev_token()?, Direction::Prev)
|
|
})
|
|
.map(|it| it.text_range().end())
|
|
.unwrap_or_else(|| stmt.syntax().text_range().start());
|
|
builder.insert(range, format!("\n{indent}{fn_}"));
|
|
}
|
|
Either::Right(Either::Left(closure_inside_closure)) => {
|
|
let Some(closure_body) = closure_inside_closure.body() else { return };
|
|
// FIXME: Maybe we can indent this properly, adding newlines and all, but this is hard.
|
|
builder.insert(
|
|
closure_body.syntax().text_range().start(),
|
|
format!("{{ {fn_} "),
|
|
);
|
|
builder
|
|
.insert(closure_body.syntax().text_range().end(), " }".to_owned());
|
|
}
|
|
Either::Right(Either::Right(block_expr)) => {
|
|
let Some(tail_expr) = block_expr.tail_expr() else { return };
|
|
let Some(insert_in) =
|
|
tail_expr.syntax().first_token().and_then(|token| {
|
|
skip_whitespace_token(token.prev_token()?, Direction::Prev)
|
|
})
|
|
else {
|
|
return;
|
|
};
|
|
let indent = tail_expr.indent_level();
|
|
fn_ = fn_.indent(indent);
|
|
builder
|
|
.insert(insert_in.text_range().end(), format!("\n{indent}{fn_}"));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
handle_calls(
|
|
builder,
|
|
ctx,
|
|
closure_name.as_ref().map(|(_, it, _)| it),
|
|
&captures_as_args,
|
|
&closure,
|
|
);
|
|
|
|
// FIXME: Place the cursor at `fun_name`, like rename does.
|
|
},
|
|
)?;
|
|
Some(())
|
|
}
|
|
|
|
fn compute_closure_type_params(
|
|
ctx: &AssistContext<'_>,
|
|
mentioned_generic_params: FxHashSet<hir::GenericParam>,
|
|
closure: &ast::ClosureExpr,
|
|
) -> (Option<ast::GenericParamList>, Option<ast::WhereClause>) {
|
|
if mentioned_generic_params.is_empty() {
|
|
return (None, None);
|
|
}
|
|
|
|
let mut mentioned_names = mentioned_generic_params
|
|
.iter()
|
|
.filter_map(|param| match param {
|
|
hir::GenericParam::TypeParam(param) => Some(param.name(ctx.db()).as_str().to_smolstr()),
|
|
hir::GenericParam::ConstParam(param) => {
|
|
Some(param.name(ctx.db()).as_str().to_smolstr())
|
|
}
|
|
hir::GenericParam::LifetimeParam(_) => None,
|
|
})
|
|
.collect::<FxHashSet<_>>();
|
|
|
|
let Some((container_params, container_where, container)) =
|
|
closure.syntax().ancestors().find_map(ast::AnyHasGenericParams::cast).and_then(
|
|
|container| {
|
|
Some((container.generic_param_list()?, container.where_clause(), container))
|
|
},
|
|
)
|
|
else {
|
|
return (None, None);
|
|
};
|
|
let containing_impl = if ast::AssocItem::can_cast(container.syntax().kind()) {
|
|
container
|
|
.syntax()
|
|
.ancestors()
|
|
.find_map(ast::Impl::cast)
|
|
.and_then(|impl_| Some((impl_.generic_param_list()?, impl_.where_clause())))
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let all_params = container_params
|
|
.type_or_const_params()
|
|
.chain(containing_impl.iter().flat_map(|(param_list, _)| param_list.type_or_const_params()))
|
|
.filter_map(|param| Some(param.name()?.text().to_smolstr()))
|
|
.collect::<FxHashSet<_>>();
|
|
|
|
// A fixpoint algorithm to detect (very roughly) if we need to include a generic parameter
|
|
// by checking if it is mentioned by another parameter we need to include.
|
|
let mut reached_fixpoint = false;
|
|
let mut container_where_bounds_indices = Vec::new();
|
|
let mut impl_where_bounds_indices = Vec::new();
|
|
while !reached_fixpoint {
|
|
reached_fixpoint = true;
|
|
|
|
let mut insert_name = |syntax: &SyntaxNode| {
|
|
let has_name = syntax
|
|
.descendants()
|
|
.filter_map(ast::NameOrNameRef::cast)
|
|
.any(|name| mentioned_names.contains(name.text().trim_start_matches("r#")));
|
|
let mut has_new_params = false;
|
|
if has_name {
|
|
syntax
|
|
.descendants()
|
|
.filter_map(ast::NameOrNameRef::cast)
|
|
.filter(|name| all_params.contains(name.text().trim_start_matches("r#")))
|
|
.for_each(|name| {
|
|
if mentioned_names.insert(name.text().trim_start_matches("r#").to_smolstr())
|
|
{
|
|
// We do this here so we don't do it if there are only matches that are not in `all_params`.
|
|
has_new_params = true;
|
|
reached_fixpoint = false;
|
|
}
|
|
});
|
|
}
|
|
has_new_params
|
|
};
|
|
|
|
for param in container_params.type_or_const_params() {
|
|
insert_name(param.syntax());
|
|
}
|
|
for (pred_index, pred) in container_where.iter().flat_map(|it| it.predicates()).enumerate()
|
|
{
|
|
if insert_name(pred.syntax()) {
|
|
container_where_bounds_indices.push(pred_index);
|
|
}
|
|
}
|
|
if let Some((impl_params, impl_where)) = &containing_impl {
|
|
for param in impl_params.type_or_const_params() {
|
|
insert_name(param.syntax());
|
|
}
|
|
for (pred_index, pred) in impl_where.iter().flat_map(|it| it.predicates()).enumerate() {
|
|
if insert_name(pred.syntax()) {
|
|
impl_where_bounds_indices.push(pred_index);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Order matters here (for beauty). First the outer impl parameters, then the direct container's.
|
|
let include_params = containing_impl
|
|
.iter()
|
|
.flat_map(|(impl_params, _)| {
|
|
impl_params.type_or_const_params().filter(|param| {
|
|
param.name().is_some_and(|name| {
|
|
mentioned_names.contains(name.text().trim_start_matches("r#"))
|
|
})
|
|
})
|
|
})
|
|
.chain(container_params.type_or_const_params().filter(|param| {
|
|
param
|
|
.name()
|
|
.is_some_and(|name| mentioned_names.contains(name.text().trim_start_matches("r#")))
|
|
}))
|
|
.map(ast::TypeOrConstParam::into);
|
|
let include_where_bounds = containing_impl
|
|
.as_ref()
|
|
.and_then(|(_, it)| it.as_ref())
|
|
.into_iter()
|
|
.flat_map(|where_| {
|
|
impl_where_bounds_indices.iter().filter_map(|&index| where_.predicates().nth(index))
|
|
})
|
|
.chain(container_where.iter().flat_map(|where_| {
|
|
container_where_bounds_indices
|
|
.iter()
|
|
.filter_map(|&index| where_.predicates().nth(index))
|
|
}))
|
|
.collect::<Vec<_>>();
|
|
let where_clause =
|
|
(!include_where_bounds.is_empty()).then(|| make::where_clause(include_where_bounds));
|
|
|
|
// FIXME: Consider generic parameters that do not appear in params/return type/captures but
|
|
// written explicitly inside the closure.
|
|
(Some(make::generic_param_list(include_params)), where_clause)
|
|
}
|
|
|
|
fn wrap_capture_in_deref_if_needed(
|
|
expr: &ast::Expr,
|
|
capture_name: &ast::Name,
|
|
capture_kind: CaptureKind,
|
|
is_ref: bool,
|
|
) -> ast::Expr {
|
|
fn peel_parens(mut expr: ast::Expr) -> ast::Expr {
|
|
loop {
|
|
if ast::ParenExpr::can_cast(expr.syntax().kind()) {
|
|
let Some(parent) = expr.syntax().parent().and_then(ast::Expr::cast) else { break };
|
|
expr = parent;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
expr
|
|
}
|
|
|
|
let capture_name = make::expr_path(make::path_from_text(&capture_name.text()));
|
|
if capture_kind == CaptureKind::Move || is_ref {
|
|
return capture_name;
|
|
}
|
|
let expr_parent = expr.syntax().parent().and_then(ast::Expr::cast);
|
|
let expr_parent_peeled_parens = expr_parent.map(peel_parens);
|
|
let does_autoderef = match expr_parent_peeled_parens {
|
|
Some(
|
|
ast::Expr::AwaitExpr(_)
|
|
| ast::Expr::CallExpr(_)
|
|
| ast::Expr::FieldExpr(_)
|
|
| ast::Expr::FormatArgsExpr(_)
|
|
| ast::Expr::MethodCallExpr(_),
|
|
) => true,
|
|
Some(ast::Expr::IndexExpr(parent_expr)) if parent_expr.base().as_ref() == Some(expr) => {
|
|
true
|
|
}
|
|
_ => false,
|
|
};
|
|
if does_autoderef {
|
|
return capture_name;
|
|
}
|
|
make::expr_prefix(T![*], capture_name).into()
|
|
}
|
|
|
|
fn capture_as_arg(ctx: &AssistContext<'_>, capture: &ClosureCapture) -> ast::Expr {
|
|
let place =
|
|
parse_expr_from_str(&capture.display_place_source_code(ctx.db()), ctx.file_id().edition())
|
|
.expect("`display_place_source_code()` produced an invalid expr");
|
|
let needs_mut = match capture.kind() {
|
|
CaptureKind::SharedRef => false,
|
|
CaptureKind::MutableRef | CaptureKind::UniqueSharedRef => true,
|
|
CaptureKind::Move => return place,
|
|
};
|
|
if let ast::Expr::PrefixExpr(expr) = &place {
|
|
if expr.op_kind() == Some(ast::UnaryOp::Deref) {
|
|
return expr.expr().expect("`display_place_source_code()` produced an invalid expr");
|
|
}
|
|
}
|
|
make::expr_ref(place, needs_mut)
|
|
}
|
|
|
|
fn handle_calls(
|
|
builder: &mut SourceChangeBuilder,
|
|
ctx: &AssistContext<'_>,
|
|
closure_name: Option<&ast::IdentPat>,
|
|
captures_as_args: &[ast::Expr],
|
|
closure: &ast::ClosureExpr,
|
|
) {
|
|
if captures_as_args.is_empty() {
|
|
return;
|
|
}
|
|
|
|
match closure_name {
|
|
Some(closure_name) => {
|
|
let Some(closure_def) = ctx.sema.to_def(closure_name) else { return };
|
|
let closure_usages = Definition::from(closure_def).usages(&ctx.sema).all();
|
|
for (_, usages) in closure_usages {
|
|
for usage in usages {
|
|
let name = match usage.name {
|
|
FileReferenceNode::Name(name) => name.syntax().clone(),
|
|
FileReferenceNode::NameRef(name_ref) => name_ref.syntax().clone(),
|
|
FileReferenceNode::FormatStringEntry(..) => continue,
|
|
FileReferenceNode::Lifetime(_) => {
|
|
unreachable!("impossible usage")
|
|
}
|
|
};
|
|
let Some(expr) = name.parent().and_then(|it| {
|
|
ast::Expr::cast(
|
|
ast::PathSegment::cast(it)?.parent_path().syntax().parent()?,
|
|
)
|
|
}) else {
|
|
continue;
|
|
};
|
|
handle_call(builder, ctx, expr, captures_as_args);
|
|
}
|
|
}
|
|
}
|
|
None => {
|
|
handle_call(builder, ctx, ast::Expr::ClosureExpr(closure.clone()), captures_as_args);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn handle_call(
|
|
builder: &mut SourceChangeBuilder,
|
|
ctx: &AssistContext<'_>,
|
|
closure_ref: ast::Expr,
|
|
captures_as_args: &[ast::Expr],
|
|
) -> Option<()> {
|
|
let call =
|
|
ast::CallExpr::cast(peel_blocks_and_refs_and_parens(closure_ref).syntax().parent()?)?;
|
|
let args = call.arg_list()?;
|
|
// The really last token is `)`; we need one before that.
|
|
let has_trailing_comma = args.syntax().last_token()?.prev_token().is_some_and(|token| {
|
|
skip_trivia_token(token, Direction::Prev).is_some_and(|token| token.kind() == T![,])
|
|
});
|
|
let has_existing_args = args.args().next().is_some();
|
|
|
|
let FileRangeWrapper { file_id, range } = ctx.sema.original_range_opt(args.syntax())?;
|
|
let first_arg_indent = args.args().next().map(|it| it.indent_level());
|
|
let arg_list_indent = args.indent_level();
|
|
let insert_newlines =
|
|
first_arg_indent.is_some_and(|first_arg_indent| first_arg_indent != arg_list_indent);
|
|
let indent =
|
|
if insert_newlines { first_arg_indent.unwrap().to_string() } else { String::new() };
|
|
// FIXME: This text manipulation seems risky.
|
|
let text = ctx.db().file_text(file_id.file_id()).text(ctx.db());
|
|
let mut text = text[..u32::from(range.end()).try_into().unwrap()].trim_end();
|
|
if !text.ends_with(')') {
|
|
return None;
|
|
}
|
|
text = text[..text.len() - 1].trim_end();
|
|
let offset = TextSize::new(text.len().try_into().unwrap());
|
|
|
|
let mut to_insert = String::new();
|
|
if has_existing_args && !has_trailing_comma {
|
|
to_insert.push(',');
|
|
}
|
|
if insert_newlines {
|
|
to_insert.push('\n');
|
|
}
|
|
let (last_arg, rest_args) =
|
|
captures_as_args.split_last().expect("already checked has captures");
|
|
if !insert_newlines && has_existing_args {
|
|
to_insert.push(' ');
|
|
}
|
|
if let Some((first_arg, rest_args)) = rest_args.split_first() {
|
|
format_to!(to_insert, "{indent}{first_arg},",);
|
|
if insert_newlines {
|
|
to_insert.push('\n');
|
|
}
|
|
for new_arg in rest_args {
|
|
if !insert_newlines {
|
|
to_insert.push(' ');
|
|
}
|
|
format_to!(to_insert, "{indent}{new_arg},",);
|
|
if insert_newlines {
|
|
to_insert.push('\n');
|
|
}
|
|
}
|
|
if !insert_newlines {
|
|
to_insert.push(' ');
|
|
}
|
|
}
|
|
format_to!(to_insert, "{indent}{last_arg}");
|
|
if has_trailing_comma {
|
|
to_insert.push(',');
|
|
}
|
|
|
|
builder.edit_file(file_id);
|
|
builder.insert(offset, to_insert);
|
|
|
|
Some(())
|
|
}
|
|
|
|
fn peel_blocks_and_refs_and_parens(mut expr: ast::Expr) -> ast::Expr {
|
|
loop {
|
|
let Some(parent) = expr.syntax().parent() else { break };
|
|
if matches!(parent.kind(), SyntaxKind::PAREN_EXPR | SyntaxKind::REF_EXPR) {
|
|
expr = ast::Expr::cast(parent).unwrap();
|
|
continue;
|
|
}
|
|
if let Some(stmt_list) = ast::StmtList::cast(parent) {
|
|
if let Some(block) = stmt_list.syntax().parent().and_then(ast::BlockExpr::cast) {
|
|
expr = ast::Expr::BlockExpr(block);
|
|
continue;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
expr
|
|
}
|
|
|
|
// FIXME:
|
|
// Somehow handle the case of `let Struct { field, .. } = capture`.
|
|
// Replacing `capture` with `capture_field` won't work.
|
|
fn expr_of_pat(pat: ast::Pat) -> Option<ast::Expr> {
|
|
'find_expr: {
|
|
for ancestor in pat.syntax().ancestors() {
|
|
if let Some(let_stmt) = ast::LetStmt::cast(ancestor.clone()) {
|
|
break 'find_expr let_stmt.initializer();
|
|
}
|
|
if ast::MatchArm::can_cast(ancestor.kind()) {
|
|
if let Some(match_) =
|
|
ancestor.parent().and_then(|it| it.parent()).and_then(ast::MatchExpr::cast)
|
|
{
|
|
break 'find_expr match_.expr();
|
|
}
|
|
}
|
|
if ast::ExprStmt::can_cast(ancestor.kind()) {
|
|
break;
|
|
}
|
|
}
|
|
None
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::tests::{check_assist, check_assist_not_applicable};
|
|
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn handles_unique_captures() {
|
|
check_assist(
|
|
convert_closure_to_fn,
|
|
r#"
|
|
//- minicore:copy
|
|
fn main() {
|
|
let s = &mut true;
|
|
let closure = |$0| { *s = false; };
|
|
closure();
|
|
}
|
|
"#,
|
|
r#"
|
|
fn main() {
|
|
let s = &mut true;
|
|
fn closure(s: &mut bool) { *s = false; }
|
|
closure(s);
|
|
}
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn multiple_capture_usages() {
|
|
check_assist(
|
|
convert_closure_to_fn,
|
|
r#"
|
|
//- minicore:copy
|
|
struct A { a: i32, b: bool }
|
|
fn main() {
|
|
let mut a = A { a: 123, b: false };
|
|
let closure = |$0| {
|
|
let b = a.b;
|
|
a = A { a: 456, b: true };
|
|
};
|
|
closure();
|
|
}
|
|
"#,
|
|
r#"
|
|
struct A { a: i32, b: bool }
|
|
fn main() {
|
|
let mut a = A { a: 123, b: false };
|
|
fn closure(a: &mut A) {
|
|
let b = a.b;
|
|
*a = A { a: 456, b: true };
|
|
}
|
|
closure(&mut a);
|
|
}
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn changes_names_of_place() {
|
|
check_assist(
|
|
convert_closure_to_fn,
|
|
r#"
|
|
//- minicore:copy
|
|
struct A { b: &'static B, c: i32 }
|
|
struct B(bool, i32);
|
|
struct C;
|
|
impl C {
|
|
fn foo(&self) {
|
|
let a = A { b: &B(false, 0), c: 123 };
|
|
let closure = |$0| {
|
|
let b = a.b.1;
|
|
let c = &*self;
|
|
};
|
|
closure();
|
|
}
|
|
}
|
|
"#,
|
|
r#"
|
|
struct A { b: &'static B, c: i32 }
|
|
struct B(bool, i32);
|
|
struct C;
|
|
impl C {
|
|
fn foo(&self) {
|
|
let a = A { b: &B(false, 0), c: 123 };
|
|
fn closure(this: &C, a_b_1: &i32) {
|
|
let b = *a_b_1;
|
|
let c = this;
|
|
}
|
|
closure(self, &a.b.1);
|
|
}
|
|
}
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn self_with_fields_does_not_change_to_this() {
|
|
check_assist(
|
|
convert_closure_to_fn,
|
|
r#"
|
|
//- minicore:copy
|
|
struct A { b: &'static B, c: i32 }
|
|
struct B(bool, i32);
|
|
impl A {
|
|
fn foo(&self) {
|
|
let closure = |$0| {
|
|
let b = self.b.1;
|
|
};
|
|
closure();
|
|
}
|
|
}
|
|
"#,
|
|
r#"
|
|
struct A { b: &'static B, c: i32 }
|
|
struct B(bool, i32);
|
|
impl A {
|
|
fn foo(&self) {
|
|
fn closure(self_b_1: &i32) {
|
|
let b = *self_b_1;
|
|
}
|
|
closure(&self.b.1);
|
|
}
|
|
}
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn replaces_async_closure_with_async_fn() {
|
|
check_assist(
|
|
convert_closure_to_fn,
|
|
r#"
|
|
//- minicore: copy, future
|
|
fn foo(&self) {
|
|
let closure = async |$0| 1;
|
|
closure();
|
|
}
|
|
"#,
|
|
r#"
|
|
fn foo(&self) {
|
|
async fn closure() -> i32 {
|
|
1
|
|
}
|
|
closure();
|
|
}
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn replaces_async_block_with_async_fn() {
|
|
check_assist(
|
|
convert_closure_to_fn,
|
|
r#"
|
|
//- minicore: copy, future
|
|
fn foo() {
|
|
let closure = |$0| async { 1 };
|
|
closure();
|
|
}
|
|
"#,
|
|
r#"
|
|
fn foo() {
|
|
async fn closure() -> i32 { 1 }
|
|
closure();
|
|
}
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "FIXME: we do not do type inference for gen blocks yet"]
|
|
fn replaces_gen_block_with_gen_fn() {
|
|
check_assist(
|
|
convert_closure_to_fn,
|
|
r#"
|
|
//- minicore: copy, iterator
|
|
//- /lib.rs edition:2024
|
|
fn foo() {
|
|
let closure = |$0| gen {
|
|
yield 1;
|
|
};
|
|
closure();
|
|
}
|
|
"#,
|
|
r#"
|
|
fn foo() {
|
|
gen fn closure() -> i32 {
|
|
yield 1;
|
|
}
|
|
closure();
|
|
}
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn leaves_block_in_place() {
|
|
check_assist(
|
|
convert_closure_to_fn,
|
|
r#"
|
|
//- minicore: copy
|
|
fn foo() {
|
|
let closure = |$0| {};
|
|
closure();
|
|
}
|
|
"#,
|
|
r#"
|
|
fn foo() {
|
|
fn closure() {}
|
|
closure();
|
|
}
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn wraps_in_block_if_needed() {
|
|
check_assist(
|
|
convert_closure_to_fn,
|
|
r#"
|
|
//- minicore: copy
|
|
fn foo() {
|
|
let a = 1;
|
|
let closure = |$0| a;
|
|
closure();
|
|
}
|
|
"#,
|
|
r#"
|
|
fn foo() {
|
|
let a = 1;
|
|
fn closure(a: &i32) -> i32 {
|
|
*a
|
|
}
|
|
closure(&a);
|
|
}
|
|
"#,
|
|
);
|
|
check_assist(
|
|
convert_closure_to_fn,
|
|
r#"
|
|
//- minicore: copy
|
|
fn foo() {
|
|
let closure = |$0| 'label: {};
|
|
closure();
|
|
}
|
|
"#,
|
|
r#"
|
|
fn foo() {
|
|
fn closure() {
|
|
'label: {}
|
|
}
|
|
closure();
|
|
}
|
|
"#,
|
|
);
|
|
check_assist(
|
|
convert_closure_to_fn,
|
|
r#"
|
|
//- minicore: copy
|
|
fn foo() {
|
|
let closure = |$0| {
|
|
const { () }
|
|
};
|
|
closure();
|
|
}
|
|
"#,
|
|
r#"
|
|
fn foo() {
|
|
fn closure() {
|
|
const { () }
|
|
}
|
|
closure();
|
|
}
|
|
"#,
|
|
);
|
|
check_assist(
|
|
convert_closure_to_fn,
|
|
r#"
|
|
//- minicore: copy
|
|
fn foo() {
|
|
let closure = |$0| unsafe { };
|
|
closure();
|
|
}
|
|
"#,
|
|
r#"
|
|
fn foo() {
|
|
fn closure() {
|
|
unsafe { }
|
|
}
|
|
closure();
|
|
}
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn closure_in_closure() {
|
|
check_assist(
|
|
convert_closure_to_fn,
|
|
r#"
|
|
//- minicore: copy
|
|
fn foo() {
|
|
let a = 1;
|
|
|| |$0| { let b = &a; };
|
|
}
|
|
"#,
|
|
r#"
|
|
fn foo() {
|
|
let a = 1;
|
|
|| { fn fun_name(a: &i32) { let b = a; } fun_name };
|
|
}
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn closure_in_block() {
|
|
check_assist(
|
|
convert_closure_to_fn,
|
|
r#"
|
|
//- minicore: copy
|
|
fn foo() {
|
|
{
|
|
let a = 1;
|
|
|$0| { let b = &a; }
|
|
};
|
|
}
|
|
"#,
|
|
r#"
|
|
fn foo() {
|
|
{
|
|
let a = 1;
|
|
fn fun_name(a: &i32) { let b = a; }
|
|
fun_name
|
|
};
|
|
}
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn finds_pat_for_expr() {
|
|
check_assist(
|
|
convert_closure_to_fn,
|
|
r#"
|
|
//- minicore: copy
|
|
struct A { b: B }
|
|
struct B(bool, i32);
|
|
fn foo() {
|
|
let mut a = A { b: B(true, 0) };
|
|
let closure = |$0| {
|
|
let A { b: B(_, ref mut c) } = a;
|
|
};
|
|
closure();
|
|
}
|
|
"#,
|
|
r#"
|
|
struct A { b: B }
|
|
struct B(bool, i32);
|
|
fn foo() {
|
|
let mut a = A { b: B(true, 0) };
|
|
fn closure(a_b_1: &mut i32) {
|
|
let A { b: B(_, ref mut c) } = a_b_1;
|
|
}
|
|
closure(&mut a.b.1);
|
|
}
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn with_existing_params() {
|
|
check_assist(
|
|
convert_closure_to_fn,
|
|
r#"
|
|
//- minicore: copy
|
|
fn foo() {
|
|
let (mut a, b) = (0.1, "abc");
|
|
let closure = |$0p1: i32, p2: &mut bool| {
|
|
a = 1.2;
|
|
let c = b;
|
|
};
|
|
closure(0, &mut false);
|
|
}
|
|
"#,
|
|
r#"
|
|
fn foo() {
|
|
let (mut a, b) = (0.1, "abc");
|
|
fn closure(p1: i32, p2: &mut bool, a: &mut f64, b: &&str) {
|
|
*a = 1.2;
|
|
let c = *b;
|
|
}
|
|
closure(0, &mut false, &mut a, &b);
|
|
}
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn with_existing_params_newlines() {
|
|
check_assist(
|
|
convert_closure_to_fn,
|
|
r#"
|
|
//- minicore: copy
|
|
fn foo() {
|
|
let (mut a, b) = (0.1, "abc");
|
|
let closure = |$0p1: i32, p2| {
|
|
let _: &mut bool = p2;
|
|
a = 1.2;
|
|
let c = b;
|
|
};
|
|
closure(
|
|
0,
|
|
&mut false
|
|
);
|
|
}
|
|
"#,
|
|
r#"
|
|
fn foo() {
|
|
let (mut a, b) = (0.1, "abc");
|
|
fn closure(p1: i32, p2: &mut bool, a: &mut f64, b: &&str) {
|
|
let _: &mut bool = p2;
|
|
*a = 1.2;
|
|
let c = *b;
|
|
}
|
|
closure(
|
|
0,
|
|
&mut false,
|
|
&mut a,
|
|
&b
|
|
);
|
|
}
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn with_existing_params_trailing_comma() {
|
|
check_assist(
|
|
convert_closure_to_fn,
|
|
r#"
|
|
//- minicore: copy
|
|
fn foo() {
|
|
let (mut a, b) = (0.1, "abc");
|
|
let closure = |$0p1: i32, p2| {
|
|
let _: &mut bool = p2;
|
|
a = 1.2;
|
|
let c = b;
|
|
};
|
|
closure(
|
|
0,
|
|
&mut false,
|
|
);
|
|
}
|
|
"#,
|
|
r#"
|
|
fn foo() {
|
|
let (mut a, b) = (0.1, "abc");
|
|
fn closure(p1: i32, p2: &mut bool, a: &mut f64, b: &&str) {
|
|
let _: &mut bool = p2;
|
|
*a = 1.2;
|
|
let c = *b;
|
|
}
|
|
closure(
|
|
0,
|
|
&mut false,
|
|
&mut a,
|
|
&b,
|
|
);
|
|
}
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn closure_using_generic_params() {
|
|
check_assist(
|
|
convert_closure_to_fn,
|
|
r#"
|
|
//- minicore: copy
|
|
struct Foo<A, B, const C: usize>(A, B);
|
|
impl<A, B: From<A>, const C: usize> Foo<A, B, C> {
|
|
fn foo<D, E, F, G>(a: A, b: D)
|
|
where
|
|
E: From<D>,
|
|
{
|
|
let closure = |$0c: F| {
|
|
let a = B::from(a);
|
|
let b = E::from(b);
|
|
};
|
|
}
|
|
}
|
|
"#,
|
|
r#"
|
|
struct Foo<A, B, const C: usize>(A, B);
|
|
impl<A, B: From<A>, const C: usize> Foo<A, B, C> {
|
|
fn foo<D, E, F, G>(a: A, b: D)
|
|
where
|
|
E: From<D>,
|
|
{
|
|
fn closure<A, B: From<A>, D, E, F>(c: F, a: A, b: D) where E: From<D> {
|
|
let a = B::from(a);
|
|
let b = E::from(b);
|
|
}
|
|
}
|
|
}
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn closure_in_stmt() {
|
|
check_assist(
|
|
convert_closure_to_fn,
|
|
r#"
|
|
//- minicore: copy
|
|
fn bar(_: impl FnOnce() -> i32) {}
|
|
fn foo() {
|
|
let a = 123;
|
|
bar(|$0| a);
|
|
}
|
|
"#,
|
|
r#"
|
|
fn bar(_: impl FnOnce() -> i32) {}
|
|
fn foo() {
|
|
let a = 123;
|
|
fn fun_name(a: &i32) -> i32 {
|
|
*a
|
|
}
|
|
bar(fun_name);
|
|
}
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn unique_and_imm() {
|
|
check_assist(
|
|
convert_closure_to_fn,
|
|
r#"
|
|
//- minicore:copy
|
|
fn main() {
|
|
let a = &mut true;
|
|
let closure = |$0| {
|
|
let b = &a;
|
|
*a = false;
|
|
};
|
|
closure();
|
|
}
|
|
"#,
|
|
r#"
|
|
fn main() {
|
|
let a = &mut true;
|
|
fn closure(a: &mut &mut bool) {
|
|
let b = a;
|
|
**a = false;
|
|
}
|
|
closure(&mut a);
|
|
}
|
|
"#,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn only_applicable_in_param_list() {
|
|
check_assist_not_applicable(
|
|
convert_closure_to_fn,
|
|
r#"
|
|
//- minicore:copy
|
|
fn main() {
|
|
let closure = || { $0 };
|
|
}
|
|
"#,
|
|
);
|
|
check_assist_not_applicable(
|
|
convert_closure_to_fn,
|
|
r#"
|
|
//- minicore:copy
|
|
fn main() {
|
|
let $0closure = || { };
|
|
}
|
|
"#,
|
|
);
|
|
}
|
|
}
|