1+ use either:: Either ;
12use ide_db:: defs:: { Definition , NameRefClass } ;
2- use itertools:: Itertools ;
3- use syntax:: { ast, AstNode , SyntaxKind , T } ;
3+ use syntax:: {
4+ ast:: { self , make, HasArgList } ,
5+ ted, AstNode ,
6+ } ;
47
58use crate :: {
69 assist_context:: { AssistContext , Assists } ,
@@ -25,21 +28,45 @@ use crate::{
2528// }
2629// ```
2730pub ( 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 ( ) . next ( ) . is_some ( ) {
31- return None ;
32- }
33- cov_mark:: hit!( add_turbo_fish_after_call) ;
34- cov_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 ! [ :: ] {
31+ let turbofish_target =
32+ ctx. find_node_at_offset :: < ast:: PathSegment > ( ) . map ( Either :: Left ) . or_else ( || {
33+ let callable_expr = ctx. find_node_at_offset :: < ast:: CallableExpr > ( ) ?;
34+
35+ if callable_expr. arg_list ( ) ?. args ( ) . next ( ) . is_some ( ) {
36+ return None ;
37+ }
38+
39+ cov_mark:: hit!( add_turbo_fish_after_call) ;
40+ cov_mark:: hit!( add_type_ascription_after_call) ;
41+
42+ match callable_expr {
43+ ast:: CallableExpr :: Call ( it) => {
44+ let ast:: Expr :: PathExpr ( path) = it. expr ( ) ? else {
45+ return None ;
46+ } ;
47+
48+ Some ( Either :: Left ( path. path ( ) ?. segment ( ) ?) )
49+ }
50+ ast:: CallableExpr :: MethodCall ( it) => Some ( Either :: Right ( it) ) ,
51+ }
52+ } ) ?;
53+
54+ let already_has_turbofish = match & turbofish_target {
55+ Either :: Left ( path_segment) => path_segment. generic_arg_list ( ) . is_some ( ) ,
56+ Either :: Right ( method_call) => method_call. generic_arg_list ( ) . is_some ( ) ,
57+ } ;
58+
59+ if already_has_turbofish {
3960 cov_mark:: hit!( add_turbo_fish_one_fish_is_enough) ;
4061 return None ;
4162 }
42- let name_ref = ast:: NameRef :: cast ( ident. parent ( ) ?) ?;
63+
64+ let name_ref = match & turbofish_target {
65+ Either :: Left ( path_segment) => path_segment. name_ref ( ) ?,
66+ Either :: Right ( method_call) => method_call. name_ref ( ) ?,
67+ } ;
68+ let ident = name_ref. ident_token ( ) ?;
69+
4370 let def = match NameRefClass :: classify ( & ctx. sema , & name_ref) ? {
4471 NameRefClass :: Definition ( def) => def,
4572 NameRefClass :: FieldShorthand { .. } | NameRefClass :: ExternCrateShorthand { .. } => {
@@ -91,33 +118,38 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti
91118 AssistId ( "add_turbo_fish" , AssistKind :: RefactorRewrite ) ,
92119 "Add `::<>`" ,
93120 ident. text_range ( ) ,
94- |builder| {
95- builder. trigger_signature_help ( ) ;
96- match ctx. config . snippet_cap {
97- Some ( cap) => {
98- let fish_head = get_snippet_fish_head ( number_of_arguments) ;
99- let snip = format ! ( "::<{fish_head}>" ) ;
100- builder. insert_snippet ( cap, ident. text_range ( ) . end ( ) , snip)
121+ |edit| {
122+ edit. trigger_signature_help ( ) ;
123+
124+ let new_arg_list = match turbofish_target {
125+ Either :: Left ( path_segment) => {
126+ edit. make_mut ( path_segment) . get_or_create_generic_arg_list ( )
101127 }
102- None => {
103- let fish_head = std:: iter:: repeat ( "_" ) . take ( number_of_arguments) . format ( ", " ) ;
104- let snip = format ! ( "::<{fish_head}>" ) ;
105- builder. insert ( ident. text_range ( ) . end ( ) , snip) ;
128+ Either :: Right ( method_call) => {
129+ edit. make_mut ( method_call) . get_or_create_generic_arg_list ( )
130+ }
131+ } ;
132+
133+ let fish_head = get_fish_head ( number_of_arguments) . clone_for_update ( ) ;
134+
135+ // Note: we need to replace the `new_arg_list` instead of being able to use something like
136+ // `GenericArgList::add_generic_arg` as `PathSegment::get_or_create_generic_arg_list`
137+ // always creates a non-turbofish form generic arg list.
138+ ted:: replace ( new_arg_list. syntax ( ) , fish_head. syntax ( ) ) ;
139+
140+ if let Some ( cap) = ctx. config . snippet_cap {
141+ for arg in fish_head. generic_args ( ) {
142+ edit. add_placeholder_snippet ( cap, arg)
106143 }
107144 }
108145 } ,
109146 )
110147}
111148
112- /// This will create a snippet string with tabstops marked
113- fn get_snippet_fish_head ( number_of_arguments : usize ) -> String {
114- let mut fish_head = ( 1 ..number_of_arguments)
115- . format_with ( "" , |i, f| f ( & format_args ! ( "${{{i}:_}}, " ) ) )
116- . to_string ( ) ;
117-
118- // tabstop 0 is a special case and always the last one
119- fish_head. push_str ( "${0:_}" ) ;
120- fish_head
149+ /// This will create a turbofish generic arg list corresponding to the number of arguments
150+ fn get_fish_head ( number_of_arguments : usize ) -> ast:: GenericArgList {
151+ let args = ( 0 ..number_of_arguments) . map ( |_| make:: type_arg ( make:: ty_placeholder ( ) ) . into ( ) ) ;
152+ make:: turbofish_generic_arg_list ( args)
121153}
122154
123155#[ cfg( test) ]
0 commit comments