Skip to main content

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