1 //! Renderer for function calls.
2
3 use hir::{db::HirDatabase, AsAssocItem, HirDisplay};
4 use ide_db::{SnippetCap, SymbolKind};
5 use itertools::Itertools;
6 use stdx::{format_to, to_lower_snake_case};
7 use syntax::{AstNode, SmolStr};
8
9 use crate::{
10 context::{CompletionContext, DotAccess, DotAccessKind, PathCompletionCtx, PathKind},
11 item::{Builder, CompletionItem, CompletionItemKind, CompletionRelevance},
12 render::{compute_exact_name_match, compute_ref_match, compute_type_match, RenderContext},
13 CallableSnippets,
14 };
15
16 #[derive(Debug)]
17 enum FuncKind<'ctx> {
18 Function(&'ctx PathCompletionCtx),
19 Method(&'ctx DotAccess, Option<hir::Name>),
20 }
21
render_fn( ctx: RenderContext<'_>, path_ctx: &PathCompletionCtx, local_name: Option<hir::Name>, func: hir::Function, ) -> Builder22 pub(crate) fn render_fn(
23 ctx: RenderContext<'_>,
24 path_ctx: &PathCompletionCtx,
25 local_name: Option<hir::Name>,
26 func: hir::Function,
27 ) -> Builder {
28 let _p = profile::span("render_fn");
29 render(ctx, local_name, func, FuncKind::Function(path_ctx))
30 }
31
render_method( ctx: RenderContext<'_>, dot_access: &DotAccess, receiver: Option<hir::Name>, local_name: Option<hir::Name>, func: hir::Function, ) -> Builder32 pub(crate) fn render_method(
33 ctx: RenderContext<'_>,
34 dot_access: &DotAccess,
35 receiver: Option<hir::Name>,
36 local_name: Option<hir::Name>,
37 func: hir::Function,
38 ) -> Builder {
39 let _p = profile::span("render_method");
40 render(ctx, local_name, func, FuncKind::Method(dot_access, receiver))
41 }
42
43 fn render(
44 ctx @ RenderContext { completion, .. }: RenderContext<'_>,
45 local_name: Option<hir::Name>,
46 func: hir::Function,
47 func_kind: FuncKind<'_>,
48 ) -> Builder {
49 let db = completion.db;
50
51 let name = local_name.unwrap_or_else(|| func.name(db));
52
53 let (call, escaped_call) = match &func_kind {
54 FuncKind::Method(_, Some(receiver)) => (
55 format!(
56 "{}.{}",
57 receiver.unescaped().display(ctx.db()),
58 name.unescaped().display(ctx.db())
59 )
60 .into(),
61 format!("{}.{}", receiver.display(ctx.db()), name.display(ctx.db())).into(),
62 ),
63 _ => (name.unescaped().to_smol_str(), name.to_smol_str()),
64 };
65 let mut item = CompletionItem::new(
66 if func.self_param(db).is_some() {
67 CompletionItemKind::Method
68 } else {
69 CompletionItemKind::SymbolKind(SymbolKind::Function)
70 },
71 ctx.source_range(),
72 call.clone(),
73 );
74
75 let ret_type = func.ret_type(db);
76 let is_op_method = func
77 .as_assoc_item(ctx.db())
78 .and_then(|trait_| trait_.containing_trait_or_trait_impl(ctx.db()))
79 .map_or(false, |trait_| completion.is_ops_trait(trait_));
80 item.set_relevance(CompletionRelevance {
81 type_match: compute_type_match(completion, &ret_type),
82 exact_name_match: compute_exact_name_match(completion, &call),
83 is_op_method,
84 ..ctx.completion_relevance()
85 });
86
87 match func_kind {
88 FuncKind::Function(path_ctx) => {
89 super::path_ref_match(completion, path_ctx, &ret_type, &mut item);
90 }
91 FuncKind::Method(DotAccess { receiver: Some(receiver), .. }, _) => {
92 if let Some(original_expr) = completion.sema.original_ast_node(receiver.clone()) {
93 if let Some(ref_match) = compute_ref_match(completion, &ret_type) {
94 item.ref_match(ref_match, original_expr.syntax().text_range().start());
95 }
96 }
97 }
98 _ => (),
99 }
100
101 item.set_documentation(ctx.docs(func))
102 .set_deprecated(ctx.is_deprecated(func) || ctx.is_deprecated_assoc_item(func))
103 .detail(detail(db, func))
104 .lookup_by(name.unescaped().to_smol_str());
105
106 match ctx.completion.config.snippet_cap {
107 Some(cap) => {
108 let complete_params = match func_kind {
109 FuncKind::Function(PathCompletionCtx {
110 kind: PathKind::Expr { .. },
111 has_call_parens: false,
112 ..
113 }) => Some(false),
114 FuncKind::Method(
115 DotAccess {
116 kind:
117 DotAccessKind::Method { has_parens: false } | DotAccessKind::Field { .. },
118 ..
119 },
120 _,
121 ) => Some(true),
122 _ => None,
123 };
124 if let Some(has_dot_receiver) = complete_params {
125 if let Some((self_param, params)) =
126 params(ctx.completion, func, &func_kind, has_dot_receiver)
127 {
128 add_call_parens(
129 &mut item,
130 completion,
131 cap,
132 call,
133 escaped_call,
134 self_param,
135 params,
136 );
137 }
138 }
139 }
140 _ => (),
141 };
142
143 match ctx.import_to_add {
144 Some(import_to_add) => {
145 item.add_import(import_to_add);
146 }
147 None => {
148 if let Some(actm) = func.as_assoc_item(db) {
149 if let Some(trt) = actm.containing_trait_or_trait_impl(db) {
150 item.trait_name(trt.name(db).to_smol_str());
151 }
152 }
153 }
154 }
155
156 item.doc_aliases(ctx.doc_aliases);
157 item
158 }
159
add_call_parens<'b>( builder: &'b mut Builder, ctx: &CompletionContext<'_>, cap: SnippetCap, name: SmolStr, escaped_name: SmolStr, self_param: Option<hir::SelfParam>, params: Vec<hir::Param>, ) -> &'b mut Builder160 pub(super) fn add_call_parens<'b>(
161 builder: &'b mut Builder,
162 ctx: &CompletionContext<'_>,
163 cap: SnippetCap,
164 name: SmolStr,
165 escaped_name: SmolStr,
166 self_param: Option<hir::SelfParam>,
167 params: Vec<hir::Param>,
168 ) -> &'b mut Builder {
169 cov_mark::hit!(inserts_parens_for_function_calls);
170
171 let (snippet, label_suffix) = if self_param.is_none() && params.is_empty() {
172 (format!("{escaped_name}()$0"), "()")
173 } else {
174 builder.trigger_call_info();
175 let snippet = if let Some(CallableSnippets::FillArguments) = ctx.config.callable {
176 let offset = if self_param.is_some() { 2 } else { 1 };
177 let function_params_snippet =
178 params.iter().enumerate().format_with(", ", |(index, param), f| {
179 match param.name(ctx.db) {
180 Some(n) => {
181 let smol_str = n.to_smol_str();
182 let text = smol_str.as_str().trim_start_matches('_');
183 let ref_ = ref_of_param(ctx, text, param.ty());
184 f(&format_args!("${{{}:{ref_}{text}}}", index + offset))
185 }
186 None => {
187 let name = match param.ty().as_adt() {
188 None => "_".to_string(),
189 Some(adt) => adt
190 .name(ctx.db)
191 .as_text()
192 .map(|s| to_lower_snake_case(s.as_str()))
193 .unwrap_or_else(|| "_".to_string()),
194 };
195 f(&format_args!("${{{}:{name}}}", index + offset))
196 }
197 }
198 });
199 match self_param {
200 Some(self_param) => {
201 format!(
202 "{}(${{1:{}}}{}{})$0",
203 escaped_name,
204 self_param.display(ctx.db),
205 if params.is_empty() { "" } else { ", " },
206 function_params_snippet
207 )
208 }
209 None => {
210 format!("{escaped_name}({function_params_snippet})$0")
211 }
212 }
213 } else {
214 cov_mark::hit!(suppress_arg_snippets);
215 format!("{escaped_name}($0)")
216 };
217
218 (snippet, "(…)")
219 };
220 builder.label(SmolStr::from_iter([&name, label_suffix])).insert_snippet(cap, snippet)
221 }
222
ref_of_param(ctx: &CompletionContext<'_>, arg: &str, ty: &hir::Type) -> &'static str223 fn ref_of_param(ctx: &CompletionContext<'_>, arg: &str, ty: &hir::Type) -> &'static str {
224 if let Some(derefed_ty) = ty.remove_ref() {
225 for (name, local) in ctx.locals.iter() {
226 if name.as_text().as_deref() == Some(arg) {
227 return if local.ty(ctx.db) == derefed_ty {
228 if ty.is_mutable_reference() {
229 "&mut "
230 } else {
231 "&"
232 }
233 } else {
234 ""
235 };
236 }
237 }
238 }
239 ""
240 }
241
detail(db: &dyn HirDatabase, func: hir::Function) -> String242 fn detail(db: &dyn HirDatabase, func: hir::Function) -> String {
243 let mut ret_ty = func.ret_type(db);
244 let mut detail = String::new();
245
246 if func.is_const(db) {
247 format_to!(detail, "const ");
248 }
249 if func.is_async(db) {
250 format_to!(detail, "async ");
251 if let Some(async_ret) = func.async_ret_type(db) {
252 ret_ty = async_ret;
253 }
254 }
255 if func.is_unsafe_to_call(db) {
256 format_to!(detail, "unsafe ");
257 }
258
259 format_to!(detail, "fn({})", params_display(db, func));
260 if !ret_ty.is_unit() {
261 format_to!(detail, " -> {}", ret_ty.display(db));
262 }
263 detail
264 }
265
params_display(db: &dyn HirDatabase, func: hir::Function) -> String266 fn params_display(db: &dyn HirDatabase, func: hir::Function) -> String {
267 if let Some(self_param) = func.self_param(db) {
268 let assoc_fn_params = func.assoc_fn_params(db);
269 let params = assoc_fn_params
270 .iter()
271 .skip(1) // skip the self param because we are manually handling that
272 .map(|p| p.ty().display(db));
273 format!(
274 "{}{}",
275 self_param.display(db),
276 params.format_with("", |display, f| {
277 f(&", ")?;
278 f(&display)
279 })
280 )
281 } else {
282 let assoc_fn_params = func.assoc_fn_params(db);
283 assoc_fn_params.iter().map(|p| p.ty().display(db)).join(", ")
284 }
285 }
286
params( ctx: &CompletionContext<'_>, func: hir::Function, func_kind: &FuncKind<'_>, has_dot_receiver: bool, ) -> Option<(Option<hir::SelfParam>, Vec<hir::Param>)>287 fn params(
288 ctx: &CompletionContext<'_>,
289 func: hir::Function,
290 func_kind: &FuncKind<'_>,
291 has_dot_receiver: bool,
292 ) -> Option<(Option<hir::SelfParam>, Vec<hir::Param>)> {
293 if ctx.config.callable.is_none() {
294 return None;
295 }
296
297 // Don't add parentheses if the expected type is some function reference.
298 if let Some(ty) = &ctx.expected_type {
299 // FIXME: check signature matches?
300 if ty.is_fn() {
301 cov_mark::hit!(no_call_parens_if_fn_ptr_needed);
302 return None;
303 }
304 }
305
306 let self_param = if has_dot_receiver || matches!(func_kind, FuncKind::Method(_, Some(_))) {
307 None
308 } else {
309 func.self_param(ctx.db)
310 };
311 Some((self_param, func.params_without_self(ctx.db)))
312 }
313
314 #[cfg(test)]
315 mod tests {
316 use crate::{
317 tests::{check_edit, check_edit_with_config, TEST_CONFIG},
318 CallableSnippets, CompletionConfig,
319 };
320
321 #[test]
inserts_parens_for_function_calls()322 fn inserts_parens_for_function_calls() {
323 cov_mark::check!(inserts_parens_for_function_calls);
324 check_edit(
325 "no_args",
326 r#"
327 fn no_args() {}
328 fn main() { no_$0 }
329 "#,
330 r#"
331 fn no_args() {}
332 fn main() { no_args()$0 }
333 "#,
334 );
335
336 check_edit(
337 "with_args",
338 r#"
339 fn with_args(x: i32, y: String) {}
340 fn main() { with_$0 }
341 "#,
342 r#"
343 fn with_args(x: i32, y: String) {}
344 fn main() { with_args(${1:x}, ${2:y})$0 }
345 "#,
346 );
347
348 check_edit(
349 "foo",
350 r#"
351 struct S;
352 impl S {
353 fn foo(&self) {}
354 }
355 fn bar(s: &S) { s.f$0 }
356 "#,
357 r#"
358 struct S;
359 impl S {
360 fn foo(&self) {}
361 }
362 fn bar(s: &S) { s.foo()$0 }
363 "#,
364 );
365
366 check_edit(
367 "foo",
368 r#"
369 struct S {}
370 impl S {
371 fn foo(&self, x: i32) {}
372 }
373 fn bar(s: &S) {
374 s.f$0
375 }
376 "#,
377 r#"
378 struct S {}
379 impl S {
380 fn foo(&self, x: i32) {}
381 }
382 fn bar(s: &S) {
383 s.foo(${1:x})$0
384 }
385 "#,
386 );
387
388 check_edit(
389 "foo",
390 r#"
391 struct S {}
392 impl S {
393 fn foo(&self, x: i32) {
394 $0
395 }
396 }
397 "#,
398 r#"
399 struct S {}
400 impl S {
401 fn foo(&self, x: i32) {
402 self.foo(${1:x})$0
403 }
404 }
405 "#,
406 );
407 }
408
409 #[test]
parens_for_method_call_as_assoc_fn()410 fn parens_for_method_call_as_assoc_fn() {
411 check_edit(
412 "foo",
413 r#"
414 struct S;
415 impl S {
416 fn foo(&self) {}
417 }
418 fn main() { S::f$0 }
419 "#,
420 r#"
421 struct S;
422 impl S {
423 fn foo(&self) {}
424 }
425 fn main() { S::foo(${1:&self})$0 }
426 "#,
427 );
428 }
429
430 #[test]
suppress_arg_snippets()431 fn suppress_arg_snippets() {
432 cov_mark::check!(suppress_arg_snippets);
433 check_edit_with_config(
434 CompletionConfig { callable: Some(CallableSnippets::AddParentheses), ..TEST_CONFIG },
435 "with_args",
436 r#"
437 fn with_args(x: i32, y: String) {}
438 fn main() { with_$0 }
439 "#,
440 r#"
441 fn with_args(x: i32, y: String) {}
442 fn main() { with_args($0) }
443 "#,
444 );
445 }
446
447 #[test]
strips_underscores_from_args()448 fn strips_underscores_from_args() {
449 check_edit(
450 "foo",
451 r#"
452 fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {}
453 fn main() { f$0 }
454 "#,
455 r#"
456 fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {}
457 fn main() { foo(${1:foo}, ${2:bar}, ${3:ho_ge_})$0 }
458 "#,
459 );
460 }
461
462 #[test]
insert_ref_when_matching_local_in_scope()463 fn insert_ref_when_matching_local_in_scope() {
464 check_edit(
465 "ref_arg",
466 r#"
467 struct Foo {}
468 fn ref_arg(x: &Foo) {}
469 fn main() {
470 let x = Foo {};
471 ref_ar$0
472 }
473 "#,
474 r#"
475 struct Foo {}
476 fn ref_arg(x: &Foo) {}
477 fn main() {
478 let x = Foo {};
479 ref_arg(${1:&x})$0
480 }
481 "#,
482 );
483 }
484
485 #[test]
insert_mut_ref_when_matching_local_in_scope()486 fn insert_mut_ref_when_matching_local_in_scope() {
487 check_edit(
488 "ref_arg",
489 r#"
490 struct Foo {}
491 fn ref_arg(x: &mut Foo) {}
492 fn main() {
493 let x = Foo {};
494 ref_ar$0
495 }
496 "#,
497 r#"
498 struct Foo {}
499 fn ref_arg(x: &mut Foo) {}
500 fn main() {
501 let x = Foo {};
502 ref_arg(${1:&mut x})$0
503 }
504 "#,
505 );
506 }
507
508 #[test]
insert_ref_when_matching_local_in_scope_for_method()509 fn insert_ref_when_matching_local_in_scope_for_method() {
510 check_edit(
511 "apply_foo",
512 r#"
513 struct Foo {}
514 struct Bar {}
515 impl Bar {
516 fn apply_foo(&self, x: &Foo) {}
517 }
518
519 fn main() {
520 let x = Foo {};
521 let y = Bar {};
522 y.$0
523 }
524 "#,
525 r#"
526 struct Foo {}
527 struct Bar {}
528 impl Bar {
529 fn apply_foo(&self, x: &Foo) {}
530 }
531
532 fn main() {
533 let x = Foo {};
534 let y = Bar {};
535 y.apply_foo(${1:&x})$0
536 }
537 "#,
538 );
539 }
540
541 #[test]
trim_mut_keyword_in_func_completion()542 fn trim_mut_keyword_in_func_completion() {
543 check_edit(
544 "take_mutably",
545 r#"
546 fn take_mutably(mut x: &i32) {}
547
548 fn main() {
549 take_m$0
550 }
551 "#,
552 r#"
553 fn take_mutably(mut x: &i32) {}
554
555 fn main() {
556 take_mutably(${1:x})$0
557 }
558 "#,
559 );
560 }
561
562 #[test]
complete_pattern_args_with_type_name_if_adt()563 fn complete_pattern_args_with_type_name_if_adt() {
564 check_edit(
565 "qux",
566 r#"
567 struct Foo {
568 bar: i32
569 }
570
571 fn qux(Foo { bar }: Foo) {
572 println!("{}", bar);
573 }
574
575 fn main() {
576 qu$0
577 }
578 "#,
579 r#"
580 struct Foo {
581 bar: i32
582 }
583
584 fn qux(Foo { bar }: Foo) {
585 println!("{}", bar);
586 }
587
588 fn main() {
589 qux(${1:foo})$0
590 }
591 "#,
592 );
593 }
594
595 #[test]
complete_fn_param()596 fn complete_fn_param() {
597 // has mut kw
598 check_edit(
599 "mut bar: u32",
600 r#"
601 fn f(foo: (), mut bar: u32) {}
602 fn g(foo: (), mut ba$0)
603 "#,
604 r#"
605 fn f(foo: (), mut bar: u32) {}
606 fn g(foo: (), mut bar: u32)
607 "#,
608 );
609
610 // has type param
611 check_edit(
612 "mut bar: u32",
613 r#"
614 fn g(foo: (), mut ba$0: u32)
615 fn f(foo: (), mut bar: u32) {}
616 "#,
617 r#"
618 fn g(foo: (), mut bar: u32)
619 fn f(foo: (), mut bar: u32) {}
620 "#,
621 );
622 }
623
624 #[test]
complete_fn_mut_param_add_comma()625 fn complete_fn_mut_param_add_comma() {
626 // add leading and trailing comma
627 check_edit(
628 ", mut bar: u32,",
629 r#"
630 fn f(foo: (), mut bar: u32) {}
631 fn g(foo: ()mut ba$0 baz: ())
632 "#,
633 r#"
634 fn f(foo: (), mut bar: u32) {}
635 fn g(foo: (), mut bar: u32, baz: ())
636 "#,
637 );
638 }
639
640 #[test]
complete_fn_mut_param_has_attribute()641 fn complete_fn_mut_param_has_attribute() {
642 check_edit(
643 r#"#[baz = "qux"] mut bar: u32"#,
644 r#"
645 fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
646 fn g(foo: (), mut ba$0)
647 "#,
648 r#"
649 fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
650 fn g(foo: (), #[baz = "qux"] mut bar: u32)
651 "#,
652 );
653
654 check_edit(
655 r#"#[baz = "qux"] mut bar: u32"#,
656 r#"
657 fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
658 fn g(foo: (), #[baz = "qux"] mut ba$0)
659 "#,
660 r#"
661 fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
662 fn g(foo: (), #[baz = "qux"] mut bar: u32)
663 "#,
664 );
665
666 check_edit(
667 r#", #[baz = "qux"] mut bar: u32"#,
668 r#"
669 fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
670 fn g(foo: ()#[baz = "qux"] mut ba$0)
671 "#,
672 r#"
673 fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
674 fn g(foo: (), #[baz = "qux"] mut bar: u32)
675 "#,
676 );
677 }
678 }
679