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 { .. } => {
@@ -58,20 +85,27 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti
58
85
59
86
if let Some ( let_stmt) = ctx. find_node_at_offset :: < ast:: LetStmt > ( ) {
60
87
if let_stmt. colon_token ( ) . is_none ( ) {
61
- let type_pos = let_stmt. pat ( ) ?. syntax ( ) . last_token ( ) ?. text_range ( ) . end ( ) ;
62
- let semi_pos = let_stmt. syntax ( ) . last_token ( ) ?. text_range ( ) . end ( ) ;
88
+ if let_stmt. pat ( ) . is_none ( ) {
89
+ return None ;
90
+ }
63
91
64
92
acc. add (
65
93
AssistId ( "add_type_ascription" , AssistKind :: RefactorRewrite ) ,
66
94
"Add `: _` before assignment operator" ,
67
95
ident. text_range ( ) ,
68
- |builder| {
96
+ |edit| {
97
+ let let_stmt = edit. make_mut ( let_stmt) ;
98
+
69
99
if let_stmt. semicolon_token ( ) . is_none ( ) {
70
- builder . insert ( semi_pos , ";" ) ;
100
+ ted :: append_child ( let_stmt . syntax ( ) , make :: tokens :: semicolon ( ) ) ;
71
101
}
72
- match ctx. config . snippet_cap {
73
- Some ( cap) => builder. insert_snippet ( cap, type_pos, ": ${0:_}" ) ,
74
- None => builder. insert ( type_pos, ": _" ) ,
102
+
103
+ let placeholder_ty = make:: ty_placeholder ( ) . clone_for_update ( ) ;
104
+
105
+ let_stmt. set_ty ( Some ( placeholder_ty. clone ( ) ) ) ;
106
+
107
+ if let Some ( cap) = ctx. config . snippet_cap {
108
+ edit. add_placeholder_snippet ( cap, placeholder_ty) ;
75
109
}
76
110
} ,
77
111
) ?
@@ -91,38 +125,46 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti
91
125
AssistId ( "add_turbo_fish" , AssistKind :: RefactorRewrite ) ,
92
126
"Add `::<>`" ,
93
127
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)
128
+ |edit| {
129
+ edit. trigger_signature_help ( ) ;
130
+
131
+ let new_arg_list = match turbofish_target {
132
+ Either :: Left ( path_segment) => {
133
+ edit. make_mut ( path_segment) . get_or_create_generic_arg_list ( )
134
+ }
135
+ Either :: Right ( method_call) => {
136
+ edit. make_mut ( method_call) . get_or_create_generic_arg_list ( )
101
137
}
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) ;
138
+ } ;
139
+
140
+ let fish_head = get_fish_head ( number_of_arguments) . clone_for_update ( ) ;
141
+
142
+ // Note: we need to replace the `new_arg_list` instead of being able to use something like
143
+ // `GenericArgList::add_generic_arg` as `PathSegment::get_or_create_generic_arg_list`
144
+ // always creates a non-turbofish form generic arg list.
145
+ ted:: replace ( new_arg_list. syntax ( ) , fish_head. syntax ( ) ) ;
146
+
147
+ if let Some ( cap) = ctx. config . snippet_cap {
148
+ for arg in fish_head. generic_args ( ) {
149
+ edit. add_placeholder_snippet ( cap, arg)
106
150
}
107
151
}
108
152
} ,
109
153
)
110
154
}
111
155
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
156
+ /// This will create a turbofish generic arg list corresponding to the number of arguments
157
+ fn get_fish_head ( number_of_arguments : usize ) -> ast:: GenericArgList {
158
+ let args = ( 0 ..number_of_arguments) . map ( |_| make:: type_arg ( make:: ty_placeholder ( ) ) . into ( ) ) ;
159
+ make:: turbofish_generic_arg_list ( args)
121
160
}
122
161
123
162
#[ cfg( test) ]
124
163
mod tests {
125
- use crate :: tests:: { check_assist, check_assist_by_label, check_assist_not_applicable} ;
164
+ use crate :: tests:: {
165
+ check_assist, check_assist_by_label, check_assist_not_applicable,
166
+ check_assist_not_applicable_by_label,
167
+ } ;
126
168
127
169
use super :: * ;
128
170
@@ -363,6 +405,20 @@ fn main() {
363
405
) ;
364
406
}
365
407
408
+ #[ test]
409
+ fn add_type_ascription_missing_pattern ( ) {
410
+ check_assist_not_applicable_by_label (
411
+ add_turbo_fish,
412
+ r#"
413
+ fn make<T>() -> T {}
414
+ fn main() {
415
+ let = make$0()
416
+ }
417
+ "# ,
418
+ "Add `: _` before assignment operator" ,
419
+ ) ;
420
+ }
421
+
366
422
#[ test]
367
423
fn add_turbo_fish_function_lifetime_parameter ( ) {
368
424
check_assist (
0 commit comments