]> git.lizzy.rs Git - rust.git/blob - crates/ide_completion/src/completions/fn_param.rs
feat: improve parser error recovery for function parameters
[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_first_param() {
106         check(
107             r#"
108 fn foo(file_id: FileId) {}
109 fn bar(file_id: FileId) {}
110 fn baz(file$0 id: u32) {}
111 "#,
112             expect![[r#"
113                 bn file_id: FileId
114             "#]],
115         );
116     }
117
118     #[test]
119     fn test_param_completion_nth_param() {
120         check(
121             r#"
122 fn foo(file_id: FileId) {}
123 fn baz(file$0, x: i32) {}
124 "#,
125             expect![[r#"
126                 bn file_id: FileId
127             "#]],
128         );
129     }
130
131     #[test]
132     fn test_param_completion_trait_param() {
133         check(
134             r#"
135 pub(crate) trait SourceRoot {
136     pub fn contains(&self, file_id: FileId) -> bool;
137     pub fn module_map(&self) -> &ModuleMap;
138     pub fn lines(&self, file_id: FileId) -> &LineIndex;
139     pub fn syntax(&self, file$0)
140 }
141 "#,
142             expect![[r#"
143                 bn file_id: FileId
144             "#]],
145         );
146     }
147
148     #[test]
149     fn completes_param_in_inner_function() {
150         check(
151             r#"
152 fn outer(text: String) {
153     fn inner($0)
154 }
155 "#,
156             expect![[r#"
157                 bn text: String
158             "#]],
159         )
160     }
161
162     #[test]
163     fn completes_non_ident_pat_param() {
164         check(
165             r#"
166 struct Bar { bar: u32 }
167
168 fn foo(Bar { bar }: Bar) {}
169 fn foo2($0) {}
170 "#,
171             expect![[r#"
172                 bn Bar { bar }: Bar
173             "#]],
174         )
175     }
176
177     #[test]
178     fn test_param_completion_self_param() {
179         check(
180             r#"
181                 struct A {}
182
183                 impl A {
184                     fn foo(file_id: FileId) {}
185                     fn new($0) {
186                     }
187                 }
188             "#,
189             expect![[r#"
190                 bn self
191                 bn &self
192                 bn mut self
193                 bn &mut self
194                 bn file_id: FileId
195             "#]],
196         )
197     }
198 }