1use hir::{AsAssocItem, HirDisplay, db::HirDatabase};
4use ide_db::{SnippetCap, SymbolKind};
5use itertools::Itertools;
6use stdx::{format_to, to_lower_snake_case};
7use syntax::{AstNode, SmolStr, ToSmolStr, format_smolstr};
8
9use crate::{
10 CallableSnippets,
11 context::{
12 CompleteSemicolon, CompletionContext, DotAccess, DotAccessKind, PathCompletionCtx, PathKind,
13 },
14 item::{
15 Builder, CompletionItem, CompletionItemKind, CompletionRelevance, CompletionRelevanceFn,
16 CompletionRelevanceReturnType, CompletionRelevanceTraitInfo,
17 },
18 render::{
19 RenderContext, compute_exact_name_match, compute_ref_match, compute_type_match, match_types,
20 },
21};
22
23#[derive(Debug)]
24enum FuncKind<'ctx> {
25 Function(&'ctx PathCompletionCtx<'ctx>),
26 Method(&'ctx DotAccess<'ctx>, Option<SmolStr>),
27}
28
29pub(crate) fn render_fn(
30 ctx: RenderContext<'_>,
31 path_ctx: &PathCompletionCtx<'_>,
32 local_name: Option<hir::Name>,
33 func: hir::Function,
34) -> Builder {
35 let _p = tracing::info_span!("render_fn").entered();
36 render(ctx, local_name, func, FuncKind::Function(path_ctx))
37}
38
39pub(crate) fn render_method(
40 ctx: RenderContext<'_>,
41 dot_access: &DotAccess<'_>,
42 receiver: Option<SmolStr>,
43 local_name: Option<hir::Name>,
44 func: hir::Function,
45) -> Builder {
46 let _p = tracing::info_span!("render_method").entered();
47 render(ctx, local_name, func, FuncKind::Method(dot_access, receiver))
48}
49
50fn render(
51 ctx @ RenderContext { completion, .. }: RenderContext<'_>,
52 local_name: Option<hir::Name>,
53 func: hir::Function,
54 func_kind: FuncKind<'_>,
55) -> Builder {
56 let db = completion.db;
57
58 let name = local_name.unwrap_or_else(|| func.name(db));
59
60 let (call, escaped_call) = match &func_kind {
61 FuncKind::Method(_, Some(receiver)) => (
62 format_smolstr!("{}.{}", receiver, name.as_str()),
63 format_smolstr!("{}.{}", receiver, name.display(ctx.db(), completion.edition)),
64 ),
65 _ => (name.as_str().to_smolstr(), name.display(db, completion.edition).to_smolstr()),
66 };
67 let has_self_param = func.self_param(db).is_some();
68 let mut item = CompletionItem::new(
69 CompletionItemKind::SymbolKind(if has_self_param {
70 SymbolKind::Method
71 } else {
72 SymbolKind::Function
73 }),
74 ctx.source_range(),
75 call.clone(),
76 completion.edition,
77 );
78
79 let ret_type = func.ret_type(db);
80 let assoc_item = func.as_assoc_item(db);
81
82 let trait_info =
83 assoc_item.and_then(|trait_| trait_.container_or_implemented_trait(db)).map(|trait_| {
84 CompletionRelevanceTraitInfo {
85 notable_trait: completion.is_doc_notable_trait(trait_),
86 is_op_method: completion.is_ops_trait(trait_),
87 }
88 });
89
90 let (has_dot_receiver, has_call_parens, cap) = match func_kind {
91 FuncKind::Function(&PathCompletionCtx {
92 kind: PathKind::Expr { .. },
93 has_call_parens,
94 ..
95 }) => (false, has_call_parens, ctx.completion.config.snippet_cap),
96 FuncKind::Method(&DotAccess { kind: DotAccessKind::Method, .. }, _) => {
97 (true, true, ctx.completion.config.snippet_cap)
98 }
99 FuncKind::Method(DotAccess { kind: DotAccessKind::Field { .. }, .. }, _) => {
100 (true, false, ctx.completion.config.snippet_cap)
101 }
102 _ => (false, false, None),
103 };
104 let complete_call_parens = cap
105 .filter(|_| !has_call_parens)
106 .and_then(|cap| Some((cap, params(ctx.completion, func, &func_kind, has_dot_receiver)?)));
107
108 let function = assoc_item
109 .and_then(|assoc_item| assoc_item.implementing_ty(db))
110 .map(|self_type| compute_return_type_match(db, &ctx, self_type, &ret_type))
111 .map(|return_type| CompletionRelevanceFn {
112 has_params: has_self_param || func.num_params(db) > 0,
113 has_self_param,
114 return_type,
115 });
116
117 item.set_relevance(CompletionRelevance {
118 type_match: if has_call_parens || complete_call_parens.is_some() {
119 compute_type_match(completion, &ret_type)
120 } else {
121 compute_type_match(completion, &func.ty(db))
122 },
123 exact_name_match: compute_exact_name_match(completion, &call),
124 function,
125 trait_: trait_info,
126 is_skipping_completion: matches!(func_kind, FuncKind::Method(_, Some(_))),
127 ..ctx.completion_relevance()
128 });
129
130 match func_kind {
131 FuncKind::Function(path_ctx) => {
132 super::path_ref_match(completion, path_ctx, &ret_type, &mut item);
133 }
134 FuncKind::Method(DotAccess { receiver: Some(receiver), .. }, _) => {
135 if let Some(original_expr) = completion.sema.original_ast_node(receiver.clone())
136 && let Some(ref_mode) = compute_ref_match(completion, &ret_type)
137 {
138 item.ref_match(ref_mode, original_expr.syntax().text_range().start());
139 }
140 }
141 _ => (),
142 }
143
144 let detail = if ctx.completion.config.full_function_signatures {
145 detail_full(ctx.completion, func)
146 } else {
147 detail(ctx.completion, func)
148 };
149 item.set_documentation(ctx.docs(func))
150 .set_deprecated(ctx.is_deprecated(func) || ctx.is_deprecated_assoc_item(func))
151 .detail(detail)
152 .lookup_by(name.as_str().to_smolstr());
153
154 if let Some((cap, (self_param, params))) = complete_call_parens {
155 add_call_parens(
156 &mut item,
157 completion,
158 cap,
159 call,
160 escaped_call,
161 self_param,
162 params,
163 &ret_type,
164 );
165 }
166
167 match ctx.import_to_add {
168 Some(import_to_add) => {
169 item.add_import(import_to_add);
170 }
171 None => {
172 if let Some(actm) = assoc_item
173 && let Some(trt) = actm.container_or_implemented_trait(db)
174 {
175 item.trait_name(trt.name(db).display_no_db(ctx.completion.edition).to_smolstr());
176 }
177 }
178 }
179
180 item.doc_aliases(ctx.doc_aliases);
181 item
182}
183
184fn compute_return_type_match(
185 db: &dyn HirDatabase,
186 ctx: &RenderContext<'_>,
187 self_type: hir::Type<'_>,
188 ret_type: &hir::Type<'_>,
189) -> CompletionRelevanceReturnType {
190 if match_types(ctx.completion, &self_type, ret_type).is_some() {
191 CompletionRelevanceReturnType::DirectConstructor
193 } else if ret_type
194 .type_arguments()
195 .any(|ret_type_arg| match_types(ctx.completion, &self_type, &ret_type_arg).is_some())
196 {
197 CompletionRelevanceReturnType::Constructor
199 } else if ret_type
200 .as_adt()
201 .map(|adt| adt.name(db).as_str().ends_with("Builder"))
202 .unwrap_or(false)
203 {
204 CompletionRelevanceReturnType::Builder
206 } else {
207 CompletionRelevanceReturnType::Other
208 }
209}
210
211pub(super) fn add_call_parens<'b>(
212 builder: &'b mut Builder,
213 ctx: &CompletionContext<'_>,
214 cap: SnippetCap,
215 name: SmolStr,
216 escaped_name: SmolStr,
217 self_param: Option<hir::SelfParam>,
218 params: Vec<hir::Param<'_>>,
219 ret_type: &hir::Type<'_>,
220) -> &'b mut Builder {
221 cov_mark::hit!(inserts_parens_for_function_calls);
222
223 let (mut snippet, label_suffix) = if self_param.is_none() && params.is_empty() {
224 (format!("{escaped_name}()$0"), "()")
225 } else {
226 builder.trigger_call_info();
227 let snippet = if let Some(CallableSnippets::FillArguments) = ctx.config.callable {
228 let offset = if self_param.is_some() { 2 } else { 1 };
229 let function_params_snippet =
230 params.iter().enumerate().format_with(", ", |(index, param), f| {
231 match param.name(ctx.db) {
232 Some(n) => {
233 let smol_str = n.display_no_db(ctx.edition).to_smolstr();
234 let text = smol_str.as_str().trim_start_matches('_');
235 let ref_ = ref_of_param(ctx, text, param.ty());
236 f(&format_args!("${{{}:{ref_}{text}}}", index + offset))
237 }
238 None => {
239 let name = match param.ty().as_adt() {
240 None => "_".to_owned(),
241 Some(adt) => to_lower_snake_case(adt.name(ctx.db).as_str()),
242 };
243 f(&format_args!("${{{}:{name}}}", index + offset))
244 }
245 }
246 });
247 match self_param {
248 Some(self_param) => {
249 format!(
250 "{}(${{1:{}}}{}{})$0",
251 escaped_name,
252 self_param.display(ctx.db, ctx.display_target),
253 if params.is_empty() { "" } else { ", " },
254 function_params_snippet
255 )
256 }
257 None => {
258 format!("{escaped_name}({function_params_snippet})$0")
259 }
260 }
261 } else {
262 cov_mark::hit!(suppress_arg_snippets);
263 format!("{escaped_name}($0)")
264 };
265
266 (snippet, "(…)")
267 };
268 if ret_type.is_unit() {
269 match ctx.complete_semicolon {
270 CompleteSemicolon::DoNotComplete => {}
271 CompleteSemicolon::CompleteSemi | CompleteSemicolon::CompleteComma => {
272 cov_mark::hit!(complete_semicolon);
273 let ch = if matches!(ctx.complete_semicolon, CompleteSemicolon::CompleteComma) {
274 ','
275 } else {
276 ';'
277 };
278 if snippet.ends_with("$0") {
279 snippet.insert(snippet.len() - "$0".len(), ch);
280 } else {
281 snippet.push(ch);
282 }
283 }
284 }
285 }
286 builder.label(SmolStr::from_iter([&name, label_suffix])).insert_snippet(cap, snippet)
287}
288
289fn ref_of_param(ctx: &CompletionContext<'_>, arg: &str, ty: &hir::Type<'_>) -> &'static str {
290 if let Some(derefed_ty) = ty.remove_ref() {
291 for (name, local) in ctx.locals.iter().sorted_by_key(|&(k, _)| k.clone()) {
292 if name.as_str() == arg {
293 return if local.ty(ctx.db) == derefed_ty {
294 if ty.is_mutable_reference() { "&mut " } else { "&" }
295 } else {
296 ""
297 };
298 }
299 }
300 }
301 ""
302}
303
304fn detail(ctx: &CompletionContext<'_>, func: hir::Function) -> String {
305 let mut ret_ty = func.ret_type(ctx.db);
306 let mut detail = String::new();
307
308 if func.is_const(ctx.db) {
309 format_to!(detail, "const ");
310 }
311 if func.is_async(ctx.db) {
312 format_to!(detail, "async ");
313 if let Some(async_ret) = func.async_ret_type(ctx.db) {
314 ret_ty = async_ret;
315 }
316 }
317 if func.is_unsafe_to_call(ctx.db, ctx.containing_function, ctx.edition) {
318 format_to!(detail, "unsafe ");
319 }
320
321 detail.push_str("fn(");
322 params_display(ctx, &mut detail, func);
323 detail.push(')');
324 if !ret_ty.is_unit() {
325 format_to!(detail, " -> {}", ret_ty.display(ctx.db, ctx.display_target));
326 }
327 detail
328}
329
330fn detail_full(ctx: &CompletionContext<'_>, func: hir::Function) -> String {
331 let signature = format!("{}", func.display(ctx.db, ctx.display_target));
332 let mut detail = String::with_capacity(signature.len());
333
334 for segment in signature.split_whitespace() {
335 if !detail.is_empty() {
336 detail.push(' ');
337 }
338
339 detail.push_str(segment);
340 }
341
342 detail
343}
344
345fn params_display(ctx: &CompletionContext<'_>, detail: &mut String, func: hir::Function) {
346 if let Some(self_param) = func.self_param(ctx.db) {
347 format_to!(detail, "{}", self_param.display(ctx.db, ctx.display_target));
348 let assoc_fn_params = func.assoc_fn_params(ctx.db);
349 let params = assoc_fn_params
350 .iter()
351 .skip(1) .map(|p| p.ty().display(ctx.db, ctx.display_target));
353 for param in params {
354 format_to!(detail, ", {}", param);
355 }
356 } else {
357 let assoc_fn_params = func.assoc_fn_params(ctx.db);
358 format_to!(
359 detail,
360 "{}",
361 assoc_fn_params.iter().map(|p| p.ty().display(ctx.db, ctx.display_target)).format(", ")
362 );
363 }
364
365 if func.is_varargs(ctx.db) {
366 detail.push_str(", ...");
367 }
368}
369
370fn params<'db>(
371 ctx: &CompletionContext<'db>,
372 func: hir::Function,
373 func_kind: &FuncKind<'_>,
374 has_dot_receiver: bool,
375) -> Option<(Option<hir::SelfParam>, Vec<hir::Param<'db>>)> {
376 ctx.config.callable.as_ref()?;
377
378 if let Some(expected) = ctx.expected_type.as_ref().filter(|e| e.is_fn())
380 && let Some(expected) = expected.as_callable(ctx.db)
381 && let Some(completed) = func.ty(ctx.db).as_callable(ctx.db)
382 && expected.sig() == completed.sig()
383 {
384 cov_mark::hit!(no_call_parens_if_fn_ptr_needed);
385 return None;
386 }
387
388 let self_param = if has_dot_receiver || matches!(func_kind, FuncKind::Method(_, Some(_))) {
389 None
390 } else {
391 func.self_param(ctx.db)
392 };
393 Some((self_param, func.params_without_self(ctx.db)))
394}
395
396#[cfg(test)]
397mod tests {
398 use crate::{
399 CallableSnippets, CompletionConfig,
400 tests::{TEST_CONFIG, check_edit, check_edit_with_config},
401 };
402
403 #[test]
404 fn inserts_parens_for_function_calls() {
405 cov_mark::check!(inserts_parens_for_function_calls);
406 check_edit(
407 "no_args",
408 r#"
409fn no_args() {}
410fn main() { no_$0 }
411"#,
412 r#"
413fn no_args() {}
414fn main() { no_args();$0 }
415"#,
416 );
417
418 check_edit(
419 "with_args",
420 r#"
421fn with_args(x: i32, y: String) {}
422fn main() { with_$0 }
423"#,
424 r#"
425fn with_args(x: i32, y: String) {}
426fn main() { with_args(${1:x}, ${2:y});$0 }
427"#,
428 );
429
430 check_edit(
431 "foo",
432 r#"
433struct S;
434impl S {
435 fn foo(&self) -> i32 { 0 }
436}
437fn bar(s: &S) { s.f$0 }
438"#,
439 r#"
440struct S;
441impl S {
442 fn foo(&self) -> i32 { 0 }
443}
444fn bar(s: &S) { s.foo()$0 }
445"#,
446 );
447
448 check_edit(
449 "foo",
450 r#"
451struct S {}
452impl S {
453 fn foo(&self, x: i32) {}
454}
455fn bar(s: &S) {
456 s.f$0
457}
458"#,
459 r#"
460struct S {}
461impl S {
462 fn foo(&self, x: i32) {}
463}
464fn bar(s: &S) {
465 s.foo(${1:x});$0
466}
467"#,
468 );
469
470 check_edit(
471 "foo",
472 r#"
473struct S {}
474impl S {
475 fn foo(&self, x: i32) {
476 $0
477 }
478}
479"#,
480 r#"
481struct S {}
482impl S {
483 fn foo(&self, x: i32) {
484 self.foo(${1:x});$0
485 }
486}
487"#,
488 );
489 }
490
491 #[test]
492 fn parens_for_method_call_as_assoc_fn() {
493 check_edit(
494 "foo",
495 r#"
496struct S;
497impl S {
498 fn foo(&self) {}
499}
500fn main() { S::f$0 }
501"#,
502 r#"
503struct S;
504impl S {
505 fn foo(&self) {}
506}
507fn main() { S::foo(${1:&self});$0 }
508"#,
509 );
510 }
511
512 #[test]
513 fn suppress_arg_snippets() {
514 cov_mark::check!(suppress_arg_snippets);
515 check_edit_with_config(
516 CompletionConfig { callable: Some(CallableSnippets::AddParentheses), ..TEST_CONFIG },
517 "with_args",
518 r#"
519fn with_args(x: i32, y: String) {}
520fn main() { with_$0 }
521"#,
522 r#"
523fn with_args(x: i32, y: String) {}
524fn main() { with_args($0); }
525"#,
526 );
527 }
528
529 #[test]
530 fn strips_underscores_from_args() {
531 check_edit(
532 "foo",
533 r#"
534fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {}
535fn main() { f$0 }
536"#,
537 r#"
538fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {}
539fn main() { foo(${1:foo}, ${2:bar}, ${3:ho_ge_});$0 }
540"#,
541 );
542 }
543
544 #[test]
545 fn insert_ref_when_matching_local_in_scope() {
546 check_edit(
547 "ref_arg",
548 r#"
549struct Foo {}
550fn ref_arg(x: &Foo) {}
551fn main() {
552 let x = Foo {};
553 ref_ar$0
554}
555"#,
556 r#"
557struct Foo {}
558fn ref_arg(x: &Foo) {}
559fn main() {
560 let x = Foo {};
561 ref_arg(${1:&x});$0
562}
563"#,
564 );
565 }
566
567 #[test]
568 fn insert_mut_ref_when_matching_local_in_scope() {
569 check_edit(
570 "ref_arg",
571 r#"
572struct Foo {}
573fn ref_arg(x: &mut Foo) {}
574fn main() {
575 let x = Foo {};
576 ref_ar$0
577}
578"#,
579 r#"
580struct Foo {}
581fn ref_arg(x: &mut Foo) {}
582fn main() {
583 let x = Foo {};
584 ref_arg(${1:&mut x});$0
585}
586"#,
587 );
588 }
589
590 #[test]
591 fn insert_ref_when_matching_local_in_scope_for_method() {
592 check_edit(
593 "apply_foo",
594 r#"
595struct Foo {}
596struct Bar {}
597impl Bar {
598 fn apply_foo(&self, x: &Foo) {}
599}
600
601fn main() {
602 let x = Foo {};
603 let y = Bar {};
604 y.$0
605}
606"#,
607 r#"
608struct Foo {}
609struct Bar {}
610impl Bar {
611 fn apply_foo(&self, x: &Foo) {}
612}
613
614fn main() {
615 let x = Foo {};
616 let y = Bar {};
617 y.apply_foo(${1:&x});$0
618}
619"#,
620 );
621 }
622
623 #[test]
624 fn trim_mut_keyword_in_func_completion() {
625 check_edit(
626 "take_mutably",
627 r#"
628fn take_mutably(mut x: &i32) {}
629
630fn main() {
631 take_m$0
632}
633"#,
634 r#"
635fn take_mutably(mut x: &i32) {}
636
637fn main() {
638 take_mutably(${1:x});$0
639}
640"#,
641 );
642 }
643
644 #[test]
645 fn complete_pattern_args_with_type_name_if_adt() {
646 check_edit(
647 "qux",
648 r#"
649struct Foo {
650 bar: i32
651}
652
653fn qux(Foo { bar }: Foo) {
654 println!("{}", bar);
655}
656
657fn main() {
658 qu$0
659}
660"#,
661 r#"
662struct Foo {
663 bar: i32
664}
665
666fn qux(Foo { bar }: Foo) {
667 println!("{}", bar);
668}
669
670fn main() {
671 qux(${1:foo});$0
672}
673"#,
674 );
675 }
676
677 #[test]
678 fn complete_fn_param() {
679 check_edit(
681 "mut bar: u32",
682 r#"
683fn f(foo: (), mut bar: u32) {}
684fn g(foo: (), mut ba$0)
685"#,
686 r#"
687fn f(foo: (), mut bar: u32) {}
688fn g(foo: (), mut bar: u32)
689"#,
690 );
691
692 check_edit(
694 "mut bar: u32",
695 r#"
696fn g(foo: (), mut ba$0: u32)
697fn f(foo: (), mut bar: u32) {}
698"#,
699 r#"
700fn g(foo: (), mut bar: u32)
701fn f(foo: (), mut bar: u32) {}
702"#,
703 );
704 }
705
706 #[test]
707 fn complete_fn_mut_param_add_comma() {
708 check_edit(
710 ", mut bar: u32,",
711 r#"
712fn f(foo: (), mut bar: u32) {}
713fn g(foo: ()mut ba$0 baz: ())
714"#,
715 r#"
716fn f(foo: (), mut bar: u32) {}
717fn g(foo: (), mut bar: u32, baz: ())
718"#,
719 );
720 }
721
722 #[test]
723 fn complete_fn_mut_param_has_attribute() {
724 check_edit(
725 r#"#[baz = "qux"] mut bar: u32"#,
726 r#"
727fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
728fn g(foo: (), mut ba$0)
729"#,
730 r#"
731fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
732fn g(foo: (), #[baz = "qux"] mut bar: u32)
733"#,
734 );
735
736 check_edit(
737 r#"#[baz = "qux"] mut bar: u32"#,
738 r#"
739fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
740fn g(foo: (), #[baz = "qux"] mut ba$0)
741"#,
742 r#"
743fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
744fn g(foo: (), #[baz = "qux"] mut bar: u32)
745"#,
746 );
747
748 check_edit(
749 r#", #[baz = "qux"] mut bar: u32"#,
750 r#"
751fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
752fn g(foo: ()#[baz = "qux"] mut ba$0)
753"#,
754 r#"
755fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
756fn g(foo: (), #[baz = "qux"] mut bar: u32)
757"#,
758 );
759 }
760
761 #[test]
762 fn complete_semicolon_for_unit() {
763 cov_mark::check!(complete_semicolon);
764 check_edit(
765 r#"foo"#,
766 r#"
767fn foo() {}
768fn bar() {
769 foo$0
770}
771"#,
772 r#"
773fn foo() {}
774fn bar() {
775 foo();$0
776}
777"#,
778 );
779 check_edit(
780 r#"foo"#,
781 r#"
782fn foo(a: i32) {}
783fn bar() {
784 foo$0
785}
786"#,
787 r#"
788fn foo(a: i32) {}
789fn bar() {
790 foo(${1:a});$0
791}
792"#,
793 );
794 check_edit(
795 r#"foo"#,
796 r#"
797fn foo(a: i32) {}
798fn bar() {
799 foo$0;
800}
801"#,
802 r#"
803fn foo(a: i32) {}
804fn bar() {
805 foo(${1:a})$0;
806}
807"#,
808 );
809 check_edit_with_config(
810 CompletionConfig { add_semicolon_to_unit: false, ..TEST_CONFIG },
811 r#"foo"#,
812 r#"
813fn foo(a: i32) {}
814fn bar() {
815 foo$0
816}
817"#,
818 r#"
819fn foo(a: i32) {}
820fn bar() {
821 foo(${1:a})$0
822}
823"#,
824 );
825 }
826
827 #[test]
828 fn complete_comma_for_unit_match_arm() {
829 cov_mark::check!(complete_semicolon);
830 check_edit(
831 r#"foo"#,
832 r#"
833fn foo() {}
834fn bar() {
835 match Some(false) {
836 v => fo$0
837 }
838}
839"#,
840 r#"
841fn foo() {}
842fn bar() {
843 match Some(false) {
844 v => foo(),$0
845 }
846}
847"#,
848 );
849 check_edit(
850 r#"foo"#,
851 r#"
852fn foo() {}
853fn bar() {
854 match Some(false) {
855 v => fo$0,
856 }
857}
858"#,
859 r#"
860fn foo() {}
861fn bar() {
862 match Some(false) {
863 v => foo()$0,
864 }
865}
866"#,
867 );
868 }
869
870 #[test]
871 fn no_semicolon_in_closure_ret() {
872 check_edit(
873 r#"foo"#,
874 r#"
875fn foo() {}
876fn baz(_: impl FnOnce()) {}
877fn bar() {
878 baz(|| fo$0);
879}
880"#,
881 r#"
882fn foo() {}
883fn baz(_: impl FnOnce()) {}
884fn bar() {
885 baz(|| foo()$0);
886}
887"#,
888 );
889 }
890
891 #[test]
892 fn no_semicolon_in_arg_list() {
893 check_edit(
894 r#"foo"#,
895 r#"
896fn foo() {}
897fn baz(_: impl FnOnce()) {}
898fn bar() {
899 baz(fo$0);
900}
901"#,
902 r#"
903fn foo() {}
904fn baz(_: impl FnOnce()) {}
905fn bar() {
906 baz(foo()$0);
907}
908"#,
909 );
910 }
911}