1- use crate :: { AssistContext , AssistId , Assists } ;
2- use ra_syntax:: { ast, ast:: TypeParamsOwner , AstNode , SyntaxKind } ;
3- use std:: collections:: HashSet ;
1+ use crate :: { assist_context:: AssistBuilder , AssistContext , AssistId , Assists } ;
2+ use ast:: { NameOwner , ParamList , TypeAscriptionOwner , TypeParamList , TypeRef } ;
3+ use ra_syntax:: { ast, ast:: TypeParamsOwner , AstNode , SyntaxKind , TextRange , TextSize } ;
4+ use rustc_hash:: FxHashSet ;
5+
6+ static ASSIST_NAME : & str = "change_lifetime_anon_to_named" ;
7+ static ASSIST_LABEL : & str = "Give anonymous lifetime a name" ;
48
59// Assist: change_lifetime_anon_to_named
610//
@@ -26,59 +30,117 @@ use std::collections::HashSet;
2630// }
2731// ```
2832// FIXME: How can we handle renaming any one of multiple anonymous lifetimes?
33+ // FIXME: should also add support for the case fun(f: &Foo) -> &<|>Foo
2934pub ( crate ) fn change_lifetime_anon_to_named ( acc : & mut Assists , ctx : & AssistContext ) -> Option < ( ) > {
30- let lifetime_token = ctx. find_token_at_offset ( SyntaxKind :: LIFETIME ) ?;
31- let lifetime_arg = ast:: LifetimeArg :: cast ( lifetime_token. parent ( ) ) ?;
32- if lifetime_arg. syntax ( ) . text ( ) != "'_" {
33- return None ;
34- }
35- let next_token = lifetime_token. next_token ( ) ?;
36- if next_token. kind ( ) != SyntaxKind :: R_ANGLE {
35+ let lifetime_token = ctx
36+ . find_token_at_offset ( SyntaxKind :: LIFETIME )
37+ . filter ( |lifetime| lifetime. text ( ) == "'_" ) ?;
38+ if let Some ( fn_def) = lifetime_token. ancestors ( ) . find_map ( ast:: FnDef :: cast) {
39+ generate_fn_def_assist ( acc, & fn_def, lifetime_token. text_range ( ) )
40+ } else if let Some ( impl_def) = lifetime_token. ancestors ( ) . find_map ( ast:: ImplDef :: cast) {
3741 // only allow naming the last anonymous lifetime
38- return None ;
39- }
40- let impl_def = lifetime_arg. syntax ( ) . ancestors ( ) . find_map ( ast:: ImplDef :: cast) ?;
41- // get the `impl` keyword so we know where to add the lifetime argument
42- let impl_kw = impl_def. syntax ( ) . first_child_or_token ( ) ?. into_token ( ) ?;
43- if impl_kw. kind ( ) != SyntaxKind :: IMPL_KW {
44- return None ;
42+ lifetime_token. next_token ( ) . filter ( |tok| tok. kind ( ) == SyntaxKind :: R_ANGLE ) ?;
43+ generate_impl_def_assist ( acc, & impl_def, lifetime_token. text_range ( ) )
44+ } else {
45+ None
4546 }
46- let new_lifetime_param = match impl_def. type_param_list ( ) {
47+ }
48+
49+ /// Generate the assist for the fn def case
50+ fn generate_fn_def_assist (
51+ acc : & mut Assists ,
52+ fn_def : & ast:: FnDef ,
53+ lifetime_loc : TextRange ,
54+ ) -> Option < ( ) > {
55+ let param_list: ParamList = fn_def. param_list ( ) ?;
56+ let new_lifetime_param = generate_unique_lifetime_param_name ( & fn_def. type_param_list ( ) ) ?;
57+ let end_of_fn_ident = fn_def. name ( ) ?. ident_token ( ) ?. text_range ( ) . end ( ) ;
58+ let self_param =
59+ // use the self if it's a reference and has no explicit lifetime
60+ param_list. self_param ( ) . filter ( |p| p. lifetime_token ( ) . is_none ( ) && p. amp_token ( ) . is_some ( ) ) ;
61+ // compute the location which implicitly has the same lifetime as the anonymous lifetime
62+ let loc_needing_lifetime = if let Some ( self_param) = self_param {
63+ // if we have a self reference, use that
64+ Some ( self_param. self_token ( ) ?. text_range ( ) . start ( ) )
65+ } else {
66+ // otherwise, if there's a single reference parameter without a named liftime, use that
67+ let fn_params_without_lifetime: Vec < _ > = param_list
68+ . params ( )
69+ . filter_map ( |param| match param. ascribed_type ( ) {
70+ Some ( TypeRef :: ReferenceType ( ascribed_type) )
71+ if ascribed_type. lifetime_token ( ) == None =>
72+ {
73+ Some ( ascribed_type. amp_token ( ) ?. text_range ( ) . end ( ) )
74+ }
75+ _ => None ,
76+ } )
77+ . collect ( ) ;
78+ match fn_params_without_lifetime. len ( ) {
79+ 1 => Some ( fn_params_without_lifetime. into_iter ( ) . nth ( 0 ) ?) ,
80+ 0 => None ,
81+ // multiple unnnamed is invalid. assist is not applicable
82+ _ => return None ,
83+ }
84+ } ;
85+ acc. add ( AssistId ( ASSIST_NAME ) , ASSIST_LABEL , lifetime_loc, |builder| {
86+ add_lifetime_param ( fn_def, builder, end_of_fn_ident, new_lifetime_param) ;
87+ builder. replace ( lifetime_loc, format ! ( "'{}" , new_lifetime_param) ) ;
88+ loc_needing_lifetime. map ( |loc| builder. insert ( loc, format ! ( "'{} " , new_lifetime_param) ) ) ;
89+ } )
90+ }
91+
92+ /// Generate the assist for the impl def case
93+ fn generate_impl_def_assist (
94+ acc : & mut Assists ,
95+ impl_def : & ast:: ImplDef ,
96+ lifetime_loc : TextRange ,
97+ ) -> Option < ( ) > {
98+ let new_lifetime_param = generate_unique_lifetime_param_name ( & impl_def. type_param_list ( ) ) ?;
99+ let end_of_impl_kw = impl_def. impl_token ( ) ?. text_range ( ) . end ( ) ;
100+ acc. add ( AssistId ( ASSIST_NAME ) , ASSIST_LABEL , lifetime_loc, |builder| {
101+ add_lifetime_param ( impl_def, builder, end_of_impl_kw, new_lifetime_param) ;
102+ builder. replace ( lifetime_loc, format ! ( "'{}" , new_lifetime_param) ) ;
103+ } )
104+ }
105+
106+ /// Given a type parameter list, generate a unique lifetime parameter name
107+ /// which is not in the list
108+ fn generate_unique_lifetime_param_name (
109+ existing_type_param_list : & Option < TypeParamList > ,
110+ ) -> Option < char > {
111+ match existing_type_param_list {
47112 Some ( type_params) => {
48- let used_lifetime_params: HashSet < _ > = type_params
113+ let used_lifetime_params: FxHashSet < _ > = type_params
49114 . lifetime_params ( )
50- . map ( |p| {
51- let mut param_name = p. syntax ( ) . text ( ) . to_string ( ) ;
52- param_name. remove ( 0 ) ;
53- param_name
54- } )
115+ . map ( |p| p. syntax ( ) . text ( ) . to_string ( ) [ 1 ..] . to_owned ( ) )
55116 . collect ( ) ;
56- ( b'a' ..=b'z' )
57- . map ( char:: from)
58- . find ( |c| !used_lifetime_params. contains ( & c. to_string ( ) ) ) ?
117+ ( b'a' ..=b'z' ) . map ( char:: from) . find ( |c| !used_lifetime_params. contains ( & c. to_string ( ) ) )
59118 }
60- None => 'a' ,
61- } ;
62- acc. add (
63- AssistId ( "change_lifetime_anon_to_named" ) ,
64- "Give anonymous lifetime a name" ,
65- lifetime_arg. syntax ( ) . text_range ( ) ,
66- |builder| {
67- match impl_def. type_param_list ( ) {
68- Some ( type_params) => {
69- builder. insert (
70- ( u32:: from ( type_params. syntax ( ) . text_range ( ) . end ( ) ) - 1 ) . into ( ) ,
71- format ! ( ", '{}" , new_lifetime_param) ,
72- ) ;
73- }
74- None => {
75- builder
76- . insert ( impl_kw. text_range ( ) . end ( ) , format ! ( "<'{}>" , new_lifetime_param) ) ;
77- }
78- }
79- builder. replace ( lifetime_arg. syntax ( ) . text_range ( ) , format ! ( "'{}" , new_lifetime_param) ) ;
80- } ,
81- )
119+ None => Some ( 'a' ) ,
120+ }
121+ }
122+
123+ /// Add the lifetime param to `builder`. If there are type parameters in `type_params_owner`, add it to the end. Otherwise
124+ /// add new type params brackets with the lifetime parameter at `new_type_params_loc`.
125+ fn add_lifetime_param < TypeParamsOwner : ast:: TypeParamsOwner > (
126+ type_params_owner : & TypeParamsOwner ,
127+ builder : & mut AssistBuilder ,
128+ new_type_params_loc : TextSize ,
129+ new_lifetime_param : char ,
130+ ) {
131+ match type_params_owner. type_param_list ( ) {
132+ // add the new lifetime parameter to an existing type param list
133+ Some ( type_params) => {
134+ builder. insert (
135+ ( u32:: from ( type_params. syntax ( ) . text_range ( ) . end ( ) ) - 1 ) . into ( ) ,
136+ format ! ( ", '{}" , new_lifetime_param) ,
137+ ) ;
138+ }
139+ // create a new type param list containing only the new lifetime parameter
140+ None => {
141+ builder. insert ( new_type_params_loc, format ! ( "<'{}>" , new_lifetime_param) ) ;
142+ }
143+ }
82144}
83145
84146#[ cfg( test) ]
@@ -117,10 +179,36 @@ mod tests {
117179 }
118180
119181 #[ test]
120- fn test_not_applicable ( ) {
182+ fn test_example_case_cursor_after_tick ( ) {
183+ check_assist (
184+ change_lifetime_anon_to_named,
185+ r#"impl Cursor<'<|>_> {"# ,
186+ r#"impl<'a> Cursor<'a> {"# ,
187+ ) ;
188+ }
189+
190+ #[ test]
191+ fn test_example_case_cursor_before_tick ( ) {
192+ check_assist (
193+ change_lifetime_anon_to_named,
194+ r#"impl Cursor<<|>'_> {"# ,
195+ r#"impl<'a> Cursor<'a> {"# ,
196+ ) ;
197+ }
198+
199+ #[ test]
200+ fn test_not_applicable_cursor_position ( ) {
121201 check_assist_not_applicable ( change_lifetime_anon_to_named, r#"impl Cursor<'_><|> {"# ) ;
122202 check_assist_not_applicable ( change_lifetime_anon_to_named, r#"impl Cursor<|><'_> {"# ) ;
203+ }
204+
205+ #[ test]
206+ fn test_not_applicable_lifetime_already_name ( ) {
123207 check_assist_not_applicable ( change_lifetime_anon_to_named, r#"impl Cursor<'a<|>> {"# ) ;
208+ check_assist_not_applicable (
209+ change_lifetime_anon_to_named,
210+ r#"fn my_fun<'a>() -> X<'a<|>>"# ,
211+ ) ;
124212 }
125213
126214 #[ test]
@@ -140,4 +228,76 @@ mod tests {
140228 r#"impl<'a, 'b, 'c> Cursor<'a, 'b, 'c>"# ,
141229 ) ;
142230 }
231+
232+ #[ test]
233+ fn test_function_return_value_anon_lifetime_param ( ) {
234+ check_assist (
235+ change_lifetime_anon_to_named,
236+ r#"fn my_fun() -> X<'_<|>>"# ,
237+ r#"fn my_fun<'a>() -> X<'a>"# ,
238+ ) ;
239+ }
240+
241+ #[ test]
242+ fn test_function_return_value_anon_reference_lifetime ( ) {
243+ check_assist (
244+ change_lifetime_anon_to_named,
245+ r#"fn my_fun() -> &'_<|> X"# ,
246+ r#"fn my_fun<'a>() -> &'a X"# ,
247+ ) ;
248+ }
249+
250+ #[ test]
251+ fn test_function_param_anon_lifetime ( ) {
252+ check_assist (
253+ change_lifetime_anon_to_named,
254+ r#"fn my_fun(x: X<'_<|>>)"# ,
255+ r#"fn my_fun<'a>(x: X<'a>)"# ,
256+ ) ;
257+ }
258+
259+ #[ test]
260+ fn test_function_add_lifetime_to_params ( ) {
261+ check_assist (
262+ change_lifetime_anon_to_named,
263+ r#"fn my_fun(f: &Foo) -> X<'_<|>>"# ,
264+ r#"fn my_fun<'a>(f: &'a Foo) -> X<'a>"# ,
265+ ) ;
266+ }
267+
268+ #[ test]
269+ fn test_function_add_lifetime_to_params_in_presence_of_other_lifetime ( ) {
270+ check_assist (
271+ change_lifetime_anon_to_named,
272+ r#"fn my_fun<'other>(f: &Foo, b: &'other Bar) -> X<'_<|>>"# ,
273+ r#"fn my_fun<'other, 'a>(f: &'a Foo, b: &'other Bar) -> X<'a>"# ,
274+ ) ;
275+ }
276+
277+ #[ test]
278+ fn test_function_not_applicable_without_self_and_multiple_unnamed_param_lifetimes ( ) {
279+ // this is not permitted under lifetime elision rules
280+ check_assist_not_applicable (
281+ change_lifetime_anon_to_named,
282+ r#"fn my_fun(f: &Foo, b: &Bar) -> X<'_<|>>"# ,
283+ ) ;
284+ }
285+
286+ #[ test]
287+ fn test_function_add_lifetime_to_self_ref_param ( ) {
288+ check_assist (
289+ change_lifetime_anon_to_named,
290+ r#"fn my_fun<'other>(&self, f: &Foo, b: &'other Bar) -> X<'_<|>>"# ,
291+ r#"fn my_fun<'other, 'a>(&'a self, f: &Foo, b: &'other Bar) -> X<'a>"# ,
292+ ) ;
293+ }
294+
295+ #[ test]
296+ fn test_function_add_lifetime_to_param_with_non_ref_self ( ) {
297+ check_assist (
298+ change_lifetime_anon_to_named,
299+ r#"fn my_fun<'other>(self, f: &Foo, b: &'other Bar) -> X<'_<|>>"# ,
300+ r#"fn my_fun<'other, 'a>(self, f: &'a Foo, b: &'other Bar) -> X<'a>"# ,
301+ ) ;
302+ }
143303}
0 commit comments