ide_completion/render/
function.rs

1//! Renderer for function calls.
2
3use 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        // fn([..]) -> Self
192        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        // fn([..]) -> Result<Self, E> OR Wrapped<Foo, Self>
198        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        // fn([..]) -> [..]Builder
205        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) // skip the self param because we are manually handling that
352            .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    // Don't add parentheses if the expected type is a function reference with the same signature.
379    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        // has mut kw
680        check_edit(
681            "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        // has unmatched mut kw
693        check_edit(
694            "bar: u32",
695            r#"
696fn f(foo: (), bar: u32) {}
697fn g(foo: (), mut ba$0)
698"#,
699            r#"
700fn f(foo: (), bar: u32) {}
701fn g(foo: (), mut bar: u32)
702"#,
703        );
704
705        check_edit(
706            "mut bar: u32",
707            r#"
708fn f(foo: (), mut bar: u32) {}
709fn g(foo: (), ba$0)
710"#,
711            r#"
712fn f(foo: (), mut bar: u32) {}
713fn g(foo: (), mut bar: u32)
714"#,
715        );
716
717        // has type param
718        check_edit(
719            "bar: u32",
720            r#"
721fn g(foo: (), mut ba$0: u32)
722fn f(foo: (), mut bar: u32) {}
723"#,
724            r#"
725fn g(foo: (), mut bar: u32)
726fn f(foo: (), mut bar: u32) {}
727"#,
728        );
729    }
730
731    #[test]
732    fn complete_fn_mut_param_add_comma() {
733        // add leading and trailing comma
734        check_edit(
735            "bar: u32",
736            r#"
737fn f(foo: (), mut bar: u32) {}
738fn g(foo: ()mut ba$0 baz: ())
739"#,
740            r#"
741fn f(foo: (), mut bar: u32) {}
742fn g(foo: (), mut bar: u32, baz: ())
743"#,
744        );
745    }
746
747    #[test]
748    fn complete_fn_mut_param_has_attribute() {
749        check_edit(
750            r#"#[baz = "qux"] mut bar: u32"#,
751            r#"
752fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
753fn g(foo: (), mut ba$0)
754"#,
755            r#"
756fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
757fn g(foo: (), #[baz = "qux"] mut bar: u32)
758"#,
759        );
760
761        check_edit(
762            r#"#[baz = "qux"] mut bar: u32"#,
763            r#"
764fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
765fn g(foo: (), #[baz = "qux"] mut ba$0)
766"#,
767            r#"
768fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
769fn g(foo: (), #[baz = "qux"] mut bar: u32)
770"#,
771        );
772
773        check_edit(
774            r#"#[baz = "qux"] mut bar: u32"#,
775            r#"
776fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
777fn g(foo: ()#[baz = "qux"] mut ba$0)
778"#,
779            r#"
780fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
781fn g(foo: (), #[baz = "qux"] mut bar: u32)
782"#,
783        );
784    }
785
786    #[test]
787    fn complete_semicolon_for_unit() {
788        cov_mark::check!(complete_semicolon);
789        check_edit(
790            r#"foo"#,
791            r#"
792fn foo() {}
793fn bar() {
794    foo$0
795}
796"#,
797            r#"
798fn foo() {}
799fn bar() {
800    foo();$0
801}
802"#,
803        );
804        check_edit(
805            r#"foo"#,
806            r#"
807fn foo(a: i32) {}
808fn bar() {
809    foo$0
810}
811"#,
812            r#"
813fn foo(a: i32) {}
814fn bar() {
815    foo(${1:a});$0
816}
817"#,
818        );
819        check_edit(
820            r#"foo"#,
821            r#"
822fn foo(a: i32) {}
823fn bar() {
824    foo$0;
825}
826"#,
827            r#"
828fn foo(a: i32) {}
829fn bar() {
830    foo(${1:a})$0;
831}
832"#,
833        );
834        check_edit_with_config(
835            CompletionConfig { add_semicolon_to_unit: false, ..TEST_CONFIG },
836            r#"foo"#,
837            r#"
838fn foo(a: i32) {}
839fn bar() {
840    foo$0
841}
842"#,
843            r#"
844fn foo(a: i32) {}
845fn bar() {
846    foo(${1:a})$0
847}
848"#,
849        );
850    }
851
852    #[test]
853    fn complete_comma_for_unit_match_arm() {
854        cov_mark::check!(complete_semicolon);
855        check_edit(
856            r#"foo"#,
857            r#"
858fn foo() {}
859fn bar() {
860    match Some(false) {
861        v => fo$0
862    }
863}
864"#,
865            r#"
866fn foo() {}
867fn bar() {
868    match Some(false) {
869        v => foo(),$0
870    }
871}
872"#,
873        );
874        check_edit(
875            r#"foo"#,
876            r#"
877fn foo() {}
878fn bar() {
879    match Some(false) {
880        v => fo$0,
881    }
882}
883"#,
884            r#"
885fn foo() {}
886fn bar() {
887    match Some(false) {
888        v => foo()$0,
889    }
890}
891"#,
892        );
893    }
894
895    #[test]
896    fn no_semicolon_in_closure_ret() {
897        check_edit(
898            r#"foo"#,
899            r#"
900fn foo() {}
901fn baz(_: impl FnOnce()) {}
902fn bar() {
903    baz(|| fo$0);
904}
905"#,
906            r#"
907fn foo() {}
908fn baz(_: impl FnOnce()) {}
909fn bar() {
910    baz(|| foo()$0);
911}
912"#,
913        );
914    }
915
916    #[test]
917    fn no_semicolon_in_arg_list() {
918        check_edit(
919            r#"foo"#,
920            r#"
921fn foo() {}
922fn baz(_: impl FnOnce()) {}
923fn bar() {
924    baz(fo$0);
925}
926"#,
927            r#"
928fn foo() {}
929fn baz(_: impl FnOnce()) {}
930fn bar() {
931    baz(foo()$0);
932}
933"#,
934        );
935    }
936
937    #[test]
938    fn no_semicolon_in_array() {
939        check_edit(
940            r#"foo"#,
941            r#"
942fn foo() {}
943fn bar() {
944    let _ = [fo$0];
945}
946"#,
947            r#"
948fn foo() {}
949fn bar() {
950    let _ = [foo()$0];
951}
952"#,
953        );
954    }
955}