]> git.lizzy.rs Git - rust.git/blob - crates/ide_completion/src/completions/fn_param.rs
Merge #9179
[rust.git] / crates / ide_completion / src / completions / fn_param.rs
1 //! See `complete_fn_param`.
2
3 use rustc_hash::FxHashMap;
4 use syntax::{
5     ast::{self, ModuleItemOwner},
6     match_ast, AstNode,
7 };
8
9 use crate::{CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions};
10
11 /// Complete repeated parameters, both name and type. For example, if all
12 /// functions in a file have a `spam: &mut Spam` parameter, a completion with
13 /// `spam: &mut Spam` insert text/label and `spam` lookup string will be
14 /// suggested.
15 pub(crate) fn complete_fn_param(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
16     if !ctx.is_param {
17         return None;
18     }
19
20     let mut params = FxHashMap::default();
21
22     let me = ctx.token.ancestors().find_map(ast::Fn::cast);
23     let mut process_fn = |func: ast::Fn| {
24         if Some(&func) == me.as_ref() {
25             return;
26         }
27         func.param_list().into_iter().flat_map(|it| it.params()).for_each(|param| {
28             if let Some(pat) = param.pat() {
29                 let text = param.syntax().text().to_string();
30                 let lookup = pat.syntax().text().to_string();
31                 params.entry(text).or_insert(lookup);
32             }
33         });
34     };
35
36     for node in ctx.token.ancestors() {
37         match_ast! {
38             match node {
39                 ast::SourceFile(it) => it.items().filter_map(|item| match item {
40                     ast::Item::Fn(it) => Some(it),
41                     _ => None,
42                 }).for_each(&mut process_fn),
43                 ast::ItemList(it) => it.items().filter_map(|item| match item {
44                     ast::Item::Fn(it) => Some(it),
45                     _ => None,
46                 }).for_each(&mut process_fn),
47                 ast::AssocItemList(it) => it.assoc_items().filter_map(|item| match item {
48                     ast::AssocItem::Fn(it) => Some(it),
49                     _ => None,
50                 }).for_each(&mut process_fn),
51                 _ => continue,
52             }
53         };
54     }
55
56     let self_completion_items = ["self", "&self", "mut self", "&mut self"];
57     if ctx.impl_def.is_some() && me?.param_list()?.params().next().is_none() {
58         self_completion_items.iter().for_each(|self_item| {
59             add_new_item_to_acc(ctx, acc, self_item.to_string(), self_item.to_string())
60         });
61     }
62
63     params.into_iter().for_each(|(label, lookup)| add_new_item_to_acc(ctx, acc, label, lookup));
64
65     Some(())
66 }
67
68 fn add_new_item_to_acc(
69     ctx: &CompletionContext,
70     acc: &mut Completions,
71     label: String,
72     lookup: String,
73 ) {
74     let mut item = CompletionItem::new(CompletionKind::Magic, ctx.source_range(), label);
75     item.kind(CompletionItemKind::Binding).lookup_by(lookup);
76     item.add_to(acc)
77 }
78
79 #[cfg(test)]
80 mod tests {
81     use expect_test::{expect, Expect};
82
83     use crate::{tests::filtered_completion_list, CompletionKind};
84
85     fn check(ra_fixture: &str, expect: Expect) {
86         let actual = filtered_completion_list(ra_fixture, CompletionKind::Magic);
87         expect.assert_eq(&actual);
88     }
89
90     #[test]
91     fn test_param_completion_last_param() {
92         check(
93             r#"
94 fn foo(file_id: FileId) {}
95 fn bar(file_id: FileId) {}
96 fn baz(file$0) {}
97 "#,
98             expect![[r#"
99                 bn file_id: FileId
100             "#]],
101         );
102     }
103
104     #[test]
105     fn test_param_completion_nth_param() {
106         check(
107             r#"
108 fn foo(file_id: FileId) {}
109 fn baz(file$0, x: i32) {}
110 "#,
111             expect![[r#"
112                 bn file_id: FileId
113             "#]],
114         );
115     }
116
117     #[test]
118     fn test_param_completion_trait_param() {
119         check(
120             r#"
121 pub(crate) trait SourceRoot {
122     pub fn contains(&self, file_id: FileId) -> bool;
123     pub fn module_map(&self) -> &ModuleMap;
124     pub fn lines(&self, file_id: FileId) -> &LineIndex;
125     pub fn syntax(&self, file$0)
126 }
127 "#,
128             expect![[r#"
129                 bn file_id: FileId
130             "#]],
131         );
132     }
133
134     #[test]
135     fn completes_param_in_inner_function() {
136         check(
137             r#"
138 fn outer(text: String) {
139     fn inner($0)
140 }
141 "#,
142             expect![[r#"
143                 bn text: String
144             "#]],
145         )
146     }
147
148     #[test]
149     fn completes_non_ident_pat_param() {
150         check(
151             r#"
152 struct Bar { bar: u32 }
153
154 fn foo(Bar { bar }: Bar) {}
155 fn foo2($0) {}
156 "#,
157             expect![[r#"
158                 bn Bar { bar }: Bar
159             "#]],
160         )
161     }
162
163     #[test]
164     fn test_param_completion_self_param() {
165         check(
166             r#"
167                 struct A {}
168
169                 impl A {
170                     fn foo(file_id: FileId) {}
171                     fn new($0) {
172                     }
173                 }
174             "#,
175             expect![[r#"
176                 bn self
177                 bn &self
178                 bn mut self
179                 bn &mut self
180                 bn file_id: FileId
181             "#]],
182         )
183     }
184 }