Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide-assists/src/handlers/convert_closure_to_fn.rs')
-rw-r--r--crates/ide-assists/src/handlers/convert_closure_to_fn.rs1271
1 files changed, 1271 insertions, 0 deletions
diff --git a/crates/ide-assists/src/handlers/convert_closure_to_fn.rs b/crates/ide-assists/src/handlers/convert_closure_to_fn.rs
new file mode 100644
index 0000000000..79f303b37a
--- /dev/null
+++ b/crates/ide-assists/src/handlers/convert_closure_to_fn.rs
@@ -0,0 +1,1271 @@
+use either::Either;
+use hir::{CaptureKind, ClosureCapture, FileRangeWrapper, HirDisplay};
+use ide_db::{
+ assists::{AssistId, AssistKind},
+ base_db::SourceDatabase,
+ defs::Definition,
+ search::FileReferenceNode,
+ source_change::SourceChangeBuilder,
+ FxHashSet,
+};
+use stdx::format_to;
+use syntax::{
+ algo::{skip_trivia_token, skip_whitespace_token},
+ ast::{
+ self,
+ edit::{AstNodeEdit, IndentLevel},
+ make, HasArgList, HasGenericParams, HasName,
+ },
+ hacks::parse_expr_from_str,
+ ted, AstNode, Direction, SyntaxKind, SyntaxNode, TextSize, ToSmolStr, T,
+};
+
+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("convert_closure_to_fn", AssistKind::RefactorRewrite),
+ "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()).unescaped().display(ctx.db()).to_smolstr())
+ }
+ hir::GenericParam::ConstParam(param) => {
+ Some(param.name(ctx.db()).unescaped().display(ctx.db()).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()));
+ 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)
+}
+
+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());
+ 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 = || { };
+}
+"#,
+ );
+ }
+}