]> git.lizzy.rs Git - rust.git/blob - crates/assists/src/handlers/introduce_named_lifetime.rs
Merge #7195
[rust.git] / crates / assists / src / handlers / introduce_named_lifetime.rs
1 use rustc_hash::FxHashSet;
2 use syntax::{
3     ast::{self, GenericParamsOwner, NameOwner},
4     AstNode, TextRange, TextSize,
5 };
6
7 use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists};
8
9 static ASSIST_NAME: &str = "introduce_named_lifetime";
10 static ASSIST_LABEL: &str = "Introduce named lifetime";
11
12 // Assist: introduce_named_lifetime
13 //
14 // Change an anonymous lifetime to a named lifetime.
15 //
16 // ```
17 // impl Cursor<'_$0> {
18 //     fn node(self) -> &SyntaxNode {
19 //         match self {
20 //             Cursor::Replace(node) | Cursor::Before(node) => node,
21 //         }
22 //     }
23 // }
24 // ```
25 // ->
26 // ```
27 // impl<'a> Cursor<'a> {
28 //     fn node(self) -> &SyntaxNode {
29 //         match self {
30 //             Cursor::Replace(node) | Cursor::Before(node) => node,
31 //         }
32 //     }
33 // }
34 // ```
35 // FIXME: How can we handle renaming any one of multiple anonymous lifetimes?
36 // FIXME: should also add support for the case fun(f: &Foo) -> &$0Foo
37 pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
38     let lifetime =
39         ctx.find_node_at_offset::<ast::Lifetime>().filter(|lifetime| lifetime.text() == "'_")?;
40     if let Some(fn_def) = lifetime.syntax().ancestors().find_map(ast::Fn::cast) {
41         generate_fn_def_assist(acc, &fn_def, lifetime.lifetime_ident_token()?.text_range())
42     } else if let Some(impl_def) = lifetime.syntax().ancestors().find_map(ast::Impl::cast) {
43         generate_impl_def_assist(acc, &impl_def, lifetime.lifetime_ident_token()?.text_range())
44     } else {
45         None
46     }
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::Fn,
53     lifetime_loc: TextRange,
54 ) -> Option<()> {
55     let param_list: ast::ParamList = fn_def.param_list()?;
56     let new_lifetime_param = generate_unique_lifetime_param_name(&fn_def.generic_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().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.ty() {
70                 Some(ast::Type::RefType(ascribed_type)) if ascribed_type.lifetime().is_none() => {
71                     Some(ascribed_type.amp_token()?.text_range().end())
72                 }
73                 _ => None,
74             })
75             .collect();
76         match fn_params_without_lifetime.len() {
77             1 => Some(fn_params_without_lifetime.into_iter().nth(0)?),
78             0 => None,
79             // multiple unnnamed is invalid. assist is not applicable
80             _ => return None,
81         }
82     };
83     acc.add(AssistId(ASSIST_NAME, AssistKind::Refactor), ASSIST_LABEL, lifetime_loc, |builder| {
84         add_lifetime_param(fn_def, builder, end_of_fn_ident, new_lifetime_param);
85         builder.replace(lifetime_loc, format!("'{}", new_lifetime_param));
86         loc_needing_lifetime.map(|loc| builder.insert(loc, format!("'{} ", new_lifetime_param)));
87     })
88 }
89
90 /// Generate the assist for the impl def case
91 fn generate_impl_def_assist(
92     acc: &mut Assists,
93     impl_def: &ast::Impl,
94     lifetime_loc: TextRange,
95 ) -> Option<()> {
96     let new_lifetime_param = generate_unique_lifetime_param_name(&impl_def.generic_param_list())?;
97     let end_of_impl_kw = impl_def.impl_token()?.text_range().end();
98     acc.add(AssistId(ASSIST_NAME, AssistKind::Refactor), ASSIST_LABEL, lifetime_loc, |builder| {
99         add_lifetime_param(impl_def, builder, end_of_impl_kw, new_lifetime_param);
100         builder.replace(lifetime_loc, format!("'{}", new_lifetime_param));
101     })
102 }
103
104 /// Given a type parameter list, generate a unique lifetime parameter name
105 /// which is not in the list
106 fn generate_unique_lifetime_param_name(
107     existing_type_param_list: &Option<ast::GenericParamList>,
108 ) -> Option<char> {
109     match existing_type_param_list {
110         Some(type_params) => {
111             let used_lifetime_params: FxHashSet<_> = type_params
112                 .lifetime_params()
113                 .map(|p| p.syntax().text().to_string()[1..].to_owned())
114                 .collect();
115             (b'a'..=b'z').map(char::from).find(|c| !used_lifetime_params.contains(&c.to_string()))
116         }
117         None => Some('a'),
118     }
119 }
120
121 /// Add the lifetime param to `builder`. If there are type parameters in `type_params_owner`, add it to the end. Otherwise
122 /// add new type params brackets with the lifetime parameter at `new_type_params_loc`.
123 fn add_lifetime_param<TypeParamsOwner: ast::GenericParamsOwner>(
124     type_params_owner: &TypeParamsOwner,
125     builder: &mut AssistBuilder,
126     new_type_params_loc: TextSize,
127     new_lifetime_param: char,
128 ) {
129     match type_params_owner.generic_param_list() {
130         // add the new lifetime parameter to an existing type param list
131         Some(type_params) => {
132             builder.insert(
133                 (u32::from(type_params.syntax().text_range().end()) - 1).into(),
134                 format!(", '{}", new_lifetime_param),
135             );
136         }
137         // create a new type param list containing only the new lifetime parameter
138         None => {
139             builder.insert(new_type_params_loc, format!("<'{}>", new_lifetime_param));
140         }
141     }
142 }
143
144 #[cfg(test)]
145 mod tests {
146     use super::*;
147     use crate::tests::{check_assist, check_assist_not_applicable};
148
149     #[test]
150     fn test_example_case() {
151         check_assist(
152             introduce_named_lifetime,
153             r#"impl Cursor<'_$0> {
154                 fn node(self) -> &SyntaxNode {
155                     match self {
156                         Cursor::Replace(node) | Cursor::Before(node) => node,
157                     }
158                 }
159             }"#,
160             r#"impl<'a> Cursor<'a> {
161                 fn node(self) -> &SyntaxNode {
162                     match self {
163                         Cursor::Replace(node) | Cursor::Before(node) => node,
164                     }
165                 }
166             }"#,
167         );
168     }
169
170     #[test]
171     fn test_example_case_simplified() {
172         check_assist(
173             introduce_named_lifetime,
174             r#"impl Cursor<'_$0> {"#,
175             r#"impl<'a> Cursor<'a> {"#,
176         );
177     }
178
179     #[test]
180     fn test_example_case_cursor_after_tick() {
181         check_assist(
182             introduce_named_lifetime,
183             r#"impl Cursor<'$0_> {"#,
184             r#"impl<'a> Cursor<'a> {"#,
185         );
186     }
187
188     #[test]
189     fn test_impl_with_other_type_param() {
190         check_assist(
191             introduce_named_lifetime,
192             "impl<I> fmt::Display for SepByBuilder<'_$0, I>
193         where
194             I: Iterator,
195             I::Item: fmt::Display,
196         {",
197             "impl<I, 'a> fmt::Display for SepByBuilder<'a, I>
198         where
199             I: Iterator,
200             I::Item: fmt::Display,
201         {",
202         )
203     }
204
205     #[test]
206     fn test_example_case_cursor_before_tick() {
207         check_assist(
208             introduce_named_lifetime,
209             r#"impl Cursor<$0'_> {"#,
210             r#"impl<'a> Cursor<'a> {"#,
211         );
212     }
213
214     #[test]
215     fn test_not_applicable_cursor_position() {
216         check_assist_not_applicable(introduce_named_lifetime, r#"impl Cursor<'_>$0 {"#);
217         check_assist_not_applicable(introduce_named_lifetime, r#"impl Cursor$0<'_> {"#);
218     }
219
220     #[test]
221     fn test_not_applicable_lifetime_already_name() {
222         check_assist_not_applicable(introduce_named_lifetime, r#"impl Cursor<'a$0> {"#);
223         check_assist_not_applicable(introduce_named_lifetime, r#"fn my_fun<'a>() -> X<'a$0>"#);
224     }
225
226     #[test]
227     fn test_with_type_parameter() {
228         check_assist(
229             introduce_named_lifetime,
230             r#"impl<T> Cursor<T, '_$0>"#,
231             r#"impl<T, 'a> Cursor<T, 'a>"#,
232         );
233     }
234
235     #[test]
236     fn test_with_existing_lifetime_name_conflict() {
237         check_assist(
238             introduce_named_lifetime,
239             r#"impl<'a, 'b> Cursor<'a, 'b, '_$0>"#,
240             r#"impl<'a, 'b, 'c> Cursor<'a, 'b, 'c>"#,
241         );
242     }
243
244     #[test]
245     fn test_function_return_value_anon_lifetime_param() {
246         check_assist(
247             introduce_named_lifetime,
248             r#"fn my_fun() -> X<'_$0>"#,
249             r#"fn my_fun<'a>() -> X<'a>"#,
250         );
251     }
252
253     #[test]
254     fn test_function_return_value_anon_reference_lifetime() {
255         check_assist(
256             introduce_named_lifetime,
257             r#"fn my_fun() -> &'_$0 X"#,
258             r#"fn my_fun<'a>() -> &'a X"#,
259         );
260     }
261
262     #[test]
263     fn test_function_param_anon_lifetime() {
264         check_assist(
265             introduce_named_lifetime,
266             r#"fn my_fun(x: X<'_$0>)"#,
267             r#"fn my_fun<'a>(x: X<'a>)"#,
268         );
269     }
270
271     #[test]
272     fn test_function_add_lifetime_to_params() {
273         check_assist(
274             introduce_named_lifetime,
275             r#"fn my_fun(f: &Foo) -> X<'_$0>"#,
276             r#"fn my_fun<'a>(f: &'a Foo) -> X<'a>"#,
277         );
278     }
279
280     #[test]
281     fn test_function_add_lifetime_to_params_in_presence_of_other_lifetime() {
282         check_assist(
283             introduce_named_lifetime,
284             r#"fn my_fun<'other>(f: &Foo, b: &'other Bar) -> X<'_$0>"#,
285             r#"fn my_fun<'other, 'a>(f: &'a Foo, b: &'other Bar) -> X<'a>"#,
286         );
287     }
288
289     #[test]
290     fn test_function_not_applicable_without_self_and_multiple_unnamed_param_lifetimes() {
291         // this is not permitted under lifetime elision rules
292         check_assist_not_applicable(
293             introduce_named_lifetime,
294             r#"fn my_fun(f: &Foo, b: &Bar) -> X<'_$0>"#,
295         );
296     }
297
298     #[test]
299     fn test_function_add_lifetime_to_self_ref_param() {
300         check_assist(
301             introduce_named_lifetime,
302             r#"fn my_fun<'other>(&self, f: &Foo, b: &'other Bar) -> X<'_$0>"#,
303             r#"fn my_fun<'other, 'a>(&'a self, f: &Foo, b: &'other Bar) -> X<'a>"#,
304         );
305     }
306
307     #[test]
308     fn test_function_add_lifetime_to_param_with_non_ref_self() {
309         check_assist(
310             introduce_named_lifetime,
311             r#"fn my_fun<'other>(self, f: &Foo, b: &'other Bar) -> X<'_$0>"#,
312             r#"fn my_fun<'other, 'a>(self, f: &'a Foo, b: &'other Bar) -> X<'a>"#,
313         );
314     }
315 }