• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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