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
22pub(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 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 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 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 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 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 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 },
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 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 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 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 (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 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 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
657fn 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}