1
+ use either:: Either ;
1
2
use 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
+ } ;
4
7
5
8
use crate :: {
6
9
assist_context:: { AssistContext , Assists } ,
@@ -25,21 +28,45 @@ use crate::{
25
28
// }
26
29
// ```
27
30
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 ( ) . 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 {
39
60
cov_mark:: hit!( add_turbo_fish_one_fish_is_enough) ;
40
61
return None ;
41
62
}
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
+
43
70
let def = match NameRefClass :: classify ( & ctx. sema , & name_ref) ? {
44
71
NameRefClass :: Definition ( def) => def,
45
72
NameRefClass :: FieldShorthand { .. } | NameRefClass :: ExternCrateShorthand { .. } => {
@@ -91,33 +118,38 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti
91
118
AssistId ( "add_turbo_fish" , AssistKind :: RefactorRewrite ) ,
92
119
"Add `::<>`" ,
93
120
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 ( )
101
127
}
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)
106
143
}
107
144
}
108
145
} ,
109
146
)
110
147
}
111
148
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)
121
153
}
122
154
123
155
#[ cfg( test) ]
0 commit comments