]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/add_turbo_fish.rs
Merge #7956
[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().count() > 0 {
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::ExternCrate(_) | NameRefClass::FieldShorthand { .. } => return None,
45     };
46     let fun = match def {
47         Definition::ModuleDef(hir::ModuleDef::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     acc.add(
81         AssistId("add_turbo_fish", AssistKind::RefactorRewrite),
82         "Add `::<>`",
83         ident.text_range(),
84         |builder| match ctx.config.snippet_cap {
85             Some(cap) => builder.insert_snippet(cap, ident.text_range().end(), "::<${0:_}>"),
86             None => builder.insert(ident.text_range().end(), "::<_>"),
87         },
88     )
89 }
90
91 #[cfg(test)]
92 mod tests {
93     use crate::tests::{check_assist, check_assist_by_label, check_assist_not_applicable};
94
95     use super::*;
96
97     #[test]
98     fn add_turbo_fish_function() {
99         check_assist(
100             add_turbo_fish,
101             r#"
102 fn make<T>() -> T {}
103 fn main() {
104     make$0();
105 }
106 "#,
107             r#"
108 fn make<T>() -> T {}
109 fn main() {
110     make::<${0:_}>();
111 }
112 "#,
113         );
114     }
115
116     #[test]
117     fn add_turbo_fish_after_call() {
118         cov_mark::check!(add_turbo_fish_after_call);
119         check_assist(
120             add_turbo_fish,
121             r#"
122 fn make<T>() -> T {}
123 fn main() {
124     make()$0;
125 }
126 "#,
127             r#"
128 fn make<T>() -> T {}
129 fn main() {
130     make::<${0:_}>();
131 }
132 "#,
133         );
134     }
135
136     #[test]
137     fn add_turbo_fish_method() {
138         check_assist(
139             add_turbo_fish,
140             r#"
141 struct S;
142 impl S {
143     fn make<T>(&self) -> T {}
144 }
145 fn main() {
146     S.make$0();
147 }
148 "#,
149             r#"
150 struct S;
151 impl S {
152     fn make<T>(&self) -> T {}
153 }
154 fn main() {
155     S.make::<${0:_}>();
156 }
157 "#,
158         );
159     }
160
161     #[test]
162     fn add_turbo_fish_one_fish_is_enough() {
163         cov_mark::check!(add_turbo_fish_one_fish_is_enough);
164         check_assist_not_applicable(
165             add_turbo_fish,
166             r#"
167 fn make<T>() -> T {}
168 fn main() {
169     make$0::<()>();
170 }
171 "#,
172         );
173     }
174
175     #[test]
176     fn add_turbo_fish_non_generic() {
177         cov_mark::check!(add_turbo_fish_non_generic);
178         check_assist_not_applicable(
179             add_turbo_fish,
180             r#"
181 fn make() -> () {}
182 fn main() {
183     make$0();
184 }
185 "#,
186         );
187     }
188
189     #[test]
190     fn add_type_ascription_function() {
191         check_assist_by_label(
192             add_turbo_fish,
193             r#"
194 fn make<T>() -> T {}
195 fn main() {
196     let x = make$0();
197 }
198 "#,
199             r#"
200 fn make<T>() -> T {}
201 fn main() {
202     let x: ${0:_} = make();
203 }
204 "#,
205             "Add `: _` before assignment operator",
206         );
207     }
208
209     #[test]
210     fn add_type_ascription_after_call() {
211         cov_mark::check!(add_type_ascription_after_call);
212         check_assist_by_label(
213             add_turbo_fish,
214             r#"
215 fn make<T>() -> T {}
216 fn main() {
217     let x = make()$0;
218 }
219 "#,
220             r#"
221 fn make<T>() -> T {}
222 fn main() {
223     let x: ${0:_} = make();
224 }
225 "#,
226             "Add `: _` before assignment operator",
227         );
228     }
229
230     #[test]
231     fn add_type_ascription_method() {
232         check_assist_by_label(
233             add_turbo_fish,
234             r#"
235 struct S;
236 impl S {
237     fn make<T>(&self) -> T {}
238 }
239 fn main() {
240     let x = S.make$0();
241 }
242 "#,
243             r#"
244 struct S;
245 impl S {
246     fn make<T>(&self) -> T {}
247 }
248 fn main() {
249     let x: ${0:_} = S.make();
250 }
251 "#,
252             "Add `: _` before assignment operator",
253         );
254     }
255
256     #[test]
257     fn add_type_ascription_already_typed() {
258         cov_mark::check!(add_type_ascription_already_typed);
259         check_assist(
260             add_turbo_fish,
261             r#"
262 fn make<T>() -> T {}
263 fn main() {
264     let x: () = make$0();
265 }
266 "#,
267             r#"
268 fn make<T>() -> T {}
269 fn main() {
270     let x: () = make::<${0:_}>();
271 }
272 "#,
273         );
274     }
275
276     #[test]
277     fn add_type_ascription_append_semicolon() {
278         check_assist_by_label(
279             add_turbo_fish,
280             r#"
281 fn make<T>() -> T {}
282 fn main() {
283     let x = make$0()
284 }
285 "#,
286             r#"
287 fn make<T>() -> T {}
288 fn main() {
289     let x: ${0:_} = make();
290 }
291 "#,
292             "Add `: _` before assignment operator",
293         );
294     }
295 }