1use either::Either;
2use ide_db::defs::{Definition, NameRefClass};
3use syntax::{
4 AstNode,
5 ast::{self, HasArgList, HasGenericArgs, syntax_factory::SyntaxFactory},
6 syntax_editor::Position,
7};
8
9use crate::{
10 AssistId,
11 assist_context::{AssistContext, Assists},
12};
13
14pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext<'_, '_>) -> Option<()> {
32 let turbofish_target =
33 ctx.find_node_at_offset::<ast::PathSegment>().map(Either::Left).or_else(|| {
34 let callable_expr = ctx.find_node_at_offset::<ast::CallableExpr>()?;
35
36 if callable_expr.arg_list()?.args().next().is_some() {
37 return None;
38 }
39
40 cov_mark::hit!(add_turbo_fish_after_call);
41 cov_mark::hit!(add_type_ascription_after_call);
42
43 match callable_expr {
44 ast::CallableExpr::Call(it) => {
45 let ast::Expr::PathExpr(path) = it.expr()? else {
46 return None;
47 };
48
49 Some(Either::Left(path.path()?.segment()?))
50 }
51 ast::CallableExpr::MethodCall(it) => Some(Either::Right(it)),
52 }
53 })?;
54
55 let already_has_turbofish = match &turbofish_target {
56 Either::Left(path_segment) => path_segment.generic_arg_list().is_some(),
57 Either::Right(method_call) => method_call.generic_arg_list().is_some(),
58 };
59
60 if already_has_turbofish {
61 cov_mark::hit!(add_turbo_fish_one_fish_is_enough);
62 return None;
63 }
64
65 let name_ref = match &turbofish_target {
66 Either::Left(path_segment) => path_segment.name_ref()?,
67 Either::Right(method_call) => method_call.name_ref()?,
68 };
69 let ident = name_ref.ident_token()?;
70
71 let def = match NameRefClass::classify(&ctx.sema, &name_ref)? {
72 NameRefClass::Definition(def, _) => def,
73 NameRefClass::FieldShorthand { .. } | NameRefClass::ExternCrateShorthand { .. } => {
74 return None;
75 }
76 };
77 let fun = match def {
78 Definition::Function(it) => it,
79 _ => return None,
80 };
81 let generics = hir::GenericDef::Function(fun).params(ctx.sema.db);
82 if generics.is_empty() {
83 cov_mark::hit!(add_turbo_fish_non_generic);
84 return None;
85 }
86
87 if let Some(let_stmt) = ctx.find_node_at_offset::<ast::LetStmt>() {
88 if let_stmt.colon_token().is_none() {
89 let_stmt.pat()?;
90
91 acc.add(
92 AssistId::refactor_rewrite("add_type_ascription"),
93 "Add `: _` before assignment operator",
94 ident.text_range(),
95 |builder| {
96 let editor = builder.make_editor(let_stmt.syntax());
97 let make = editor.make();
98
99 if let_stmt.semicolon_token().is_none() {
100 editor.insert(
101 Position::last_child_of(let_stmt.syntax()),
102 make.token(syntax::SyntaxKind::SEMICOLON),
103 );
104 }
105
106 let placeholder_ty = make.ty_placeholder();
107
108 if let Some(pat) = let_stmt.pat() {
109 let elements = vec![
110 make.token(syntax::SyntaxKind::COLON).into(),
111 make.whitespace(" ").into(),
112 placeholder_ty.syntax().clone().into(),
113 ];
114 editor.insert_all(Position::after(pat.syntax()), elements);
115 if let Some(cap) = ctx.config.snippet_cap {
116 editor.add_annotation(
117 placeholder_ty.syntax(),
118 builder.make_placeholder_snippet(cap),
119 );
120 }
121 }
122
123 builder.add_file_edits(ctx.vfs_file_id(), editor);
124 },
125 )?
126 } else {
127 cov_mark::hit!(add_type_ascription_already_typed);
128 }
129 }
130
131 let number_of_arguments = generics
132 .iter()
133 .filter(|param| {
134 matches!(param, hir::GenericParam::TypeParam(_) | hir::GenericParam::ConstParam(_))
135 })
136 .count();
137
138 acc.add(
139 AssistId::refactor_rewrite("add_turbo_fish"),
140 "Add `::<>`",
141 ident.text_range(),
142 |builder| {
143 builder.trigger_parameter_hints();
144 let editor = match &turbofish_target {
145 Either::Left(it) => builder.make_editor(it.syntax()),
146 Either::Right(it) => builder.make_editor(it.syntax()),
147 };
148
149 let fish_head = get_fish_head(editor.make(), number_of_arguments);
150
151 match turbofish_target {
152 Either::Left(path_segment) => {
153 if let Some(generic_arg_list) = path_segment.generic_arg_list() {
154 editor.replace(generic_arg_list.syntax(), fish_head.syntax());
155 } else {
156 editor.insert(
157 Position::last_child_of(path_segment.syntax()),
158 fish_head.syntax(),
159 );
160 }
161 }
162 Either::Right(method_call) => {
163 if let Some(generic_arg_list) = method_call.generic_arg_list() {
164 editor.replace(generic_arg_list.syntax(), fish_head.syntax());
165 } else {
166 let position = if let Some(arg_list) = method_call.arg_list() {
167 Position::before(arg_list.syntax())
168 } else {
169 Position::last_child_of(method_call.syntax())
170 };
171 editor.insert(position, fish_head.syntax());
172 }
173 }
174 };
175
176 if let Some(cap) = ctx.config.snippet_cap {
177 for arg in fish_head.generic_args() {
178 editor.add_annotation(arg.syntax(), builder.make_placeholder_snippet(cap));
179 }
180 }
181 builder.add_file_edits(ctx.vfs_file_id(), editor);
182 },
183 )
184}
185
186fn get_fish_head(make: &SyntaxFactory, number_of_arguments: usize) -> ast::GenericArgList {
188 let args = (0..number_of_arguments).map(|_| make.type_arg(make.ty_placeholder()).into());
189 make.generic_arg_list(args, true)
190}
191
192#[cfg(test)]
193mod tests {
194 use crate::tests::{
195 check_assist, check_assist_by_label, check_assist_not_applicable,
196 check_assist_not_applicable_by_label,
197 };
198
199 use super::*;
200
201 #[test]
202 fn add_turbo_fish_function() {
203 check_assist(
204 add_turbo_fish,
205 r#"
206fn make<T>() -> T {}
207fn main() {
208 make$0();
209}
210"#,
211 r#"
212fn make<T>() -> T {}
213fn main() {
214 make::<${0:_}>();
215}
216"#,
217 );
218 }
219
220 #[test]
221 fn add_turbo_fish_function_multiple_generic_types() {
222 check_assist(
223 add_turbo_fish,
224 r#"
225fn make<T, A>() -> T {}
226fn main() {
227 make$0();
228}
229"#,
230 r#"
231fn make<T, A>() -> T {}
232fn main() {
233 make::<${1:_}, ${0:_}>();
234}
235"#,
236 );
237 }
238
239 #[test]
240 fn add_turbo_fish_function_many_generic_types() {
241 check_assist(
242 add_turbo_fish,
243 r#"
244fn make<T, A, B, C, D, E, F>() -> T {}
245fn main() {
246 make$0();
247}
248"#,
249 r#"
250fn make<T, A, B, C, D, E, F>() -> T {}
251fn main() {
252 make::<${1:_}, ${2:_}, ${3:_}, ${4:_}, ${5:_}, ${6:_}, ${0:_}>();
253}
254"#,
255 );
256 }
257
258 #[test]
259 fn add_turbo_fish_after_call() {
260 cov_mark::check!(add_turbo_fish_after_call);
261 check_assist(
262 add_turbo_fish,
263 r#"
264fn make<T>() -> T {}
265fn main() {
266 make()$0;
267}
268"#,
269 r#"
270fn make<T>() -> T {}
271fn main() {
272 make::<${0:_}>();
273}
274"#,
275 );
276 }
277
278 #[test]
279 fn add_turbo_fish_method() {
280 check_assist(
281 add_turbo_fish,
282 r#"
283struct S;
284impl S {
285 fn make<T>(&self) -> T {}
286}
287fn main() {
288 S.make$0();
289}
290"#,
291 r#"
292struct S;
293impl S {
294 fn make<T>(&self) -> T {}
295}
296fn main() {
297 S.make::<${0:_}>();
298}
299"#,
300 );
301 }
302
303 #[test]
304 fn add_turbo_fish_one_fish_is_enough() {
305 cov_mark::check!(add_turbo_fish_one_fish_is_enough);
306 check_assist_not_applicable(
307 add_turbo_fish,
308 r#"
309fn make<T>() -> T {}
310fn main() {
311 make$0::<()>();
312}
313"#,
314 );
315 }
316
317 #[test]
318 fn add_turbo_fish_non_generic() {
319 cov_mark::check!(add_turbo_fish_non_generic);
320 check_assist_not_applicable(
321 add_turbo_fish,
322 r#"
323fn make() -> () {}
324fn main() {
325 make$0();
326}
327"#,
328 );
329 }
330
331 #[test]
332 fn add_type_ascription_function() {
333 check_assist_by_label(
334 add_turbo_fish,
335 r#"
336fn make<T>() -> T {}
337fn main() {
338 let x = make$0();
339}
340"#,
341 r#"
342fn make<T>() -> T {}
343fn main() {
344 let x: ${0:_} = make();
345}
346"#,
347 "Add `: _` before assignment operator",
348 );
349 }
350
351 #[test]
352 fn add_type_ascription_after_call() {
353 cov_mark::check!(add_type_ascription_after_call);
354 check_assist_by_label(
355 add_turbo_fish,
356 r#"
357fn make<T>() -> T {}
358fn main() {
359 let x = make()$0;
360}
361"#,
362 r#"
363fn make<T>() -> T {}
364fn main() {
365 let x: ${0:_} = make();
366}
367"#,
368 "Add `: _` before assignment operator",
369 );
370 }
371
372 #[test]
373 fn add_type_ascription_method() {
374 check_assist_by_label(
375 add_turbo_fish,
376 r#"
377struct S;
378impl S {
379 fn make<T>(&self) -> T {}
380}
381fn main() {
382 let x = S.make$0();
383}
384"#,
385 r#"
386struct S;
387impl S {
388 fn make<T>(&self) -> T {}
389}
390fn main() {
391 let x: ${0:_} = S.make();
392}
393"#,
394 "Add `: _` before assignment operator",
395 );
396 }
397
398 #[test]
399 fn add_type_ascription_already_typed() {
400 cov_mark::check!(add_type_ascription_already_typed);
401 check_assist(
402 add_turbo_fish,
403 r#"
404fn make<T>() -> T {}
405fn main() {
406 let x: () = make$0();
407}
408"#,
409 r#"
410fn make<T>() -> T {}
411fn main() {
412 let x: () = make::<${0:_}>();
413}
414"#,
415 );
416 }
417
418 #[test]
419 fn add_type_ascription_append_semicolon() {
420 check_assist_by_label(
421 add_turbo_fish,
422 r#"
423fn make<T>() -> T {}
424fn main() {
425 let x = make$0()
426}
427"#,
428 r#"
429fn make<T>() -> T {}
430fn main() {
431 let x: ${0:_} = make();
432}
433"#,
434 "Add `: _` before assignment operator",
435 );
436 }
437
438 #[test]
439 fn add_type_ascription_missing_pattern() {
440 check_assist_not_applicable_by_label(
441 add_turbo_fish,
442 r#"
443fn make<T>() -> T {}
444fn main() {
445 let = make$0()
446}
447"#,
448 "Add `: _` before assignment operator",
449 );
450 }
451
452 #[test]
453 fn add_turbo_fish_function_lifetime_parameter() {
454 check_assist(
455 add_turbo_fish,
456 r#"
457fn make<'a, T, A>(t: T, a: A) {}
458fn main() {
459 make$0(5, 2);
460}
461"#,
462 r#"
463fn make<'a, T, A>(t: T, a: A) {}
464fn main() {
465 make::<${1:_}, ${0:_}>(5, 2);
466}
467"#,
468 );
469 }
470
471 #[test]
472 fn add_turbo_fish_function_const_parameter() {
473 check_assist(
474 add_turbo_fish,
475 r#"
476fn make<T, const N: usize>(t: T) {}
477fn main() {
478 make$0(3);
479}
480"#,
481 r#"
482fn make<T, const N: usize>(t: T) {}
483fn main() {
484 make::<${1:_}, ${0:_}>(3);
485}
486"#,
487 );
488 }
489}