aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists/src')
-rw-r--r--crates/ra_assists/src/handlers/change_lifetime_anon_to_named.rs260
1 files changed, 210 insertions, 50 deletions
diff --git a/crates/ra_assists/src/handlers/change_lifetime_anon_to_named.rs b/crates/ra_assists/src/handlers/change_lifetime_anon_to_named.rs
index 922213607..999aec421 100644
--- a/crates/ra_assists/src/handlers/change_lifetime_anon_to_named.rs
+++ b/crates/ra_assists/src/handlers/change_lifetime_anon_to_named.rs
@@ -1,6 +1,10 @@
1use crate::{AssistContext, AssistId, Assists}; 1use crate::{assist_context::AssistBuilder, AssistContext, AssistId, Assists};
2use ra_syntax::{ast, ast::TypeParamsOwner, AstNode, SyntaxKind}; 2use ast::{NameOwner, ParamList, TypeAscriptionOwner, TypeParamList, TypeRef};
3use std::collections::HashSet; 3use ra_syntax::{ast, ast::TypeParamsOwner, AstNode, SyntaxKind, TextRange, TextSize};
4use rustc_hash::FxHashSet;
5
6static ASSIST_NAME: &str = "change_lifetime_anon_to_named";
7static ASSIST_LABEL: &str = "Give anonymous lifetime a name";
4 8
5// Assist: change_lifetime_anon_to_named 9// Assist: change_lifetime_anon_to_named
6// 10//
@@ -26,59 +30,117 @@ use std::collections::HashSet;
26// } 30// }
27// ``` 31// ```
28// FIXME: How can we handle renaming any one of multiple anonymous lifetimes? 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
29pub(crate) fn change_lifetime_anon_to_named(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 34pub(crate) fn change_lifetime_anon_to_named(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
30 let lifetime_token = ctx.find_token_at_offset(SyntaxKind::LIFETIME)?; 35 let lifetime_token = ctx
31 let lifetime_arg = ast::LifetimeArg::cast(lifetime_token.parent())?; 36 .find_token_at_offset(SyntaxKind::LIFETIME)
32 if lifetime_arg.syntax().text() != "'_" { 37 .filter(|lifetime| lifetime.text() == "'_")?;
33 return None; 38 if let Some(fn_def) = lifetime_token.ancestors().find_map(ast::FnDef::cast) {
34 } 39 generate_fn_def_assist(acc, &fn_def, lifetime_token.text_range())
35 let next_token = lifetime_token.next_token()?; 40 } else if let Some(impl_def) = lifetime_token.ancestors().find_map(ast::ImplDef::cast) {
36 if next_token.kind() != SyntaxKind::R_ANGLE {
37 // only allow naming the last anonymous lifetime 41 // only allow naming the last anonymous lifetime
38 return None; 42 lifetime_token.next_token().filter(|tok| tok.kind() == SyntaxKind::R_ANGLE)?;
39 } 43 generate_impl_def_assist(acc, &impl_def, lifetime_token.text_range())
40 let impl_def = lifetime_arg.syntax().ancestors().find_map(ast::ImplDef::cast)?; 44 } else {
41 // get the `impl` keyword so we know where to add the lifetime argument 45 None
42 let impl_kw = impl_def.syntax().first_child_or_token()?.into_token()?;
43 if impl_kw.kind() != SyntaxKind::IMPL_KW {
44 return 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
50fn 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
93fn 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
108fn generate_unique_lifetime_param_name(
109 existing_type_param_list: &Option<TypeParamList>,
110) -> Option<char> {
111 match existing_type_param_list {
47 Some(type_params) => { 112 Some(type_params) => {
48 let used_lifetime_params: HashSet<_> = type_params 113 let used_lifetime_params: FxHashSet<_> = type_params
49 .lifetime_params() 114 .lifetime_params()
50 .map(|p| { 115 .map(|p| p.syntax().text().to_string()[1..].to_owned())
51 let mut param_name = p.syntax().text().to_string();
52 param_name.remove(0);
53 param_name
54 })
55 .collect(); 116 .collect();
56 (b'a'..=b'z') 117 (b'a'..=b'z').map(char::from).find(|c| !used_lifetime_params.contains(&c.to_string()))
57 .map(char::from)
58 .find(|c| !used_lifetime_params.contains(&c.to_string()))?
59 } 118 }
60 None => 'a', 119 None => Some('a'),
61 }; 120 }
62 acc.add( 121}
63 AssistId("change_lifetime_anon_to_named"), 122
64 "Give anonymous lifetime a name", 123/// Add the lifetime param to `builder`. If there are type parameters in `type_params_owner`, add it to the end. Otherwise
65 lifetime_arg.syntax().text_range(), 124/// add new type params brackets with the lifetime parameter at `new_type_params_loc`.
66 |builder| { 125fn add_lifetime_param<TypeParamsOwner: ast::TypeParamsOwner>(
67 match impl_def.type_param_list() { 126 type_params_owner: &TypeParamsOwner,
68 Some(type_params) => { 127 builder: &mut AssistBuilder,
69 builder.insert( 128 new_type_params_loc: TextSize,
70 (u32::from(type_params.syntax().text_range().end()) - 1).into(), 129 new_lifetime_param: char,
71 format!(", '{}", new_lifetime_param), 130) {
72 ); 131 match type_params_owner.type_param_list() {
73 } 132 // add the new lifetime parameter to an existing type param list
74 None => { 133 Some(type_params) => {
75 builder 134 builder.insert(
76 .insert(impl_kw.text_range().end(), format!("<'{}>", new_lifetime_param)); 135 (u32::from(type_params.syntax().text_range().end()) - 1).into(),
77 } 136 format!(", '{}", new_lifetime_param),
78 } 137 );
79 builder.replace(lifetime_arg.syntax().text_range(), format!("'{}", new_lifetime_param)); 138 }
80 }, 139 // create a new type param list containing only the new lifetime parameter
81 ) 140 None => {
141 builder.insert(new_type_params_loc, format!("<'{}>", new_lifetime_param));
142 }
143 }
82} 144}
83 145
84#[cfg(test)] 146#[cfg(test)]
@@ -117,10 +179,36 @@ mod tests {
117 } 179 }
118 180
119 #[test] 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 check_assist_not_applicable(change_lifetime_anon_to_named, r#"impl Cursor<'_><|> {"#); 201 check_assist_not_applicable(change_lifetime_anon_to_named, r#"impl Cursor<'_><|> {"#);
122 check_assist_not_applicable(change_lifetime_anon_to_named, r#"impl Cursor<|><'_> {"#); 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 check_assist_not_applicable(change_lifetime_anon_to_named, r#"impl Cursor<'a<|>> {"#); 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 #[test] 214 #[test]
@@ -140,4 +228,76 @@ mod tests {
140 r#"impl<'a, 'b, 'c> Cursor<'a, 'b, 'c>"#, 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}