]> git.lizzy.rs Git - rust.git/blob - crates/ide_completion/src/render/function.rs
Merge #10440
[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 = match &self.receiver {
78             Some(receiver) => format!("{}.{}", receiver, &self.name),
79             None => self.name.clone(),
80         };
81         let mut item =
82             CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), call.clone());
83         item.kind(self.kind())
84             .set_documentation(self.ctx.docs(self.func))
85             .set_deprecated(
86                 self.ctx.is_deprecated(self.func) || self.ctx.is_deprecated_assoc_item(self.func),
87             )
88             .detail(self.detail())
89             .add_call_parens(self.ctx.completion, call.clone(), params);
90
91         if import_to_add.is_none() {
92             let db = self.ctx.db();
93             if let Some(actm) = self.func.as_assoc_item(db) {
94                 if let Some(trt) = actm.containing_trait_or_trait_impl(db) {
95                     item.trait_name(trt.name(db).to_string());
96                 }
97             }
98         }
99
100         item.add_import(import_to_add).lookup_by(self.name);
101
102         let ret_type = self.func.ret_type(self.ctx.db());
103         item.set_relevance(CompletionRelevance {
104             type_match: compute_type_match(self.ctx.completion, &ret_type),
105             exact_name_match: compute_exact_name_match(self.ctx.completion, &call),
106             ..CompletionRelevance::default()
107         });
108
109         if let Some(ref_match) = compute_ref_match(self.ctx.completion, &ret_type) {
110             // FIXME
111             // For now we don't properly calculate the edits for ref match
112             // completions on methods, so we've disabled them. See #8058.
113             if !self.is_method {
114                 item.ref_match(ref_match);
115             }
116         }
117
118         item.build()
119     }
120
121     fn detail(&self) -> String {
122         let ret_ty = self.func.ret_type(self.ctx.db());
123         let ret = if ret_ty.is_unit() {
124             // Omit the return type if it is the unit type
125             String::new()
126         } else {
127             format!(" {}", self.ty_display())
128         };
129
130         format!("fn({}){}", self.params_display(), ret)
131     }
132
133     fn params_display(&self) -> String {
134         if let Some(self_param) = self.func.self_param(self.ctx.db()) {
135             let params = self
136                 .func
137                 .assoc_fn_params(self.ctx.db())
138                 .into_iter()
139                 .skip(1) // skip the self param because we are manually handling that
140                 .map(|p| p.ty().display(self.ctx.db()).to_string());
141
142             std::iter::once(self_param.display(self.ctx.db()).to_owned()).chain(params).join(", ")
143         } else {
144             let params = self
145                 .func
146                 .assoc_fn_params(self.ctx.db())
147                 .into_iter()
148                 .map(|p| p.ty().display(self.ctx.db()).to_string())
149                 .join(", ");
150             params
151         }
152     }
153
154     fn ty_display(&self) -> String {
155         let ret_ty = self.func.ret_type(self.ctx.db());
156
157         format!("-> {}", ret_ty.display(self.ctx.db()))
158     }
159
160     fn add_arg(&self, arg: &str, ty: &hir::Type) -> String {
161         if let Some(derefed_ty) = ty.remove_ref() {
162             for (name, local) in self.ctx.completion.locals.iter() {
163                 if name == arg && local.ty(self.ctx.db()) == derefed_ty {
164                     let mutability = if ty.is_mutable_reference() { "&mut " } else { "&" };
165                     return format!("{}{}", mutability, arg);
166                 }
167             }
168         }
169         arg.to_string()
170     }
171
172     fn params(&self) -> Params {
173         let ast_params = match self.ast_node.param_list() {
174             Some(it) => it,
175             None => return Params::Named(Vec::new()),
176         };
177
178         let mut params_pats = Vec::new();
179         let params_ty = if self.ctx.completion.has_dot_receiver() || self.receiver.is_some() {
180             self.func.method_params(self.ctx.db()).unwrap_or_default()
181         } else {
182             if let Some(s) = ast_params.self_param() {
183                 cov_mark::hit!(parens_for_method_call_as_assoc_fn);
184                 params_pats.push(Some(s.to_string()));
185             }
186             self.func.assoc_fn_params(self.ctx.db())
187         };
188         params_pats
189             .extend(ast_params.params().into_iter().map(|it| it.pat().map(|it| it.to_string())));
190
191         let params = params_pats
192             .into_iter()
193             .zip(params_ty)
194             .flat_map(|(pat, param_ty)| {
195                 let pat = pat?;
196                 let name = pat;
197                 let arg = name.trim_start_matches("mut ").trim_start_matches('_');
198                 Some(self.add_arg(arg, param_ty.ty()))
199             })
200             .collect();
201         Params::Named(params)
202     }
203
204     fn kind(&self) -> CompletionItemKind {
205         if self.func.self_param(self.ctx.db()).is_some() {
206             CompletionItemKind::Method
207         } else {
208             SymbolKind::Function.into()
209         }
210     }
211 }
212
213 #[cfg(test)]
214 mod tests {
215     use crate::{
216         tests::{check_edit, check_edit_with_config, TEST_CONFIG},
217         CompletionConfig,
218     };
219
220     #[test]
221     fn inserts_parens_for_function_calls() {
222         cov_mark::check!(inserts_parens_for_function_calls);
223         check_edit(
224             "no_args",
225             r#"
226 fn no_args() {}
227 fn main() { no_$0 }
228 "#,
229             r#"
230 fn no_args() {}
231 fn main() { no_args()$0 }
232 "#,
233         );
234
235         check_edit(
236             "with_args",
237             r#"
238 fn with_args(x: i32, y: String) {}
239 fn main() { with_$0 }
240 "#,
241             r#"
242 fn with_args(x: i32, y: String) {}
243 fn main() { with_args(${1:x}, ${2:y})$0 }
244 "#,
245         );
246
247         check_edit(
248             "foo",
249             r#"
250 struct S;
251 impl S {
252     fn foo(&self) {}
253 }
254 fn bar(s: &S) { s.f$0 }
255 "#,
256             r#"
257 struct S;
258 impl S {
259     fn foo(&self) {}
260 }
261 fn bar(s: &S) { s.foo()$0 }
262 "#,
263         );
264
265         check_edit(
266             "foo",
267             r#"
268 struct S {}
269 impl S {
270     fn foo(&self, x: i32) {}
271 }
272 fn bar(s: &S) {
273     s.f$0
274 }
275 "#,
276             r#"
277 struct S {}
278 impl S {
279     fn foo(&self, x: i32) {}
280 }
281 fn bar(s: &S) {
282     s.foo(${1:x})$0
283 }
284 "#,
285         );
286
287         check_edit(
288             "foo",
289             r#"
290 struct S {}
291 impl S {
292     fn foo(&self, x: i32) {
293         $0
294     }
295 }
296 "#,
297             r#"
298 struct S {}
299 impl S {
300     fn foo(&self, x: i32) {
301         self.foo(${1:x})$0
302     }
303 }
304 "#,
305         );
306     }
307
308     #[test]
309     fn parens_for_method_call_as_assoc_fn() {
310         cov_mark::check!(parens_for_method_call_as_assoc_fn);
311         check_edit(
312             "foo",
313             r#"
314 struct S;
315 impl S {
316     fn foo(&self) {}
317 }
318 fn main() { S::f$0 }
319 "#,
320             r#"
321 struct S;
322 impl S {
323     fn foo(&self) {}
324 }
325 fn main() { S::foo(${1:&self})$0 }
326 "#,
327         );
328     }
329
330     #[test]
331     fn suppress_arg_snippets() {
332         cov_mark::check!(suppress_arg_snippets);
333         check_edit_with_config(
334             CompletionConfig { add_call_argument_snippets: false, ..TEST_CONFIG },
335             "with_args",
336             r#"
337 fn with_args(x: i32, y: String) {}
338 fn main() { with_$0 }
339 "#,
340             r#"
341 fn with_args(x: i32, y: String) {}
342 fn main() { with_args($0) }
343 "#,
344         );
345     }
346
347     #[test]
348     fn strips_underscores_from_args() {
349         check_edit(
350             "foo",
351             r#"
352 fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {}
353 fn main() { f$0 }
354 "#,
355             r#"
356 fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {}
357 fn main() { foo(${1:foo}, ${2:bar}, ${3:ho_ge_})$0 }
358 "#,
359         );
360     }
361
362     #[test]
363     fn insert_ref_when_matching_local_in_scope() {
364         check_edit(
365             "ref_arg",
366             r#"
367 struct Foo {}
368 fn ref_arg(x: &Foo) {}
369 fn main() {
370     let x = Foo {};
371     ref_ar$0
372 }
373 "#,
374             r#"
375 struct Foo {}
376 fn ref_arg(x: &Foo) {}
377 fn main() {
378     let x = Foo {};
379     ref_arg(${1:&x})$0
380 }
381 "#,
382         );
383     }
384
385     #[test]
386     fn insert_mut_ref_when_matching_local_in_scope() {
387         check_edit(
388             "ref_arg",
389             r#"
390 struct Foo {}
391 fn ref_arg(x: &mut Foo) {}
392 fn main() {
393     let x = Foo {};
394     ref_ar$0
395 }
396 "#,
397             r#"
398 struct Foo {}
399 fn ref_arg(x: &mut Foo) {}
400 fn main() {
401     let x = Foo {};
402     ref_arg(${1:&mut x})$0
403 }
404 "#,
405         );
406     }
407
408     #[test]
409     fn insert_ref_when_matching_local_in_scope_for_method() {
410         check_edit(
411             "apply_foo",
412             r#"
413 struct Foo {}
414 struct Bar {}
415 impl Bar {
416     fn apply_foo(&self, x: &Foo) {}
417 }
418
419 fn main() {
420     let x = Foo {};
421     let y = Bar {};
422     y.$0
423 }
424 "#,
425             r#"
426 struct Foo {}
427 struct Bar {}
428 impl Bar {
429     fn apply_foo(&self, x: &Foo) {}
430 }
431
432 fn main() {
433     let x = Foo {};
434     let y = Bar {};
435     y.apply_foo(${1:&x})$0
436 }
437 "#,
438         );
439     }
440
441     #[test]
442     fn trim_mut_keyword_in_func_completion() {
443         check_edit(
444             "take_mutably",
445             r#"
446 fn take_mutably(mut x: &i32) {}
447
448 fn main() {
449     take_m$0
450 }
451 "#,
452             r#"
453 fn take_mutably(mut x: &i32) {}
454
455 fn main() {
456     take_mutably(${1:x})$0
457 }
458 "#,
459         );
460     }
461 }