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