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 = 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 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 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 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, ¶m.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) .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 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 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 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 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 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}