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