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