]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/add_turbo_fish.rs
f67c65d16684d884b4b2aa34b51f2d8a3b8e62f2
[rust.git] / crates / ide_assists / src / handlers / add_turbo_fish.rs
1 use ide_db::defs::{Definition, NameRefClass};
2 use syntax::{ast, AstNode, SyntaxKind, T};
3
4 use crate::{
5     assist_context::{AssistContext, Assists},
6     AssistId, AssistKind,
7 };
8
9 // Assist: add_turbo_fish
10 //
11 // Adds `::<_>` to a call of a generic method or function.
12 //
13 // ```
14 // fn make<T>() -> T { todo!() }
15 // fn main() {
16 //     let x = make$0();
17 // }
18 // ```
19 // ->
20 // ```
21 // fn make<T>() -> T { todo!() }
22 // fn main() {
23 //     let x = make::<${0:_}>();
24 // }
25 // ```
26 pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
27     let ident = ctx.find_token_syntax_at_offset(SyntaxKind::IDENT).or_else(|| {
28         let arg_list = ctx.find_node_at_offset::<ast::ArgList>()?;
29         if arg_list.args().next().is_some() {
30             return None;
31         }
32         cov_mark::hit!(add_turbo_fish_after_call);
33         cov_mark::hit!(add_type_ascription_after_call);
34         arg_list.l_paren_token()?.prev_token().filter(|it| it.kind() == SyntaxKind::IDENT)
35     })?;
36     let next_token = ident.next_token()?;
37     if next_token.kind() == T![::] {
38         cov_mark::hit!(add_turbo_fish_one_fish_is_enough);
39         return None;
40     }
41     let name_ref = ast::NameRef::cast(ident.parent()?)?;
42     let def = match NameRefClass::classify(&ctx.sema, &name_ref)? {
43         NameRefClass::Definition(def) => def,
44         NameRefClass::FieldShorthand { .. } => return None,
45     };
46     let fun = match def {
47         Definition::Function(it) => it,
48         _ => return None,
49     };
50     let generics = hir::GenericDef::Function(fun).params(ctx.sema.db);
51     if generics.is_empty() {
52         cov_mark::hit!(add_turbo_fish_non_generic);
53         return None;
54     }
55
56     if let Some(let_stmt) = ctx.find_node_at_offset::<ast::LetStmt>() {
57         if let_stmt.colon_token().is_none() {
58             let type_pos = let_stmt.pat()?.syntax().last_token()?.text_range().end();
59             let semi_pos = let_stmt.syntax().last_token()?.text_range().end();
60
61             acc.add(
62                 AssistId("add_type_ascription", AssistKind::RefactorRewrite),
63                 "Add `: _` before assignment operator",
64                 ident.text_range(),
65                 |builder| {
66                     if let_stmt.semicolon_token().is_none() {
67                         builder.insert(semi_pos, ";");
68                     }
69                     match ctx.config.snippet_cap {
70                         Some(cap) => builder.insert_snippet(cap, type_pos, ": ${0:_}"),
71                         None => builder.insert(type_pos, ": _"),
72                     }
73                 },
74             )?
75         } else {
76             cov_mark::hit!(add_type_ascription_already_typed);
77         }
78     }
79
80     let number_of_arguments = generics
81         .iter()
82         .filter(|param| {
83             matches!(param, hir::GenericParam::TypeParam(_) | hir::GenericParam::ConstParam(_))
84         })
85         .count();
86     let fish_head = std::iter::repeat("_").take(number_of_arguments).collect::<Vec<_>>().join(",");
87
88     acc.add(
89         AssistId("add_turbo_fish", AssistKind::RefactorRewrite),
90         "Add `::<>`",
91         ident.text_range(),
92         |builder| match ctx.config.snippet_cap {
93             Some(cap) => {
94                 let snip = format!("::<${{0:{}}}>", fish_head);
95                 builder.insert_snippet(cap, ident.text_range().end(), snip)
96             }
97             None => {
98                 let snip = format!("::<{}>", fish_head);
99                 builder.insert(ident.text_range().end(), snip);
100             }
101         },
102     )
103 }
104
105 #[cfg(test)]
106 mod tests {
107     use crate::tests::{check_assist, check_assist_by_label, check_assist_not_applicable};
108
109     use super::*;
110
111     #[test]
112     fn add_turbo_fish_function() {
113         check_assist(
114             add_turbo_fish,
115             r#"
116 fn make<T>() -> T {}
117 fn main() {
118     make$0();
119 }
120 "#,
121             r#"
122 fn make<T>() -> T {}
123 fn main() {
124     make::<${0:_}>();
125 }
126 "#,
127         );
128     }
129
130     #[test]
131     fn add_turbo_fish_function_multiple_generic_types() {
132         check_assist(
133             add_turbo_fish,
134             r#"
135 fn make<T, A>() -> T {}
136 fn main() {
137     make$0();
138 }
139 "#,
140             r#"
141 fn make<T, A>() -> T {}
142 fn main() {
143     make::<${0:_,_}>();
144 }
145 "#,
146         );
147     }
148
149     #[test]
150     fn add_turbo_fish_function_many_generic_types() {
151         check_assist(
152             add_turbo_fish,
153             r#"
154 fn make<T, A, B, C, D, E, F>() -> T {}
155 fn main() {
156     make$0();
157 }
158 "#,
159             r#"
160 fn make<T, A, B, C, D, E, F>() -> T {}
161 fn main() {
162     make::<${0:_,_,_,_,_,_,_}>();
163 }
164 "#,
165         );
166     }
167
168     #[test]
169     fn add_turbo_fish_after_call() {
170         cov_mark::check!(add_turbo_fish_after_call);
171         check_assist(
172             add_turbo_fish,
173             r#"
174 fn make<T>() -> T {}
175 fn main() {
176     make()$0;
177 }
178 "#,
179             r#"
180 fn make<T>() -> T {}
181 fn main() {
182     make::<${0:_}>();
183 }
184 "#,
185         );
186     }
187
188     #[test]
189     fn add_turbo_fish_method() {
190         check_assist(
191             add_turbo_fish,
192             r#"
193 struct S;
194 impl S {
195     fn make<T>(&self) -> T {}
196 }
197 fn main() {
198     S.make$0();
199 }
200 "#,
201             r#"
202 struct S;
203 impl S {
204     fn make<T>(&self) -> T {}
205 }
206 fn main() {
207     S.make::<${0:_}>();
208 }
209 "#,
210         );
211     }
212
213     #[test]
214     fn add_turbo_fish_one_fish_is_enough() {
215         cov_mark::check!(add_turbo_fish_one_fish_is_enough);
216         check_assist_not_applicable(
217             add_turbo_fish,
218             r#"
219 fn make<T>() -> T {}
220 fn main() {
221     make$0::<()>();
222 }
223 "#,
224         );
225     }
226
227     #[test]
228     fn add_turbo_fish_non_generic() {
229         cov_mark::check!(add_turbo_fish_non_generic);
230         check_assist_not_applicable(
231             add_turbo_fish,
232             r#"
233 fn make() -> () {}
234 fn main() {
235     make$0();
236 }
237 "#,
238         );
239     }
240
241     #[test]
242     fn add_type_ascription_function() {
243         check_assist_by_label(
244             add_turbo_fish,
245             r#"
246 fn make<T>() -> T {}
247 fn main() {
248     let x = make$0();
249 }
250 "#,
251             r#"
252 fn make<T>() -> T {}
253 fn main() {
254     let x: ${0:_} = make();
255 }
256 "#,
257             "Add `: _` before assignment operator",
258         );
259     }
260
261     #[test]
262     fn add_type_ascription_after_call() {
263         cov_mark::check!(add_type_ascription_after_call);
264         check_assist_by_label(
265             add_turbo_fish,
266             r#"
267 fn make<T>() -> T {}
268 fn main() {
269     let x = make()$0;
270 }
271 "#,
272             r#"
273 fn make<T>() -> T {}
274 fn main() {
275     let x: ${0:_} = make();
276 }
277 "#,
278             "Add `: _` before assignment operator",
279         );
280     }
281
282     #[test]
283     fn add_type_ascription_method() {
284         check_assist_by_label(
285             add_turbo_fish,
286             r#"
287 struct S;
288 impl S {
289     fn make<T>(&self) -> T {}
290 }
291 fn main() {
292     let x = S.make$0();
293 }
294 "#,
295             r#"
296 struct S;
297 impl S {
298     fn make<T>(&self) -> T {}
299 }
300 fn main() {
301     let x: ${0:_} = S.make();
302 }
303 "#,
304             "Add `: _` before assignment operator",
305         );
306     }
307
308     #[test]
309     fn add_type_ascription_already_typed() {
310         cov_mark::check!(add_type_ascription_already_typed);
311         check_assist(
312             add_turbo_fish,
313             r#"
314 fn make<T>() -> T {}
315 fn main() {
316     let x: () = make$0();
317 }
318 "#,
319             r#"
320 fn make<T>() -> T {}
321 fn main() {
322     let x: () = make::<${0:_}>();
323 }
324 "#,
325         );
326     }
327
328     #[test]
329     fn add_type_ascription_append_semicolon() {
330         check_assist_by_label(
331             add_turbo_fish,
332             r#"
333 fn make<T>() -> T {}
334 fn main() {
335     let x = make$0()
336 }
337 "#,
338             r#"
339 fn make<T>() -> T {}
340 fn main() {
341     let x: ${0:_} = make();
342 }
343 "#,
344             "Add `: _` before assignment operator",
345         );
346     }
347
348     #[test]
349     fn add_turbo_fish_function_lifetime_parameter() {
350         check_assist(
351             add_turbo_fish,
352             r#"
353 fn make<'a, T, A>(t: T, a: A) {}
354 fn main() {
355     make$0(5, 2);
356 }
357 "#,
358             r#"
359 fn make<'a, T, A>(t: T, a: A) {}
360 fn main() {
361     make::<${0:_,_}>(5, 2);
362 }
363 "#,
364         );
365     }
366
367     #[test]
368     fn add_turbo_fish_function_const_parameter() {
369         check_assist(
370             add_turbo_fish,
371             r#"
372 fn make<T, const N: usize>(t: T) {}
373 fn main() {
374     make$0(3);
375 }
376 "#,
377             r#"
378 fn make<T, const N: usize>(t: T) {}
379 fn main() {
380     make::<${0:_,_}>(3);
381 }
382 "#,
383         );
384     }
385 }