Skip to main content

ide_assists/handlers/
add_turbo_fish.rs

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
14// Assist: add_turbo_fish
15//
16// Adds `::<_>` to a call of a generic method or function.
17//
18// ```
19// fn make<T>() -> T { todo!() }
20// fn main() {
21//     let x = make$0();
22// }
23// ```
24// ->
25// ```
26// fn make<T>() -> T { todo!() }
27// fn main() {
28//     let x = make::<${0:_}>();
29// }
30// ```
31pub(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
186/// This will create a turbofish generic arg list corresponding to the number of arguments
187fn 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}