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