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 { .. } => {
@@ -58,20 +85,27 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti
5885
5986 if let Some ( let_stmt) = ctx. find_node_at_offset :: < ast:: LetStmt > ( ) {
6087 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+ }
6391
6492 acc. add (
6593 AssistId ( "add_type_ascription" , AssistKind :: RefactorRewrite ) ,
6694 "Add `: _` before assignment operator" ,
6795 ident. text_range ( ) ,
68- |builder| {
96+ |edit| {
97+ let let_stmt = edit. make_mut ( let_stmt) ;
98+
6999 if let_stmt. semicolon_token ( ) . is_none ( ) {
70- builder . insert ( semi_pos , ";" ) ;
100+ ted :: append_child ( let_stmt . syntax ( ) , make :: tokens :: semicolon ( ) ) ;
71101 }
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) ;
75109 }
76110 } ,
77111 ) ?
@@ -91,38 +125,46 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti
91125 AssistId ( "add_turbo_fish" , AssistKind :: RefactorRewrite ) ,
92126 "Add `::<>`" ,
93127 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 ( )
101137 }
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)
106150 }
107151 }
108152 } ,
109153 )
110154}
111155
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)
121160}
122161
123162#[ cfg( test) ]
124163mod 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+ } ;
126168
127169 use super :: * ;
128170
@@ -363,6 +405,20 @@ fn main() {
363405 ) ;
364406 }
365407
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+
366422 #[ test]
367423 fn add_turbo_fish_function_lifetime_parameter ( ) {
368424 check_assist (
0 commit comments