]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/add_turbo_fish.rs
Merge #7824
[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 use test_utils::mark;
4
5 use crate::{
6     assist_context::{AssistContext, Assists},
7     AssistId, AssistKind,
8 };
9
10 // Assist: add_turbo_fish
11 //
12 // Adds `::<_>` to a call of a generic method or function.
13 //
14 // ```
15 // fn make<T>() -> T { todo!() }
16 // fn main() {
17 //     let x = make$0();
18 // }
19 // ```
20 // ->
21 // ```
22 // fn make<T>() -> T { todo!() }
23 // fn main() {
24 //     let x = make::<${0:_}>();
25 // }
26 // ```
27 pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
28     let ident = ctx.find_token_syntax_at_offset(SyntaxKind::IDENT).or_else(|| {
29         let arg_list = ctx.find_node_at_offset::<ast::ArgList>()?;
30         if arg_list.args().count() > 0 {
31             return None;
32         }
33         mark::hit!(add_turbo_fish_after_call);
34         mark::hit!(add_type_ascription_after_call);
35         arg_list.l_paren_token()?.prev_token().filter(|it| it.kind() == SyntaxKind::IDENT)
36     })?;
37     let next_token = ident.next_token()?;
38     if next_token.kind() == T![::] {
39         mark::hit!(add_turbo_fish_one_fish_is_enough);
40         return None;
41     }
42     let name_ref = ast::NameRef::cast(ident.parent())?;
43     let def = match NameRefClass::classify(&ctx.sema, &name_ref)? {
44         NameRefClass::Definition(def) => def,
45         NameRefClass::ExternCrate(_) | NameRefClass::FieldShorthand { .. } => return None,
46     };
47     let fun = match def {
48         Definition::ModuleDef(hir::ModuleDef::Function(it)) => it,
49         _ => return None,
50     };
51     let generics = hir::GenericDef::Function(fun).params(ctx.sema.db);
52     if generics.is_empty() {
53         mark::hit!(add_turbo_fish_non_generic);
54         return None;
55     }
56
57     if let Some(let_stmt) = ctx.find_node_at_offset::<ast::LetStmt>() {
58         if let_stmt.colon_token().is_none() {
59             let type_pos = let_stmt.pat()?.syntax().last_token()?.text_range().end();
60             acc.add(
61                 AssistId("add_type_ascription", AssistKind::RefactorRewrite),
62                 "Add `: _` before assignment operator",
63                 ident.text_range(),
64                 |builder| match ctx.config.snippet_cap {
65                     Some(cap) => builder.insert_snippet(cap, type_pos, ": ${0:_}"),
66                     None => builder.insert(type_pos, ": _"),
67                 },
68             )?
69         } else {
70             mark::hit!(add_type_ascription_already_typed);
71         }
72     }
73
74     acc.add(
75         AssistId("add_turbo_fish", AssistKind::RefactorRewrite),
76         "Add `::<>`",
77         ident.text_range(),
78         |builder| match ctx.config.snippet_cap {
79             Some(cap) => builder.insert_snippet(cap, ident.text_range().end(), "::<${0:_}>"),
80             None => builder.insert(ident.text_range().end(), "::<_>"),
81         },
82     )
83 }
84
85 #[cfg(test)]
86 mod tests {
87     use crate::tests::{check_assist, check_assist_by_label, check_assist_not_applicable};
88
89     use super::*;
90     use test_utils::mark;
91
92     #[test]
93     fn add_turbo_fish_function() {
94         check_assist(
95             add_turbo_fish,
96             r#"
97 fn make<T>() -> T {}
98 fn main() {
99     make$0();
100 }
101 "#,
102             r#"
103 fn make<T>() -> T {}
104 fn main() {
105     make::<${0:_}>();
106 }
107 "#,
108         );
109     }
110
111     #[test]
112     fn add_turbo_fish_after_call() {
113         mark::check!(add_turbo_fish_after_call);
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_method() {
133         check_assist(
134             add_turbo_fish,
135             r#"
136 struct S;
137 impl S {
138     fn make<T>(&self) -> T {}
139 }
140 fn main() {
141     S.make$0();
142 }
143 "#,
144             r#"
145 struct S;
146 impl S {
147     fn make<T>(&self) -> T {}
148 }
149 fn main() {
150     S.make::<${0:_}>();
151 }
152 "#,
153         );
154     }
155
156     #[test]
157     fn add_turbo_fish_one_fish_is_enough() {
158         mark::check!(add_turbo_fish_one_fish_is_enough);
159         check_assist_not_applicable(
160             add_turbo_fish,
161             r#"
162 fn make<T>() -> T {}
163 fn main() {
164     make$0::<()>();
165 }
166 "#,
167         );
168     }
169
170     #[test]
171     fn add_turbo_fish_non_generic() {
172         mark::check!(add_turbo_fish_non_generic);
173         check_assist_not_applicable(
174             add_turbo_fish,
175             r#"
176 fn make() -> () {}
177 fn main() {
178     make$0();
179 }
180 "#,
181         );
182     }
183
184     #[test]
185     fn add_type_ascription_function() {
186         check_assist_by_label(
187             add_turbo_fish,
188             r#"
189 fn make<T>() -> T {}
190 fn main() {
191     let x = make$0();
192 }
193 "#,
194             r#"
195 fn make<T>() -> T {}
196 fn main() {
197     let x: ${0:_} = make();
198 }
199 "#,
200             "Add `: _` before assignment operator",
201         );
202     }
203
204     #[test]
205     fn add_type_ascription_after_call() {
206         mark::check!(add_type_ascription_after_call);
207         check_assist_by_label(
208             add_turbo_fish,
209             r#"
210 fn make<T>() -> T {}
211 fn main() {
212     let x = make()$0;
213 }
214 "#,
215             r#"
216 fn make<T>() -> T {}
217 fn main() {
218     let x: ${0:_} = make();
219 }
220 "#,
221             "Add `: _` before assignment operator",
222         );
223     }
224
225     #[test]
226     fn add_type_ascription_method() {
227         check_assist_by_label(
228             add_turbo_fish,
229             r#"
230 struct S;
231 impl S {
232     fn make<T>(&self) -> T {}
233 }
234 fn main() {
235     let x = S.make$0();
236 }
237 "#,
238             r#"
239 struct S;
240 impl S {
241     fn make<T>(&self) -> T {}
242 }
243 fn main() {
244     let x: ${0:_} = S.make();
245 }
246 "#,
247             "Add `: _` before assignment operator",
248         );
249     }
250
251     #[test]
252     fn add_type_ascription_already_typed() {
253         mark::check!(add_type_ascription_already_typed);
254         check_assist(
255             add_turbo_fish,
256             r#"
257 fn make<T>() -> T {}
258 fn main() {
259     let x: () = make$0();
260 }
261 "#,
262             r#"
263 fn make<T>() -> T {}
264 fn main() {
265     let x: () = make::<${0:_}>();
266 }
267 "#,
268         );
269     }
270 }