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_ =
237                                ref_of_param(ctx, text, &param.ty().instantiate_with_errors());
238                            f(&format_args!("${{{}:{ref_}{text}}}", index + offset))
239                        }
240                        None => {
241                            let name = match param.ty().as_adt() {
242                                None => "_".to_owned(),
243                                Some(adt) => to_lower_snake_case(adt.name(ctx.db).as_str()),
244                            };
245                            f(&format_args!("${{{}:{name}}}", index + offset))
246                        }
247                    }
248                });
249            match self_param {
250                Some(self_param) => {
251                    format!(
252                        "{}(${{1:{}}}{}{})$0",
253                        escaped_name,
254                        self_param.display(ctx.db, ctx.display_target),
255                        if params.is_empty() { "" } else { ", " },
256                        function_params_snippet
257                    )
258                }
259                None => {
260                    format!("{escaped_name}({function_params_snippet})$0")
261                }
262            }
263        } else {
264            cov_mark::hit!(suppress_arg_snippets);
265            format!("{escaped_name}($0)")
266        };
267
268        (snippet, "(…)")
269    };
270    if ret_type.is_unit() {
271        match ctx.complete_semicolon {
272            CompleteSemicolon::DoNotComplete => {}
273            CompleteSemicolon::CompleteSemi | CompleteSemicolon::CompleteComma => {
274                cov_mark::hit!(complete_semicolon);
275                let ch = if matches!(ctx.complete_semicolon, CompleteSemicolon::CompleteComma) {
276                    ','
277                } else {
278                    ';'
279                };
280                if snippet.ends_with("$0") {
281                    snippet.insert(snippet.len() - "$0".len(), ch);
282                } else {
283                    snippet.push(ch);
284                }
285            }
286        }
287    }
288    builder.label(SmolStr::from_iter([&name, label_suffix])).insert_snippet(cap, snippet)
289}
290
291fn ref_of_param(ctx: &CompletionContext<'_, '_>, arg: &str, ty: &hir::Type<'_>) -> &'static str {
292    if let Some((_, mutability)) = ty.as_reference() {
293        let ref_prefix = if mutability.is_mut() { "&mut " } else { "&" };
294
295        for (name, local) in ctx.locals.iter().sorted_by_key(|&(k, _)| k.clone()) {
296            if name.as_str() == arg {
297                let local_ty = local.ty(ctx.db);
298                let added_ref = local_ty.add_reference(ctx.db, mutability);
299                let needs_ref =
300                    !local_ty.could_coerce_to(ctx.db, ty) && added_ref.could_coerce_to(ctx.db, ty);
301                return if needs_ref { ref_prefix } else { "" };
302            }
303        }
304    }
305    ""
306}
307
308fn detail(ctx: &CompletionContext<'_, '_>, func: hir::Function) -> String {
309    let mut ret_ty = func.ret_type(ctx.db);
310    let mut detail = String::new();
311
312    if func.is_const(ctx.db) {
313        format_to!(detail, "const ");
314    }
315    if func.is_async(ctx.db) {
316        format_to!(detail, "async ");
317        if let Some(async_ret) = func.async_ret_type(ctx.db) {
318            ret_ty = async_ret;
319        }
320    }
321    if func.is_unsafe_to_call(ctx.db, ctx.containing_function, ctx.edition) {
322        format_to!(detail, "unsafe ");
323    }
324
325    detail.push_str("fn(");
326    params_display(ctx, &mut detail, func);
327    detail.push(')');
328    if !ret_ty.is_unit() {
329        format_to!(detail, " -> {}", ret_ty.display(ctx.db, ctx.display_target));
330    }
331    detail
332}
333
334fn detail_full(ctx: &CompletionContext<'_, '_>, func: hir::Function) -> String {
335    let signature = format!("{}", func.display(ctx.db, ctx.display_target));
336    let mut detail = String::with_capacity(signature.len());
337
338    for segment in signature.split_whitespace() {
339        if !detail.is_empty() && !detail.ends_with('(') && !segment.starts_with(')') {
340            detail.push(' ');
341        }
342
343        detail.push_str(segment);
344    }
345
346    detail
347}
348
349fn params_display(ctx: &CompletionContext<'_, '_>, detail: &mut String, func: hir::Function) {
350    if let Some(self_param) = func.self_param(ctx.db) {
351        format_to!(detail, "{}", self_param.display(ctx.db, ctx.display_target));
352        let assoc_fn_params = func.assoc_fn_params(ctx.db);
353        let params = assoc_fn_params
354            .iter()
355            .skip(1) // skip the self param because we are manually handling that
356            .map(|p| p.ty().display(ctx.db, ctx.display_target));
357        for param in params {
358            format_to!(detail, ", {}", param);
359        }
360    } else {
361        let assoc_fn_params = func.assoc_fn_params(ctx.db);
362        format_to!(
363            detail,
364            "{}",
365            assoc_fn_params.iter().map(|p| p.ty().display(ctx.db, ctx.display_target)).format(", ")
366        );
367    }
368
369    if func.is_varargs(ctx.db) {
370        detail.push_str(", ...");
371    }
372}
373
374fn params<'db>(
375    ctx: &CompletionContext<'_, 'db>,
376    func: hir::Function,
377    func_kind: &FuncKind<'_>,
378    has_dot_receiver: bool,
379) -> Option<(Option<hir::SelfParam>, Vec<hir::Param<'db>>)> {
380    ctx.config.callable.as_ref()?;
381
382    // Don't add parentheses if the expected type is a function reference with the same signature.
383    if let Some(expected) = ctx.expected_type.as_ref().filter(|e| e.is_fn())
384        && let Some(expected) = expected.as_callable(ctx.db)
385        && let Some(completed) = func.ty(ctx.db).as_callable(ctx.db)
386        && expected.sig() == completed.sig()
387    {
388        cov_mark::hit!(no_call_parens_if_fn_ptr_needed);
389        return None;
390    }
391
392    let self_param = if has_dot_receiver || matches!(func_kind, FuncKind::Method(_, Some(_))) {
393        None
394    } else {
395        func.self_param(ctx.db)
396    };
397    Some((self_param, func.params_without_self(ctx.db)))
398}
399
400#[cfg(test)]
401mod tests {
402    use crate::{
403        CallableSnippets, CompletionConfig,
404        tests::{TEST_CONFIG, check_edit, check_edit_with_config},
405    };
406
407    #[test]
408    fn inserts_parens_for_function_calls() {
409        cov_mark::check!(inserts_parens_for_function_calls);
410        check_edit(
411            "no_args",
412            r#"
413fn no_args() {}
414fn main() { no_$0 }
415"#,
416            r#"
417fn no_args() {}
418fn main() { no_args();$0 }
419"#,
420        );
421
422        check_edit(
423            "with_args",
424            r#"
425fn with_args(x: i32, y: String) {}
426fn main() { with_$0 }
427"#,
428            r#"
429fn with_args(x: i32, y: String) {}
430fn main() { with_args(${1:x}, ${2:y});$0 }
431"#,
432        );
433
434        check_edit(
435            "foo",
436            r#"
437struct S;
438impl S {
439    fn foo(&self) -> i32 { 0 }
440}
441fn bar(s: &S) { s.f$0 }
442"#,
443            r#"
444struct S;
445impl S {
446    fn foo(&self) -> i32 { 0 }
447}
448fn bar(s: &S) { s.foo()$0 }
449"#,
450        );
451
452        check_edit(
453            "foo",
454            r#"
455struct S {}
456impl S {
457    fn foo(&self, x: i32) {}
458}
459fn bar(s: &S) {
460    s.f$0
461}
462"#,
463            r#"
464struct S {}
465impl S {
466    fn foo(&self, x: i32) {}
467}
468fn bar(s: &S) {
469    s.foo(${1:x});$0
470}
471"#,
472        );
473
474        check_edit(
475            "foo",
476            r#"
477struct S {}
478impl S {
479    fn foo(&self, x: i32, y: &i32) {
480        $0
481    }
482}
483"#,
484            r#"
485struct S {}
486impl S {
487    fn foo(&self, x: i32, y: &i32) {
488        self.foo(${1:x}, ${2:y});$0
489    }
490}
491"#,
492        );
493    }
494
495    #[test]
496    fn parens_for_method_call_as_assoc_fn() {
497        check_edit(
498            "foo",
499            r#"
500struct S;
501impl S {
502    fn foo(&self) {}
503}
504fn main() { S::f$0 }
505"#,
506            r#"
507struct S;
508impl S {
509    fn foo(&self) {}
510}
511fn main() { S::foo(${1:&self});$0 }
512"#,
513        );
514    }
515
516    #[test]
517    fn suppress_arg_snippets() {
518        cov_mark::check!(suppress_arg_snippets);
519        check_edit_with_config(
520            CompletionConfig { callable: Some(CallableSnippets::AddParentheses), ..TEST_CONFIG },
521            "with_args",
522            r#"
523fn with_args(x: i32, y: String) {}
524fn main() { with_$0 }
525"#,
526            r#"
527fn with_args(x: i32, y: String) {}
528fn main() { with_args($0); }
529"#,
530        );
531    }
532
533    #[test]
534    fn strips_underscores_from_args() {
535        check_edit(
536            "foo",
537            r#"
538fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {}
539fn main() { f$0 }
540"#,
541            r#"
542fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {}
543fn main() { foo(${1:foo}, ${2:bar}, ${3:ho_ge_});$0 }
544"#,
545        );
546    }
547
548    #[test]
549    fn insert_ref_when_matching_local_in_scope() {
550        check_edit(
551            "ref_arg",
552            r#"
553struct Foo {}
554fn ref_arg(x: &Foo) {}
555fn main() {
556    let x = Foo {};
557    ref_ar$0
558}
559"#,
560            r#"
561struct Foo {}
562fn ref_arg(x: &Foo) {}
563fn main() {
564    let x = Foo {};
565    ref_arg(${1:&x});$0
566}
567"#,
568        );
569        check_edit(
570            "ref_arg",
571            r#"
572//- minicore: coerce_unsized
573fn ref_arg(x: &[i32]) {}
574fn main() {
575    let x = [2];
576    ref_ar$0
577}
578"#,
579            r#"
580fn ref_arg(x: &[i32]) {}
581fn main() {
582    let x = [2];
583    ref_arg(${1:&x});$0
584}
585"#,
586        );
587    }
588
589    #[test]
590    fn insert_mut_ref_when_matching_local_in_scope() {
591        check_edit(
592            "ref_arg",
593            r#"
594struct Foo {}
595fn ref_arg(x: &mut Foo) {}
596fn main() {
597    let x = Foo {};
598    ref_ar$0
599}
600"#,
601            r#"
602struct Foo {}
603fn ref_arg(x: &mut Foo) {}
604fn main() {
605    let x = Foo {};
606    ref_arg(${1:&mut x});$0
607}
608"#,
609        );
610    }
611
612    #[test]
613    fn insert_ref_when_matching_local_in_scope_for_method() {
614        check_edit(
615            "apply_foo",
616            r#"
617struct Foo {}
618struct Bar {}
619impl Bar {
620    fn apply_foo(&self, x: &Foo) {}
621}
622
623fn main() {
624    let x = Foo {};
625    let y = Bar {};
626    y.$0
627}
628"#,
629            r#"
630struct Foo {}
631struct Bar {}
632impl Bar {
633    fn apply_foo(&self, x: &Foo) {}
634}
635
636fn main() {
637    let x = Foo {};
638    let y = Bar {};
639    y.apply_foo(${1:&x});$0
640}
641"#,
642        );
643    }
644
645    #[test]
646    fn trim_mut_keyword_in_func_completion() {
647        check_edit(
648            "take_mutably",
649            r#"
650fn take_mutably(mut x: &i32) {}
651
652fn main() {
653    take_m$0
654}
655"#,
656            r#"
657fn take_mutably(mut x: &i32) {}
658
659fn main() {
660    take_mutably(${1:x});$0
661}
662"#,
663        );
664    }
665
666    #[test]
667    fn complete_pattern_args_with_type_name_if_adt() {
668        check_edit(
669            "qux",
670            r#"
671struct Foo {
672    bar: i32
673}
674
675fn qux(Foo { bar }: Foo) {
676    println!("{}", bar);
677}
678
679fn main() {
680  qu$0
681}
682"#,
683            r#"
684struct Foo {
685    bar: i32
686}
687
688fn qux(Foo { bar }: Foo) {
689    println!("{}", bar);
690}
691
692fn main() {
693  qux(${1:foo});$0
694}
695"#,
696        );
697    }
698
699    #[test]
700    fn complete_fn_param() {
701        // has mut kw
702        check_edit(
703            "bar: u32",
704            r#"
705fn f(foo: (), mut bar: u32) {}
706fn g(foo: (), mut ba$0)
707"#,
708            r#"
709fn f(foo: (), mut bar: u32) {}
710fn g(foo: (), mut bar: u32)
711"#,
712        );
713
714        // has unmatched mut kw
715        check_edit(
716            "bar: u32",
717            r#"
718fn f(foo: (), bar: u32) {}
719fn g(foo: (), mut ba$0)
720"#,
721            r#"
722fn f(foo: (), bar: u32) {}
723fn g(foo: (), mut bar: u32)
724"#,
725        );
726
727        check_edit(
728            "mut bar: u32",
729            r#"
730fn f(foo: (), mut bar: u32) {}
731fn g(foo: (), ba$0)
732"#,
733            r#"
734fn f(foo: (), mut bar: u32) {}
735fn g(foo: (), mut bar: u32)
736"#,
737        );
738
739        // has type param
740        check_edit(
741            "bar: u32",
742            r#"
743fn g(foo: (), mut ba$0: u32)
744fn f(foo: (), mut bar: u32) {}
745"#,
746            r#"
747fn g(foo: (), mut bar: u32)
748fn f(foo: (), mut bar: u32) {}
749"#,
750        );
751    }
752
753    #[test]
754    fn complete_fn_mut_param_add_comma() {
755        // add leading and trailing comma
756        check_edit(
757            "bar: u32",
758            r#"
759fn f(foo: (), mut bar: u32) {}
760fn g(foo: ()mut ba$0 baz: ())
761"#,
762            r#"
763fn f(foo: (), mut bar: u32) {}
764fn g(foo: (), mut bar: u32, baz: ())
765"#,
766        );
767    }
768
769    #[test]
770    fn complete_fn_mut_param_has_attribute() {
771        check_edit(
772            r#"#[baz = "qux"] mut bar: u32"#,
773            r#"
774fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
775fn g(foo: (), mut ba$0)
776"#,
777            r#"
778fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
779fn g(foo: (), #[baz = "qux"] mut bar: u32)
780"#,
781        );
782
783        check_edit(
784            r#"#[baz = "qux"] mut bar: u32"#,
785            r#"
786fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
787fn g(foo: (), #[baz = "qux"] mut ba$0)
788"#,
789            r#"
790fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
791fn g(foo: (), #[baz = "qux"] mut bar: u32)
792"#,
793        );
794
795        check_edit(
796            r#"#[baz = "qux"] mut bar: u32"#,
797            r#"
798fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
799fn g(foo: ()#[baz = "qux"] mut ba$0)
800"#,
801            r#"
802fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
803fn g(foo: (), #[baz = "qux"] mut bar: u32)
804"#,
805        );
806    }
807
808    #[test]
809    fn complete_semicolon_for_unit() {
810        cov_mark::check!(complete_semicolon);
811        check_edit(
812            r#"foo"#,
813            r#"
814fn foo() {}
815fn bar() {
816    foo$0
817}
818"#,
819            r#"
820fn foo() {}
821fn bar() {
822    foo();$0
823}
824"#,
825        );
826        check_edit(
827            r#"foo"#,
828            r#"
829fn foo(a: i32) {}
830fn bar() {
831    foo$0
832}
833"#,
834            r#"
835fn foo(a: i32) {}
836fn bar() {
837    foo(${1:a});$0
838}
839"#,
840        );
841        check_edit(
842            r#"foo"#,
843            r#"
844fn foo(a: i32) {}
845fn bar() {
846    foo$0;
847}
848"#,
849            r#"
850fn foo(a: i32) {}
851fn bar() {
852    foo(${1:a})$0;
853}
854"#,
855        );
856        check_edit_with_config(
857            CompletionConfig { add_semicolon_to_unit: false, ..TEST_CONFIG },
858            r#"foo"#,
859            r#"
860fn foo(a: i32) {}
861fn bar() {
862    foo$0
863}
864"#,
865            r#"
866fn foo(a: i32) {}
867fn bar() {
868    foo(${1:a})$0
869}
870"#,
871        );
872    }
873
874    #[test]
875    fn complete_comma_for_unit_match_arm() {
876        cov_mark::check!(complete_semicolon);
877        check_edit(
878            r#"foo"#,
879            r#"
880fn foo() {}
881fn bar() {
882    match Some(false) {
883        v => fo$0
884    }
885}
886"#,
887            r#"
888fn foo() {}
889fn bar() {
890    match Some(false) {
891        v => foo(),$0
892    }
893}
894"#,
895        );
896        check_edit(
897            r#"foo"#,
898            r#"
899fn foo() {}
900fn bar() {
901    match Some(false) {
902        v => fo$0,
903    }
904}
905"#,
906            r#"
907fn foo() {}
908fn bar() {
909    match Some(false) {
910        v => foo()$0,
911    }
912}
913"#,
914        );
915    }
916
917    #[test]
918    fn no_semicolon_in_closure_ret() {
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_arg_list() {
940        check_edit(
941            r#"foo"#,
942            r#"
943fn foo() {}
944fn baz(_: impl FnOnce()) {}
945fn bar() {
946    baz(fo$0);
947}
948"#,
949            r#"
950fn foo() {}
951fn baz(_: impl FnOnce()) {}
952fn bar() {
953    baz(foo()$0);
954}
955"#,
956        );
957    }
958
959    #[test]
960    fn no_semicolon_in_array() {
961        check_edit(
962            r#"foo"#,
963            r#"
964fn foo() {}
965fn bar() {
966    let _ = [fo$0];
967}
968"#,
969            r#"
970fn foo() {}
971fn bar() {
972    let _ = [foo()$0];
973}
974"#,
975        );
976    }
977
978    #[test]
979    fn no_semicolon_in_match() {
980        check_edit(
981            r#"foo"#,
982            r#"
983fn foo() {}
984fn bar() {
985    match fo$0 {}
986}
987"#,
988            r#"
989fn foo() {}
990fn bar() {
991    match foo()$0 {}
992}
993"#,
994        );
995    }
996}