]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/add_turbo_fish.rs
e493e4e192e9dafc29c02205e66a519d21f53c63
[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| matches!(param, hir::GenericParam::TypeParam(_) | hir::GenericParam::ConstParam(_)))
83         .count();
84     let fish_head = std::iter::repeat("_").take(number_of_arguments).collect::<Vec<_>>().join(",");
85
86     acc.add(
87         AssistId("add_turbo_fish", AssistKind::RefactorRewrite),
88         "Add `::<>`",
89         ident.text_range(),
90         |builder| match ctx.config.snippet_cap {
91             Some(cap) => {
92                 let snip = format!("::<${{0:{}}}>", fish_head);
93                 builder.insert_snippet(cap, ident.text_range().end(), snip)
94             }
95             None => {
96                 let snip = format!("::<{}>", fish_head);
97                 builder.insert(ident.text_range().end(), snip);
98             }
99         },
100     )
101 }
102
103 #[cfg(test)]
104 mod tests {
105     use crate::tests::{check_assist, check_assist_by_label, check_assist_not_applicable};
106
107     use super::*;
108
109     #[test]
110     fn add_turbo_fish_function() {
111         check_assist(
112             add_turbo_fish,
113             r#"
114 fn make<T>() -> T {}
115 fn main() {
116     make$0();
117 }
118 "#,
119             r#"
120 fn make<T>() -> T {}
121 fn main() {
122     make::<${0:_}>();
123 }
124 "#,
125         );
126     }
127
128     #[test]
129     fn add_turbo_fish_function_multiple_generic_types() {
130         check_assist(
131             add_turbo_fish,
132             r#"
133 fn make<T, A>() -> T {}
134 fn main() {
135     make$0();
136 }
137 "#,
138             r#"
139 fn make<T, A>() -> T {}
140 fn main() {
141     make::<${0:_,_}>();
142 }
143 "#,
144         );
145     }
146
147     #[test]
148     fn add_turbo_fish_function_many_generic_types() {
149         check_assist(
150             add_turbo_fish,
151             r#"
152 fn make<T, A, B, C, D, E, F>() -> T {}
153 fn main() {
154     make$0();
155 }
156 "#,
157             r#"
158 fn make<T, A, B, C, D, E, F>() -> T {}
159 fn main() {
160     make::<${0:_,_,_,_,_,_,_}>();
161 }
162 "#,
163         );
164     }
165
166     #[test]
167     fn add_turbo_fish_after_call() {
168         cov_mark::check!(add_turbo_fish_after_call);
169         check_assist(
170             add_turbo_fish,
171             r#"
172 fn make<T>() -> T {}
173 fn main() {
174     make()$0;
175 }
176 "#,
177             r#"
178 fn make<T>() -> T {}
179 fn main() {
180     make::<${0:_}>();
181 }
182 "#,
183         );
184     }
185
186     #[test]
187     fn add_turbo_fish_method() {
188         check_assist(
189             add_turbo_fish,
190             r#"
191 struct S;
192 impl S {
193     fn make<T>(&self) -> T {}
194 }
195 fn main() {
196     S.make$0();
197 }
198 "#,
199             r#"
200 struct S;
201 impl S {
202     fn make<T>(&self) -> T {}
203 }
204 fn main() {
205     S.make::<${0:_}>();
206 }
207 "#,
208         );
209     }
210
211     #[test]
212     fn add_turbo_fish_one_fish_is_enough() {
213         cov_mark::check!(add_turbo_fish_one_fish_is_enough);
214         check_assist_not_applicable(
215             add_turbo_fish,
216             r#"
217 fn make<T>() -> T {}
218 fn main() {
219     make$0::<()>();
220 }
221 "#,
222         );
223     }
224
225     #[test]
226     fn add_turbo_fish_non_generic() {
227         cov_mark::check!(add_turbo_fish_non_generic);
228         check_assist_not_applicable(
229             add_turbo_fish,
230             r#"
231 fn make() -> () {}
232 fn main() {
233     make$0();
234 }
235 "#,
236         );
237     }
238
239     #[test]
240     fn add_type_ascription_function() {
241         check_assist_by_label(
242             add_turbo_fish,
243             r#"
244 fn make<T>() -> T {}
245 fn main() {
246     let x = make$0();
247 }
248 "#,
249             r#"
250 fn make<T>() -> T {}
251 fn main() {
252     let x: ${0:_} = make();
253 }
254 "#,
255             "Add `: _` before assignment operator",
256         );
257     }
258
259     #[test]
260     fn add_type_ascription_after_call() {
261         cov_mark::check!(add_type_ascription_after_call);
262         check_assist_by_label(
263             add_turbo_fish,
264             r#"
265 fn make<T>() -> T {}
266 fn main() {
267     let x = make()$0;
268 }
269 "#,
270             r#"
271 fn make<T>() -> T {}
272 fn main() {
273     let x: ${0:_} = make();
274 }
275 "#,
276             "Add `: _` before assignment operator",
277         );
278     }
279
280     #[test]
281     fn add_type_ascription_method() {
282         check_assist_by_label(
283             add_turbo_fish,
284             r#"
285 struct S;
286 impl S {
287     fn make<T>(&self) -> T {}
288 }
289 fn main() {
290     let x = S.make$0();
291 }
292 "#,
293             r#"
294 struct S;
295 impl S {
296     fn make<T>(&self) -> T {}
297 }
298 fn main() {
299     let x: ${0:_} = S.make();
300 }
301 "#,
302             "Add `: _` before assignment operator",
303         );
304     }
305
306     #[test]
307     fn add_type_ascription_already_typed() {
308         cov_mark::check!(add_type_ascription_already_typed);
309         check_assist(
310             add_turbo_fish,
311             r#"
312 fn make<T>() -> T {}
313 fn main() {
314     let x: () = make$0();
315 }
316 "#,
317             r#"
318 fn make<T>() -> T {}
319 fn main() {
320     let x: () = make::<${0:_}>();
321 }
322 "#,
323         );
324     }
325
326     #[test]
327     fn add_type_ascription_append_semicolon() {
328         check_assist_by_label(
329             add_turbo_fish,
330             r#"
331 fn make<T>() -> T {}
332 fn main() {
333     let x = make$0()
334 }
335 "#,
336             r#"
337 fn make<T>() -> T {}
338 fn main() {
339     let x: ${0:_} = make();
340 }
341 "#,
342             "Add `: _` before assignment operator",
343         );
344     }
345
346     #[test]
347     fn add_turbo_fish_function_lifetime_parameter() {
348         check_assist(
349             add_turbo_fish,
350             r#"
351 fn make<'a, T, A>(t: T, a: A) {}
352 fn main() {
353     make$0(5, 2);
354 }
355 "#,
356             r#"
357 fn make<'a, T, A>(t: T, a: A) {}
358 fn main() {
359     make::<${0:_,_}>(5, 2);
360 }
361 "#,
362         );
363     }
364
365     #[test]
366     fn add_turbo_fish_function_const_parameter() {
367         check_assist(
368             add_turbo_fish,
369             r#"
370 fn make<T, const N: usize>(t: T) {}
371 fn main() {
372     make$0(3);
373 }
374 "#,
375             r#"
376 fn make<T, const N: usize>(t: T) {}
377 fn main() {
378     make::<${0:_,_}>(3);
379 }
380 "#,
381         );
382     }
383 }