ide_assists/handlers/
convert_closure_to_fn.rs

1use either::Either;
2use hir::{CaptureKind, ClosureCapture, FileRangeWrapper, HirDisplay};
3use ide_db::{
4    FxHashSet, assists::AssistId, base_db::SourceDatabase, defs::Definition,
5    search::FileReferenceNode, source_change::SourceChangeBuilder,
6};
7use stdx::format_to;
8use syntax::{
9    AstNode, Direction, SyntaxKind, SyntaxNode, T, TextSize, ToSmolStr,
10    algo::{skip_trivia_token, skip_whitespace_token},
11    ast::{
12        self, HasArgList, HasGenericParams, HasName,
13        edit::{AstNodeEdit, IndentLevel},
14        make,
15    },
16    hacks::parse_expr_from_str,
17    ted,
18};
19
20use crate::assist_context::{AssistContext, Assists};
21
22// Assist: convert_closure_to_fn
23//
24// This converts a closure to a freestanding function, changing all captures to parameters.
25//
26// ```
27// # //- minicore: copy
28// # struct String;
29// # impl String {
30// #     fn new() -> Self {}
31// #     fn push_str(&mut self, s: &str) {}
32// # }
33// fn main() {
34//     let mut s = String::new();
35//     let closure = |$0a| s.push_str(a);
36//     closure("abc");
37// }
38// ```
39// ->
40// ```
41// # struct String;
42// # impl String {
43// #     fn new() -> Self {}
44// #     fn push_str(&mut self, s: &str) {}
45// # }
46// fn main() {
47//     let mut s = String::new();
48//     fn closure(a: &str, s: &mut String) {
49//         s.push_str(a)
50//     }
51//     closure("abc", &mut s);
52// }
53// ```
54pub(crate) fn convert_closure_to_fn(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
55    let closure = ctx.find_node_at_offset::<ast::ClosureExpr>()?;
56    if ctx.find_node_at_offset::<ast::Expr>() != Some(ast::Expr::ClosureExpr(closure.clone())) {
57        // Not inside the parameter list.
58        return None;
59    }
60    let closure_name = closure.syntax().parent().and_then(|parent| {
61        let closure_decl = ast::LetStmt::cast(parent)?;
62        match closure_decl.pat()? {
63            ast::Pat::IdentPat(pat) => Some((closure_decl, pat.clone(), pat.name()?)),
64            _ => None,
65        }
66    });
67    let module = ctx.sema.scope(closure.syntax())?.module();
68    let closure_ty = ctx.sema.type_of_expr(&closure.clone().into())?;
69    let callable = closure_ty.original.as_callable(ctx.db())?;
70    let closure_ty = closure_ty.original.as_closure()?;
71
72    let mut ret_ty = callable.return_type();
73    let mut closure_mentioned_generic_params = ret_ty.generic_params(ctx.db());
74
75    let mut params = callable
76        .params()
77        .into_iter()
78        .map(|param| {
79            let node = ctx.sema.source(param.clone())?.value.right()?;
80            let param_ty = param.ty();
81            closure_mentioned_generic_params.extend(param_ty.generic_params(ctx.db()));
82            match node.ty() {
83                Some(_) => Some(node),
84                None => {
85                    let ty = param_ty
86                        .display_source_code(ctx.db(), module.into(), true)
87                        .unwrap_or_else(|_| "_".to_owned());
88                    Some(make::param(node.pat()?, make::ty(&ty)))
89                }
90            }
91        })
92        .collect::<Option<Vec<_>>>()?;
93
94    let mut body = closure.body()?.clone_for_update();
95    let mut is_gen = false;
96    let mut is_async = closure.async_token().is_some();
97    if is_async {
98        ret_ty = ret_ty.future_output(ctx.db())?;
99    }
100    // We defer the wrapping of the body in the block, because `make::block()` will generate a new node,
101    // but we need to locate `AstPtr`s inside the body.
102    let mut wrap_body_in_block = true;
103    if let ast::Expr::BlockExpr(block) = &body {
104        if let Some(async_token) = block.async_token()
105            && !is_async
106        {
107            is_async = true;
108            ret_ty = ret_ty.future_output(ctx.db())?;
109            let token_idx = async_token.index();
110            let whitespace_tokens_after_count = async_token
111                .siblings_with_tokens(Direction::Next)
112                .skip(1)
113                .take_while(|token| token.kind() == SyntaxKind::WHITESPACE)
114                .count();
115            body.syntax().splice_children(
116                token_idx..token_idx + whitespace_tokens_after_count + 1,
117                Vec::new(),
118            );
119        }
120        if let Some(gen_token) = block.gen_token() {
121            is_gen = true;
122            ret_ty = ret_ty.iterator_item(ctx.db())?;
123            let token_idx = gen_token.index();
124            let whitespace_tokens_after_count = gen_token
125                .siblings_with_tokens(Direction::Next)
126                .skip(1)
127                .take_while(|token| token.kind() == SyntaxKind::WHITESPACE)
128                .count();
129            body.syntax().splice_children(
130                token_idx..token_idx + whitespace_tokens_after_count + 1,
131                Vec::new(),
132            );
133        }
134
135        if block.try_token().is_none()
136            && block.unsafe_token().is_none()
137            && block.label().is_none()
138            && block.const_token().is_none()
139            && block.async_token().is_none()
140        {
141            wrap_body_in_block = false;
142        }
143    };
144
145    acc.add(
146        AssistId::refactor_rewrite("convert_closure_to_fn"),
147        "Convert closure to fn",
148        closure.param_list()?.syntax().text_range(),
149        |builder| {
150            let closure_name_or_default = closure_name
151                .as_ref()
152                .map(|(_, _, it)| it.clone())
153                .unwrap_or_else(|| make::name("fun_name"));
154            let captures = closure_ty.captured_items(ctx.db());
155            let capture_tys = closure_ty.capture_types(ctx.db());
156
157            let mut captures_as_args = Vec::with_capacity(captures.len());
158
159            let body_root = body.syntax().ancestors().last().unwrap();
160            // We need to defer this work because otherwise the text range of elements is being messed up, and
161            // replacements for the next captures won't work.
162            let mut capture_usages_replacement_map = Vec::with_capacity(captures.len());
163
164            for (capture, capture_ty) in std::iter::zip(&captures, &capture_tys) {
165                // FIXME: Allow configuring the replacement of `self`.
166                let capture_name =
167                    if capture.local().is_self(ctx.db()) && !capture.has_field_projections() {
168                        make::name("this")
169                    } else {
170                        make::name(&capture.place_to_name(ctx.db()))
171                    };
172
173                closure_mentioned_generic_params.extend(capture_ty.generic_params(ctx.db()));
174
175                let capture_ty = capture_ty
176                    .display_source_code(ctx.db(), module.into(), true)
177                    .unwrap_or_else(|_| "_".to_owned());
178                params.push(make::param(
179                    ast::Pat::IdentPat(make::ident_pat(false, false, capture_name.clone_subtree())),
180                    make::ty(&capture_ty),
181                ));
182
183                for capture_usage in capture.usages().sources(ctx.db()) {
184                    if capture_usage.file_id() != ctx.file_id() {
185                        // This is from a macro, don't change it.
186                        continue;
187                    }
188
189                    let capture_usage_source = capture_usage.source();
190                    let capture_usage_source = capture_usage_source.to_node(&body_root);
191                    let expr = match capture_usage_source {
192                        Either::Left(expr) => expr,
193                        Either::Right(pat) => {
194                            let Some(expr) = expr_of_pat(pat) else { continue };
195                            expr
196                        }
197                    };
198                    let replacement = wrap_capture_in_deref_if_needed(
199                        &expr,
200                        &capture_name,
201                        capture.kind(),
202                        capture_usage.is_ref(),
203                    )
204                    .clone_for_update();
205                    capture_usages_replacement_map.push((expr, replacement));
206                }
207
208                captures_as_args.push(capture_as_arg(ctx, capture));
209            }
210
211            let (closure_type_params, closure_where_clause) =
212                compute_closure_type_params(ctx, closure_mentioned_generic_params, &closure);
213
214            for (old, new) in capture_usages_replacement_map {
215                if old == body {
216                    body = new;
217                } else {
218                    ted::replace(old.syntax(), new.syntax());
219                }
220            }
221
222            let body = if wrap_body_in_block {
223                make::block_expr([], Some(body))
224            } else {
225                ast::BlockExpr::cast(body.syntax().clone()).unwrap()
226            };
227
228            let params = make::param_list(None, params);
229            let ret_ty = if ret_ty.is_unit() {
230                None
231            } else {
232                let ret_ty = ret_ty
233                    .display_source_code(ctx.db(), module.into(), true)
234                    .unwrap_or_else(|_| "_".to_owned());
235                Some(make::ret_type(make::ty(&ret_ty)))
236            };
237            let mut fn_ = make::fn_(
238                None,
239                None,
240                closure_name_or_default.clone(),
241                closure_type_params,
242                closure_where_clause,
243                params,
244                body,
245                ret_ty,
246                is_async,
247                false,
248                false,
249                is_gen,
250            );
251            fn_ = fn_.dedent(IndentLevel::from_token(&fn_.syntax().last_token().unwrap()));
252
253            builder.edit_file(ctx.vfs_file_id());
254            match &closure_name {
255                Some((closure_decl, _, _)) => {
256                    fn_ = fn_.indent(closure_decl.indent_level());
257                    builder.replace(closure_decl.syntax().text_range(), fn_.to_string());
258                }
259                None => {
260                    let Some(top_stmt) =
261                        closure.syntax().ancestors().skip(1).find_map(|ancestor| {
262                            ast::Stmt::cast(ancestor.clone()).map(Either::Left).or_else(|| {
263                                ast::ClosureExpr::cast(ancestor.clone())
264                                    .map(Either::Left)
265                                    .or_else(|| ast::BlockExpr::cast(ancestor).map(Either::Right))
266                                    .map(Either::Right)
267                            })
268                        })
269                    else {
270                        return;
271                    };
272                    builder.replace(
273                        closure.syntax().text_range(),
274                        closure_name_or_default.to_string(),
275                    );
276                    match top_stmt {
277                        Either::Left(stmt) => {
278                            let indent = stmt.indent_level();
279                            fn_ = fn_.indent(indent);
280                            let range = stmt
281                                .syntax()
282                                .first_token()
283                                .and_then(|token| {
284                                    skip_whitespace_token(token.prev_token()?, Direction::Prev)
285                                })
286                                .map(|it| it.text_range().end())
287                                .unwrap_or_else(|| stmt.syntax().text_range().start());
288                            builder.insert(range, format!("\n{indent}{fn_}"));
289                        }
290                        Either::Right(Either::Left(closure_inside_closure)) => {
291                            let Some(closure_body) = closure_inside_closure.body() else { return };
292                            // FIXME: Maybe we can indent this properly, adding newlines and all, but this is hard.
293                            builder.insert(
294                                closure_body.syntax().text_range().start(),
295                                format!("{{ {fn_} "),
296                            );
297                            builder
298                                .insert(closure_body.syntax().text_range().end(), " }".to_owned());
299                        }
300                        Either::Right(Either::Right(block_expr)) => {
301                            let Some(tail_expr) = block_expr.tail_expr() else { return };
302                            let Some(insert_in) =
303                                tail_expr.syntax().first_token().and_then(|token| {
304                                    skip_whitespace_token(token.prev_token()?, Direction::Prev)
305                                })
306                            else {
307                                return;
308                            };
309                            let indent = tail_expr.indent_level();
310                            fn_ = fn_.indent(indent);
311                            builder
312                                .insert(insert_in.text_range().end(), format!("\n{indent}{fn_}"));
313                        }
314                    }
315                }
316            }
317
318            handle_calls(
319                builder,
320                ctx,
321                closure_name.as_ref().map(|(_, it, _)| it),
322                &captures_as_args,
323                &closure,
324            );
325
326            // FIXME: Place the cursor at `fun_name`, like rename does.
327        },
328    )?;
329    Some(())
330}
331
332fn compute_closure_type_params(
333    ctx: &AssistContext<'_>,
334    mentioned_generic_params: FxHashSet<hir::GenericParam>,
335    closure: &ast::ClosureExpr,
336) -> (Option<ast::GenericParamList>, Option<ast::WhereClause>) {
337    if mentioned_generic_params.is_empty() {
338        return (None, None);
339    }
340
341    let mut mentioned_names = mentioned_generic_params
342        .iter()
343        .filter_map(|param| match param {
344            hir::GenericParam::TypeParam(param) => Some(param.name(ctx.db()).as_str().to_smolstr()),
345            hir::GenericParam::ConstParam(param) => {
346                Some(param.name(ctx.db()).as_str().to_smolstr())
347            }
348            hir::GenericParam::LifetimeParam(_) => None,
349        })
350        .collect::<FxHashSet<_>>();
351
352    let Some((container_params, container_where, container)) =
353        closure.syntax().ancestors().find_map(ast::AnyHasGenericParams::cast).and_then(
354            |container| {
355                Some((container.generic_param_list()?, container.where_clause(), container))
356            },
357        )
358    else {
359        return (None, None);
360    };
361    let containing_impl = if ast::AssocItem::can_cast(container.syntax().kind()) {
362        container
363            .syntax()
364            .ancestors()
365            .find_map(ast::Impl::cast)
366            .and_then(|impl_| Some((impl_.generic_param_list()?, impl_.where_clause())))
367    } else {
368        None
369    };
370
371    let all_params = container_params
372        .type_or_const_params()
373        .chain(containing_impl.iter().flat_map(|(param_list, _)| param_list.type_or_const_params()))
374        .filter_map(|param| Some(param.name()?.text().to_smolstr()))
375        .collect::<FxHashSet<_>>();
376
377    // A fixpoint algorithm to detect (very roughly) if we need to include a generic parameter
378    // by checking if it is mentioned by another parameter we need to include.
379    let mut reached_fixpoint = false;
380    let mut container_where_bounds_indices = Vec::new();
381    let mut impl_where_bounds_indices = Vec::new();
382    while !reached_fixpoint {
383        reached_fixpoint = true;
384
385        let mut insert_name = |syntax: &SyntaxNode| {
386            let has_name = syntax
387                .descendants()
388                .filter_map(ast::NameOrNameRef::cast)
389                .any(|name| mentioned_names.contains(name.text().trim_start_matches("r#")));
390            let mut has_new_params = false;
391            if has_name {
392                syntax
393                    .descendants()
394                    .filter_map(ast::NameOrNameRef::cast)
395                    .filter(|name| all_params.contains(name.text().trim_start_matches("r#")))
396                    .for_each(|name| {
397                        if mentioned_names.insert(name.text().trim_start_matches("r#").to_smolstr())
398                        {
399                            // We do this here so we don't do it if there are only matches that are not in `all_params`.
400                            has_new_params = true;
401                            reached_fixpoint = false;
402                        }
403                    });
404            }
405            has_new_params
406        };
407
408        for param in container_params.type_or_const_params() {
409            insert_name(param.syntax());
410        }
411        for (pred_index, pred) in container_where.iter().flat_map(|it| it.predicates()).enumerate()
412        {
413            if insert_name(pred.syntax()) {
414                container_where_bounds_indices.push(pred_index);
415            }
416        }
417        if let Some((impl_params, impl_where)) = &containing_impl {
418            for param in impl_params.type_or_const_params() {
419                insert_name(param.syntax());
420            }
421            for (pred_index, pred) in impl_where.iter().flat_map(|it| it.predicates()).enumerate() {
422                if insert_name(pred.syntax()) {
423                    impl_where_bounds_indices.push(pred_index);
424                }
425            }
426        }
427    }
428
429    // Order matters here (for beauty). First the outer impl parameters, then the direct container's.
430    let include_params = containing_impl
431        .iter()
432        .flat_map(|(impl_params, _)| {
433            impl_params.type_or_const_params().filter(|param| {
434                param.name().is_some_and(|name| {
435                    mentioned_names.contains(name.text().trim_start_matches("r#"))
436                })
437            })
438        })
439        .chain(container_params.type_or_const_params().filter(|param| {
440            param
441                .name()
442                .is_some_and(|name| mentioned_names.contains(name.text().trim_start_matches("r#")))
443        }))
444        .map(ast::TypeOrConstParam::into);
445    let include_where_bounds = containing_impl
446        .as_ref()
447        .and_then(|(_, it)| it.as_ref())
448        .into_iter()
449        .flat_map(|where_| {
450            impl_where_bounds_indices.iter().filter_map(|&index| where_.predicates().nth(index))
451        })
452        .chain(container_where.iter().flat_map(|where_| {
453            container_where_bounds_indices
454                .iter()
455                .filter_map(|&index| where_.predicates().nth(index))
456        }))
457        .collect::<Vec<_>>();
458    let where_clause =
459        (!include_where_bounds.is_empty()).then(|| make::where_clause(include_where_bounds));
460
461    // FIXME: Consider generic parameters that do not appear in params/return type/captures but
462    // written explicitly inside the closure.
463    (Some(make::generic_param_list(include_params)), where_clause)
464}
465
466fn wrap_capture_in_deref_if_needed(
467    expr: &ast::Expr,
468    capture_name: &ast::Name,
469    capture_kind: CaptureKind,
470    is_ref: bool,
471) -> ast::Expr {
472    fn peel_parens(mut expr: ast::Expr) -> ast::Expr {
473        loop {
474            if ast::ParenExpr::can_cast(expr.syntax().kind()) {
475                let Some(parent) = expr.syntax().parent().and_then(ast::Expr::cast) else { break };
476                expr = parent;
477            } else {
478                break;
479            }
480        }
481        expr
482    }
483
484    let capture_name = make::expr_path(make::path_from_text(&capture_name.text()));
485    if capture_kind == CaptureKind::Move || is_ref {
486        return capture_name;
487    }
488    let expr_parent = expr.syntax().parent().and_then(ast::Expr::cast);
489    let expr_parent_peeled_parens = expr_parent.map(peel_parens);
490    let does_autoderef = match expr_parent_peeled_parens {
491        Some(
492            ast::Expr::AwaitExpr(_)
493            | ast::Expr::CallExpr(_)
494            | ast::Expr::FieldExpr(_)
495            | ast::Expr::FormatArgsExpr(_)
496            | ast::Expr::MethodCallExpr(_),
497        ) => true,
498        Some(ast::Expr::IndexExpr(parent_expr)) if parent_expr.base().as_ref() == Some(expr) => {
499            true
500        }
501        _ => false,
502    };
503    if does_autoderef {
504        return capture_name;
505    }
506    make::expr_prefix(T![*], capture_name).into()
507}
508
509fn capture_as_arg(ctx: &AssistContext<'_>, capture: &ClosureCapture<'_>) -> ast::Expr {
510    let place = parse_expr_from_str(&capture.display_place_source_code(ctx.db()), ctx.edition())
511        .expect("`display_place_source_code()` produced an invalid expr");
512    let needs_mut = match capture.kind() {
513        CaptureKind::SharedRef => false,
514        CaptureKind::MutableRef | CaptureKind::UniqueSharedRef => true,
515        CaptureKind::Move => return place,
516    };
517    if let ast::Expr::PrefixExpr(expr) = &place
518        && expr.op_kind() == Some(ast::UnaryOp::Deref)
519    {
520        return expr.expr().expect("`display_place_source_code()` produced an invalid expr");
521    }
522    make::expr_ref(place, needs_mut)
523}
524
525fn handle_calls(
526    builder: &mut SourceChangeBuilder,
527    ctx: &AssistContext<'_>,
528    closure_name: Option<&ast::IdentPat>,
529    captures_as_args: &[ast::Expr],
530    closure: &ast::ClosureExpr,
531) {
532    if captures_as_args.is_empty() {
533        return;
534    }
535
536    match closure_name {
537        Some(closure_name) => {
538            let Some(closure_def) = ctx.sema.to_def(closure_name) else { return };
539            let closure_usages = Definition::from(closure_def).usages(&ctx.sema).all();
540            for (_, usages) in closure_usages {
541                for usage in usages {
542                    let name = match usage.name {
543                        FileReferenceNode::Name(name) => name.syntax().clone(),
544                        FileReferenceNode::NameRef(name_ref) => name_ref.syntax().clone(),
545                        FileReferenceNode::FormatStringEntry(..) => continue,
546                        FileReferenceNode::Lifetime(_) => {
547                            unreachable!("impossible usage")
548                        }
549                    };
550                    let Some(expr) = name.parent().and_then(|it| {
551                        ast::Expr::cast(
552                            ast::PathSegment::cast(it)?.parent_path().syntax().parent()?,
553                        )
554                    }) else {
555                        continue;
556                    };
557                    handle_call(builder, ctx, expr, captures_as_args);
558                }
559            }
560        }
561        None => {
562            handle_call(builder, ctx, ast::Expr::ClosureExpr(closure.clone()), captures_as_args);
563        }
564    }
565}
566
567fn handle_call(
568    builder: &mut SourceChangeBuilder,
569    ctx: &AssistContext<'_>,
570    closure_ref: ast::Expr,
571    captures_as_args: &[ast::Expr],
572) -> Option<()> {
573    let call =
574        ast::CallExpr::cast(peel_blocks_and_refs_and_parens(closure_ref).syntax().parent()?)?;
575    let args = call.arg_list()?;
576    // The really last token is `)`; we need one before that.
577    let has_trailing_comma = args.syntax().last_token()?.prev_token().is_some_and(|token| {
578        skip_trivia_token(token, Direction::Prev).is_some_and(|token| token.kind() == T![,])
579    });
580    let has_existing_args = args.args().next().is_some();
581
582    let FileRangeWrapper { file_id, range } = ctx.sema.original_range_opt(args.syntax())?;
583    let first_arg_indent = args.args().next().map(|it| it.indent_level());
584    let arg_list_indent = args.indent_level();
585    let insert_newlines =
586        first_arg_indent.is_some_and(|first_arg_indent| first_arg_indent != arg_list_indent);
587    let indent =
588        if insert_newlines { first_arg_indent.unwrap().to_string() } else { String::new() };
589    // FIXME: This text manipulation seems risky.
590    let text = ctx.db().file_text(file_id.file_id(ctx.db())).text(ctx.db());
591    let mut text = text[..u32::from(range.end()).try_into().unwrap()].trim_end();
592    if !text.ends_with(')') {
593        return None;
594    }
595    text = text[..text.len() - 1].trim_end();
596    let offset = TextSize::new(text.len().try_into().unwrap());
597
598    let mut to_insert = String::new();
599    if has_existing_args && !has_trailing_comma {
600        to_insert.push(',');
601    }
602    if insert_newlines {
603        to_insert.push('\n');
604    }
605    let (last_arg, rest_args) =
606        captures_as_args.split_last().expect("already checked has captures");
607    if !insert_newlines && has_existing_args {
608        to_insert.push(' ');
609    }
610    if let Some((first_arg, rest_args)) = rest_args.split_first() {
611        format_to!(to_insert, "{indent}{first_arg},",);
612        if insert_newlines {
613            to_insert.push('\n');
614        }
615        for new_arg in rest_args {
616            if !insert_newlines {
617                to_insert.push(' ');
618            }
619            format_to!(to_insert, "{indent}{new_arg},",);
620            if insert_newlines {
621                to_insert.push('\n');
622            }
623        }
624        if !insert_newlines {
625            to_insert.push(' ');
626        }
627    }
628    format_to!(to_insert, "{indent}{last_arg}");
629    if has_trailing_comma {
630        to_insert.push(',');
631    }
632
633    builder.edit_file(file_id.file_id(ctx.db()));
634    builder.insert(offset, to_insert);
635
636    Some(())
637}
638
639fn peel_blocks_and_refs_and_parens(mut expr: ast::Expr) -> ast::Expr {
640    loop {
641        let Some(parent) = expr.syntax().parent() else { break };
642        if matches!(parent.kind(), SyntaxKind::PAREN_EXPR | SyntaxKind::REF_EXPR) {
643            expr = ast::Expr::cast(parent).unwrap();
644            continue;
645        }
646        if let Some(stmt_list) = ast::StmtList::cast(parent)
647            && let Some(block) = stmt_list.syntax().parent().and_then(ast::BlockExpr::cast)
648        {
649            expr = ast::Expr::BlockExpr(block);
650            continue;
651        }
652        break;
653    }
654    expr
655}
656
657// FIXME:
658// Somehow handle the case of `let Struct { field, .. } = capture`.
659// Replacing `capture` with `capture_field` won't work.
660fn expr_of_pat(pat: ast::Pat) -> Option<ast::Expr> {
661    'find_expr: {
662        for ancestor in pat.syntax().ancestors() {
663            if let Some(let_stmt) = ast::LetStmt::cast(ancestor.clone()) {
664                break 'find_expr let_stmt.initializer();
665            }
666            if ast::MatchArm::can_cast(ancestor.kind())
667                && let Some(match_) =
668                    ancestor.parent().and_then(|it| it.parent()).and_then(ast::MatchExpr::cast)
669            {
670                break 'find_expr match_.expr();
671            }
672            if ast::ExprStmt::can_cast(ancestor.kind()) {
673                break;
674            }
675        }
676        None
677    }
678}
679
680#[cfg(test)]
681mod tests {
682    use crate::tests::{check_assist, check_assist_not_applicable};
683
684    use super::*;
685
686    #[test]
687    fn handles_unique_captures() {
688        check_assist(
689            convert_closure_to_fn,
690            r#"
691//- minicore:copy
692fn main() {
693    let s = &mut true;
694    let closure = |$0| { *s = false; };
695    closure();
696}
697"#,
698            r#"
699fn main() {
700    let s = &mut true;
701    fn closure(s: &mut bool) { *s = false; }
702    closure(s);
703}
704"#,
705        );
706    }
707
708    #[test]
709    fn multiple_capture_usages() {
710        check_assist(
711            convert_closure_to_fn,
712            r#"
713//- minicore:copy
714struct A { a: i32, b: bool }
715fn main() {
716    let mut a = A { a: 123, b: false };
717    let closure = |$0| {
718        let b = a.b;
719        a = A { a: 456, b: true };
720    };
721    closure();
722}
723"#,
724            r#"
725struct A { a: i32, b: bool }
726fn main() {
727    let mut a = A { a: 123, b: false };
728    fn closure(a: &mut A) {
729        let b = a.b;
730        *a = A { a: 456, b: true };
731    }
732    closure(&mut a);
733}
734"#,
735        );
736    }
737
738    #[test]
739    fn changes_names_of_place() {
740        check_assist(
741            convert_closure_to_fn,
742            r#"
743//- minicore:copy
744struct A { b: &'static B, c: i32 }
745struct B(bool, i32);
746struct C;
747impl C {
748    fn foo(&self) {
749        let a = A { b: &B(false, 0), c: 123 };
750        let closure = |$0| {
751            let b = a.b.1;
752            let c = &*self;
753        };
754        closure();
755    }
756}
757"#,
758            r#"
759struct A { b: &'static B, c: i32 }
760struct B(bool, i32);
761struct C;
762impl C {
763    fn foo(&self) {
764        let a = A { b: &B(false, 0), c: 123 };
765        fn closure(this: &C, a_b_1: &i32) {
766            let b = *a_b_1;
767            let c = this;
768        }
769        closure(self, &a.b.1);
770    }
771}
772"#,
773        );
774    }
775
776    #[test]
777    fn self_with_fields_does_not_change_to_this() {
778        check_assist(
779            convert_closure_to_fn,
780            r#"
781//- minicore:copy
782struct A { b: &'static B, c: i32 }
783struct B(bool, i32);
784impl A {
785    fn foo(&self) {
786        let closure = |$0| {
787            let b = self.b.1;
788        };
789        closure();
790    }
791}
792"#,
793            r#"
794struct A { b: &'static B, c: i32 }
795struct B(bool, i32);
796impl A {
797    fn foo(&self) {
798        fn closure(self_b_1: &i32) {
799            let b = *self_b_1;
800        }
801        closure(&self.b.1);
802    }
803}
804"#,
805        );
806    }
807
808    #[test]
809    fn replaces_async_closure_with_async_fn() {
810        check_assist(
811            convert_closure_to_fn,
812            r#"
813//- minicore: copy, future
814fn foo(&self) {
815    let closure = async |$0| 1;
816    closure();
817}
818"#,
819            r#"
820fn foo(&self) {
821    async fn closure() -> i32 {
822        1
823    }
824    closure();
825}
826"#,
827        );
828    }
829
830    #[test]
831    fn replaces_async_block_with_async_fn() {
832        check_assist(
833            convert_closure_to_fn,
834            r#"
835//- minicore: copy, future
836fn foo() {
837    let closure = |$0| async { 1 };
838    closure();
839}
840"#,
841            r#"
842fn foo() {
843    async fn closure() -> i32 { 1 }
844    closure();
845}
846"#,
847        );
848    }
849
850    #[test]
851    #[ignore = "FIXME: we do not do type inference for gen blocks yet"]
852    fn replaces_gen_block_with_gen_fn() {
853        check_assist(
854            convert_closure_to_fn,
855            r#"
856//- minicore: copy, iterator
857//- /lib.rs edition:2024
858fn foo() {
859    let closure = |$0| gen {
860        yield 1;
861    };
862    closure();
863}
864"#,
865            r#"
866fn foo() {
867    gen fn closure() -> i32 {
868        yield 1;
869    }
870    closure();
871}
872"#,
873        );
874    }
875
876    #[test]
877    fn leaves_block_in_place() {
878        check_assist(
879            convert_closure_to_fn,
880            r#"
881//- minicore: copy
882fn foo() {
883    let closure = |$0| {};
884    closure();
885}
886"#,
887            r#"
888fn foo() {
889    fn closure() {}
890    closure();
891}
892"#,
893        );
894    }
895
896    #[test]
897    fn wraps_in_block_if_needed() {
898        check_assist(
899            convert_closure_to_fn,
900            r#"
901//- minicore: copy
902fn foo() {
903    let a = 1;
904    let closure = |$0| a;
905    closure();
906}
907"#,
908            r#"
909fn foo() {
910    let a = 1;
911    fn closure(a: &i32) -> i32 {
912        *a
913    }
914    closure(&a);
915}
916"#,
917        );
918        check_assist(
919            convert_closure_to_fn,
920            r#"
921//- minicore: copy
922fn foo() {
923    let closure = |$0| 'label: {};
924    closure();
925}
926"#,
927            r#"
928fn foo() {
929    fn closure() {
930        'label: {}
931    }
932    closure();
933}
934"#,
935        );
936        check_assist(
937            convert_closure_to_fn,
938            r#"
939//- minicore: copy
940fn foo() {
941    let closure = |$0| {
942        const { () }
943    };
944    closure();
945}
946"#,
947            r#"
948fn foo() {
949    fn closure() {
950        const { () }
951    }
952    closure();
953}
954"#,
955        );
956        check_assist(
957            convert_closure_to_fn,
958            r#"
959//- minicore: copy
960fn foo() {
961    let closure = |$0| unsafe { };
962    closure();
963}
964"#,
965            r#"
966fn foo() {
967    fn closure() {
968        unsafe { }
969    }
970    closure();
971}
972"#,
973        );
974    }
975
976    #[test]
977    fn closure_in_closure() {
978        check_assist(
979            convert_closure_to_fn,
980            r#"
981//- minicore: copy
982fn foo() {
983    let a = 1;
984    || |$0| { let b = &a; };
985}
986"#,
987            r#"
988fn foo() {
989    let a = 1;
990    || { fn fun_name(a: &i32) { let b = a; } fun_name };
991}
992"#,
993        );
994    }
995
996    #[test]
997    fn closure_in_block() {
998        check_assist(
999            convert_closure_to_fn,
1000            r#"
1001//- minicore: copy
1002fn foo() {
1003    {
1004        let a = 1;
1005        |$0| { let b = &a; }
1006    };
1007}
1008"#,
1009            r#"
1010fn foo() {
1011    {
1012        let a = 1;
1013        fn fun_name(a: &i32) { let b = a; }
1014        fun_name
1015    };
1016}
1017"#,
1018        );
1019    }
1020
1021    #[test]
1022    fn finds_pat_for_expr() {
1023        check_assist(
1024            convert_closure_to_fn,
1025            r#"
1026//- minicore: copy
1027struct A { b: B }
1028struct B(bool, i32);
1029fn foo() {
1030    let mut a = A { b: B(true, 0) };
1031    let closure = |$0| {
1032        let A { b: B(_, ref mut c) } = a;
1033    };
1034    closure();
1035}
1036"#,
1037            r#"
1038struct A { b: B }
1039struct B(bool, i32);
1040fn foo() {
1041    let mut a = A { b: B(true, 0) };
1042    fn closure(a_b_1: &mut i32) {
1043        let A { b: B(_, ref mut c) } = a_b_1;
1044    }
1045    closure(&mut a.b.1);
1046}
1047"#,
1048        );
1049    }
1050
1051    #[test]
1052    fn with_existing_params() {
1053        check_assist(
1054            convert_closure_to_fn,
1055            r#"
1056//- minicore: copy
1057fn foo() {
1058    let (mut a, b) = (0.1, "abc");
1059    let closure = |$0p1: i32, p2: &mut bool| {
1060        a = 1.2;
1061        let c = b;
1062    };
1063    closure(0, &mut false);
1064}
1065"#,
1066            r#"
1067fn foo() {
1068    let (mut a, b) = (0.1, "abc");
1069    fn closure(p1: i32, p2: &mut bool, a: &mut f64, b: &&str) {
1070        *a = 1.2;
1071        let c = *b;
1072    }
1073    closure(0, &mut false, &mut a, &b);
1074}
1075"#,
1076        );
1077    }
1078
1079    #[test]
1080    fn with_existing_params_newlines() {
1081        check_assist(
1082            convert_closure_to_fn,
1083            r#"
1084//- minicore: copy
1085fn foo() {
1086    let (mut a, b) = (0.1, "abc");
1087    let closure = |$0p1: i32, p2| {
1088        let _: &mut bool = p2;
1089        a = 1.2;
1090        let c = b;
1091    };
1092    closure(
1093        0,
1094        &mut false
1095    );
1096}
1097"#,
1098            r#"
1099fn foo() {
1100    let (mut a, b) = (0.1, "abc");
1101    fn closure(p1: i32, p2: &mut bool, a: &mut f64, b: &&str) {
1102        let _: &mut bool = p2;
1103        *a = 1.2;
1104        let c = *b;
1105    }
1106    closure(
1107        0,
1108        &mut false,
1109        &mut a,
1110        &b
1111    );
1112}
1113"#,
1114        );
1115    }
1116
1117    #[test]
1118    fn with_existing_params_trailing_comma() {
1119        check_assist(
1120            convert_closure_to_fn,
1121            r#"
1122//- minicore: copy
1123fn foo() {
1124    let (mut a, b) = (0.1, "abc");
1125    let closure = |$0p1: i32, p2| {
1126        let _: &mut bool = p2;
1127        a = 1.2;
1128        let c = b;
1129    };
1130    closure(
1131        0,
1132        &mut false,
1133    );
1134}
1135"#,
1136            r#"
1137fn foo() {
1138    let (mut a, b) = (0.1, "abc");
1139    fn closure(p1: i32, p2: &mut bool, a: &mut f64, b: &&str) {
1140        let _: &mut bool = p2;
1141        *a = 1.2;
1142        let c = *b;
1143    }
1144    closure(
1145        0,
1146        &mut false,
1147        &mut a,
1148        &b,
1149    );
1150}
1151"#,
1152        );
1153    }
1154
1155    #[test]
1156    fn closure_using_generic_params() {
1157        check_assist(
1158            convert_closure_to_fn,
1159            r#"
1160//- minicore: copy
1161struct Foo<A, B, const C: usize>(A, B);
1162impl<A, B: From<A>, const C: usize> Foo<A, B, C> {
1163    fn foo<D, E, F, G>(a: A, b: D)
1164    where
1165        E: From<D>,
1166    {
1167        let closure = |$0c: F| {
1168            let a = B::from(a);
1169            let b = E::from(b);
1170        };
1171    }
1172}
1173"#,
1174            r#"
1175struct Foo<A, B, const C: usize>(A, B);
1176impl<A, B: From<A>, const C: usize> Foo<A, B, C> {
1177    fn foo<D, E, F, G>(a: A, b: D)
1178    where
1179        E: From<D>,
1180    {
1181        fn closure<A, B: From<A>, D, E, F>(c: F, a: A, b: D) where E: From<D> {
1182            let a = B::from(a);
1183            let b = E::from(b);
1184        }
1185    }
1186}
1187"#,
1188        );
1189    }
1190
1191    #[test]
1192    fn closure_in_stmt() {
1193        check_assist(
1194            convert_closure_to_fn,
1195            r#"
1196//- minicore: copy
1197fn bar(_: impl FnOnce() -> i32) {}
1198fn foo() {
1199    let a = 123;
1200    bar(|$0| a);
1201}
1202"#,
1203            r#"
1204fn bar(_: impl FnOnce() -> i32) {}
1205fn foo() {
1206    let a = 123;
1207    fn fun_name(a: &i32) -> i32 {
1208        *a
1209    }
1210    bar(fun_name);
1211}
1212"#,
1213        );
1214    }
1215
1216    #[test]
1217    fn unique_and_imm() {
1218        check_assist(
1219            convert_closure_to_fn,
1220            r#"
1221//- minicore:copy
1222fn main() {
1223    let a = &mut true;
1224    let closure = |$0| {
1225        let b = &a;
1226        *a = false;
1227    };
1228    closure();
1229}
1230"#,
1231            r#"
1232fn main() {
1233    let a = &mut true;
1234    fn closure(a: &mut &mut bool) {
1235        let b = a;
1236        **a = false;
1237    }
1238    closure(&mut a);
1239}
1240"#,
1241        );
1242    }
1243
1244    #[test]
1245    fn only_applicable_in_param_list() {
1246        check_assist_not_applicable(
1247            convert_closure_to_fn,
1248            r#"
1249//- minicore:copy
1250fn main() {
1251    let closure = || { $0 };
1252}
1253"#,
1254        );
1255        check_assist_not_applicable(
1256            convert_closure_to_fn,
1257            r#"
1258//- minicore:copy
1259fn main() {
1260    let $0closure = || { };
1261}
1262"#,
1263        );
1264    }
1265}