]> git.lizzy.rs Git - rust.git/blob - crates/ide_completion/src/render/function.rs
Merge #9734
[rust.git] / crates / ide_completion / src / render / function.rs
1 //! Renderer for function calls.
2
3 use hir::{AsAssocItem, HasSource, HirDisplay};
4 use ide_db::SymbolKind;
5 use itertools::Itertools;
6 use syntax::ast;
7
8 use crate::{
9     item::{CompletionItem, CompletionItemKind, CompletionKind, CompletionRelevance, ImportEdit},
10     render::{
11         builder_ext::Params, compute_exact_name_match, compute_ref_match, compute_type_match,
12         RenderContext,
13     },
14 };
15
16 pub(crate) fn render_fn(
17     ctx: RenderContext<'_>,
18     import_to_add: Option<ImportEdit>,
19     local_name: Option<hir::Name>,
20     fn_: hir::Function,
21 ) -> Option<CompletionItem> {
22     let _p = profile::span("render_fn");
23     Some(FunctionRender::new(ctx, None, local_name, fn_, false)?.render(import_to_add))
24 }
25
26 pub(crate) fn render_method(
27     ctx: RenderContext<'_>,
28     import_to_add: Option<ImportEdit>,
29     receiver: Option<hir::Name>,
30     local_name: Option<hir::Name>,
31     fn_: hir::Function,
32 ) -> Option<CompletionItem> {
33     let _p = profile::span("render_method");
34     Some(FunctionRender::new(ctx, receiver, local_name, fn_, true)?.render(import_to_add))
35 }
36
37 #[derive(Debug)]
38 struct FunctionRender<'a> {
39     ctx: RenderContext<'a>,
40     name: String,
41     receiver: Option<hir::Name>,
42     func: hir::Function,
43     /// NB: having `ast::Fn` here might or might not be a good idea. The problem
44     /// with it is that, to get an `ast::`, you want to parse the corresponding
45     /// source file. So, when flyimport completions suggest a bunch of
46     /// functions, we spend quite some time parsing many files.
47     ///
48     /// We need ast because we want to access parameter names (patterns). We can
49     /// add them to the hir of the function itself, but parameter names are not
50     /// something hir cares otherwise.
51     ///
52     /// Alternatively we can reconstruct params from the function body, but that
53     /// would require parsing anyway.
54     ///
55     /// It seems that just using `ast` is the best choice -- most of parses
56     /// should be cached anyway.
57     ast_node: ast::Fn,
58     is_method: bool,
59 }
60
61 impl<'a> FunctionRender<'a> {
62     fn new(
63         ctx: RenderContext<'a>,
64         receiver: Option<hir::Name>,
65         local_name: Option<hir::Name>,
66         fn_: hir::Function,
67         is_method: bool,
68     ) -> Option<FunctionRender<'a>> {
69         let name = local_name.unwrap_or_else(|| fn_.name(ctx.db())).to_string();
70         let ast_node = fn_.source(ctx.db())?.value;
71
72         Some(FunctionRender { ctx, name, receiver, func: fn_, ast_node, is_method })
73     }
74
75     fn render(self, import_to_add: Option<ImportEdit>) -> CompletionItem {
76         let params = self.params();
77         let call = if let Some(receiver) = &self.receiver {
78             format!("{}.{}", receiver, &self.name)
79         } else {
80             self.name.clone()
81         };
82         let mut item =
83             CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), call.clone());
84         item.kind(self.kind())
85             .set_documentation(self.ctx.docs(self.func))
86             .set_deprecated(
87                 self.ctx.is_deprecated(self.func) || self.ctx.is_deprecated_assoc_item(self.func),
88             )
89             .detail(self.detail())
90             .add_call_parens(self.ctx.completion, call.clone(), params);
91
92         if import_to_add.is_none() {
93             let db = self.ctx.db();
94             if let Some(actm) = self.func.as_assoc_item(db) {
95                 if let Some(trt) = actm.containing_trait_or_trait_impl(db) {
96                     item.trait_name(trt.name(db).to_string());
97                 }
98             }
99         }
100
101         item.add_import(import_to_add).lookup_by(self.name);
102
103         let ret_type = self.func.ret_type(self.ctx.db());
104         item.set_relevance(CompletionRelevance {
105             type_match: compute_type_match(self.ctx.completion, &ret_type),
106             exact_name_match: compute_exact_name_match(self.ctx.completion, &call),
107             ..CompletionRelevance::default()
108         });
109
110         if let Some(ref_match) = compute_ref_match(self.ctx.completion, &ret_type) {
111             // FIXME
112             // For now we don't properly calculate the edits for ref match
113             // completions on methods, so we've disabled them. See #8058.
114             if !self.is_method {
115                 item.ref_match(ref_match);
116             }
117         }
118
119         item.build()
120     }
121
122     fn detail(&self) -> String {
123         let ret_ty = self.func.ret_type(self.ctx.db());
124         let ret = if ret_ty.is_unit() {
125             // Omit the return type if it is the unit type
126             String::new()
127         } else {
128             format!(" {}", self.ty_display())
129         };
130
131         format!("fn({}){}", self.params_display(), ret)
132     }
133
134     fn params_display(&self) -> String {
135         if let Some(self_param) = self.func.self_param(self.ctx.db()) {
136             let params = self
137                 .func
138                 .assoc_fn_params(self.ctx.db())
139                 .into_iter()
140                 .skip(1) // skip the self param because we are manually handling that
141                 .map(|p| p.ty().display(self.ctx.db()).to_string());
142
143             std::iter::once(self_param.display(self.ctx.db()).to_owned()).chain(params).join(", ")
144         } else {
145             let params = self
146                 .func
147                 .assoc_fn_params(self.ctx.db())
148                 .into_iter()
149                 .map(|p| p.ty().display(self.ctx.db()).to_string())
150                 .join(", ");
151             params
152         }
153     }
154
155     fn ty_display(&self) -> String {
156         let ret_ty = self.func.ret_type(self.ctx.db());
157
158         format!("-> {}", ret_ty.display(self.ctx.db()))
159     }
160
161     fn add_arg(&self, arg: &str, ty: &hir::Type) -> String {
162         if let Some(derefed_ty) = ty.remove_ref() {
163             for (name, local) in self.ctx.completion.locals.iter() {
164                 if name == arg && local.ty(self.ctx.db()) == derefed_ty {
165                     let mutability = if ty.is_mutable_reference() { "&mut " } else { "&" };
166                     return format!("{}{}", mutability, arg);
167                 }
168             }
169         }
170         arg.to_string()
171     }
172
173     fn params(&self) -> Params {
174         let ast_params = match self.ast_node.param_list() {
175             Some(it) => it,
176             None => return Params::Named(Vec::new()),
177         };
178
179         let mut params_pats = Vec::new();
180         let params_ty = if self.ctx.completion.has_dot_receiver() || self.receiver.is_some() {
181             self.func.method_params(self.ctx.db()).unwrap_or_default()
182         } else {
183             if let Some(s) = ast_params.self_param() {
184                 cov_mark::hit!(parens_for_method_call_as_assoc_fn);
185                 params_pats.push(Some(s.to_string()));
186             }
187             self.func.assoc_fn_params(self.ctx.db())
188         };
189         params_pats
190             .extend(ast_params.params().into_iter().map(|it| it.pat().map(|it| it.to_string())));
191
192         let params = params_pats
193             .into_iter()
194             .zip(params_ty)
195             .flat_map(|(pat, param_ty)| {
196                 let pat = pat?;
197                 let name = pat;
198                 let arg = name.trim_start_matches("mut ").trim_start_matches('_');
199                 Some(self.add_arg(arg, param_ty.ty()))
200             })
201             .collect();
202         Params::Named(params)
203     }
204
205     fn kind(&self) -> CompletionItemKind {
206         if self.func.self_param(self.ctx.db()).is_some() {
207             CompletionItemKind::Method
208         } else {
209             SymbolKind::Function.into()
210         }
211     }
212 }
213
214 #[cfg(test)]
215 mod tests {
216     use crate::{
217         tests::{check_edit, check_edit_with_config, TEST_CONFIG},
218         CompletionConfig,
219     };
220
221     #[test]
222     fn inserts_parens_for_function_calls() {
223         cov_mark::check!(inserts_parens_for_function_calls);
224         check_edit(
225             "no_args",
226             r#"
227 fn no_args() {}
228 fn main() { no_$0 }
229 "#,
230             r#"
231 fn no_args() {}
232 fn main() { no_args()$0 }
233 "#,
234         );
235
236         check_edit(
237             "with_args",
238             r#"
239 fn with_args(x: i32, y: String) {}
240 fn main() { with_$0 }
241 "#,
242             r#"
243 fn with_args(x: i32, y: String) {}
244 fn main() { with_args(${1:x}, ${2:y})$0 }
245 "#,
246         );
247
248         check_edit(
249             "foo",
250             r#"
251 struct S;
252 impl S {
253     fn foo(&self) {}
254 }
255 fn bar(s: &S) { s.f$0 }
256 "#,
257             r#"
258 struct S;
259 impl S {
260     fn foo(&self) {}
261 }
262 fn bar(s: &S) { s.foo()$0 }
263 "#,
264         );
265
266         check_edit(
267             "foo",
268             r#"
269 struct S {}
270 impl S {
271     fn foo(&self, x: i32) {}
272 }
273 fn bar(s: &S) {
274     s.f$0
275 }
276 "#,
277             r#"
278 struct S {}
279 impl S {
280     fn foo(&self, x: i32) {}
281 }
282 fn bar(s: &S) {
283     s.foo(${1:x})$0
284 }
285 "#,
286         );
287
288         check_edit(
289             "foo",
290             r#"
291 struct S {}
292 impl S {
293     fn foo(&self, x: i32) {
294         $0
295     }
296 }
297 "#,
298             r#"
299 struct S {}
300 impl S {
301     fn foo(&self, x: i32) {
302         self.foo(${1:x})$0
303     }
304 }
305 "#,
306         );
307     }
308
309     #[test]
310     fn parens_for_method_call_as_assoc_fn() {
311         cov_mark::check!(parens_for_method_call_as_assoc_fn);
312         check_edit(
313             "foo",
314             r#"
315 struct S;
316 impl S {
317     fn foo(&self) {}
318 }
319 fn main() { S::f$0 }
320 "#,
321             r#"
322 struct S;
323 impl S {
324     fn foo(&self) {}
325 }
326 fn main() { S::foo(${1:&self})$0 }
327 "#,
328         );
329     }
330
331     #[test]
332     fn suppress_arg_snippets() {
333         cov_mark::check!(suppress_arg_snippets);
334         check_edit_with_config(
335             CompletionConfig { add_call_argument_snippets: false, ..TEST_CONFIG },
336             "with_args",
337             r#"
338 fn with_args(x: i32, y: String) {}
339 fn main() { with_$0 }
340 "#,
341             r#"
342 fn with_args(x: i32, y: String) {}
343 fn main() { with_args($0) }
344 "#,
345         );
346     }
347
348     #[test]
349     fn strips_underscores_from_args() {
350         check_edit(
351             "foo",
352             r#"
353 fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {}
354 fn main() { f$0 }
355 "#,
356             r#"
357 fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {}
358 fn main() { foo(${1:foo}, ${2:bar}, ${3:ho_ge_})$0 }
359 "#,
360         );
361     }
362
363     #[test]
364     fn insert_ref_when_matching_local_in_scope() {
365         check_edit(
366             "ref_arg",
367             r#"
368 struct Foo {}
369 fn ref_arg(x: &Foo) {}
370 fn main() {
371     let x = Foo {};
372     ref_ar$0
373 }
374 "#,
375             r#"
376 struct Foo {}
377 fn ref_arg(x: &Foo) {}
378 fn main() {
379     let x = Foo {};
380     ref_arg(${1:&x})$0
381 }
382 "#,
383         );
384     }
385
386     #[test]
387     fn insert_mut_ref_when_matching_local_in_scope() {
388         check_edit(
389             "ref_arg",
390             r#"
391 struct Foo {}
392 fn ref_arg(x: &mut Foo) {}
393 fn main() {
394     let x = Foo {};
395     ref_ar$0
396 }
397 "#,
398             r#"
399 struct Foo {}
400 fn ref_arg(x: &mut Foo) {}
401 fn main() {
402     let x = Foo {};
403     ref_arg(${1:&mut x})$0
404 }
405 "#,
406         );
407     }
408
409     #[test]
410     fn insert_ref_when_matching_local_in_scope_for_method() {
411         check_edit(
412             "apply_foo",
413             r#"
414 struct Foo {}
415 struct Bar {}
416 impl Bar {
417     fn apply_foo(&self, x: &Foo) {}
418 }
419
420 fn main() {
421     let x = Foo {};
422     let y = Bar {};
423     y.$0
424 }
425 "#,
426             r#"
427 struct Foo {}
428 struct Bar {}
429 impl Bar {
430     fn apply_foo(&self, x: &Foo) {}
431 }
432
433 fn main() {
434     let x = Foo {};
435     let y = Bar {};
436     y.apply_foo(${1:&x})$0
437 }
438 "#,
439         );
440     }
441
442     #[test]
443     fn trim_mut_keyword_in_func_completion() {
444         check_edit(
445             "take_mutably",
446             r#"
447 fn take_mutably(mut x: &i32) {}
448
449 fn main() {
450     take_m$0
451 }
452 "#,
453             r#"
454 fn take_mutably(mut x: &i32) {}
455
456 fn main() {
457     take_mutably(${1:x})$0
458 }
459 "#,
460         );
461     }
462 }