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" ;
4
8
5
9
// Assist: change_lifetime_anon_to_named
6
10
//
@@ -26,59 +30,117 @@ use std::collections::HashSet;
26
30
// }
27
31
// ```
28
32
// 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
29
34
pub ( 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) {
37
41
// 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
45
46
}
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 {
47
112
Some ( type_params) => {
48
- let used_lifetime_params: HashSet < _ > = type_params
113
+ let used_lifetime_params: FxHashSet < _ > = type_params
49
114
. 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 ( ) )
55
116
. 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 ( ) ) )
59
118
}
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
+ }
82
144
}
83
145
84
146
#[ cfg( test) ]
@@ -117,10 +179,36 @@ mod tests {
117
179
}
118
180
119
181
#[ 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 ( ) {
121
201
check_assist_not_applicable ( change_lifetime_anon_to_named, r#"impl Cursor<'_><|> {"# ) ;
122
202
check_assist_not_applicable ( change_lifetime_anon_to_named, r#"impl Cursor<|><'_> {"# ) ;
203
+ }
204
+
205
+ #[ test]
206
+ fn test_not_applicable_lifetime_already_name ( ) {
123
207
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
+ ) ;
124
212
}
125
213
126
214
#[ test]
@@ -140,4 +228,76 @@ mod tests {
140
228
r#"impl<'a, 'b, 'c> Cursor<'a, 'b, 'c>"# ,
141
229
) ;
142
230
}
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
+ }
143
303
}
0 commit comments