2 ast::{self, NameOwner, TypeAscriptionOwner, TypeParamsOwner},
3 AstNode, SyntaxKind, TextRange, TextSize,
5 use rustc_hash::FxHashSet;
7 use crate::{assist_context::AssistBuilder, AssistContext, AssistId, Assists};
9 static ASSIST_NAME: &str = "introduce_named_lifetime";
10 static ASSIST_LABEL: &str = "Introduce named lifetime";
12 // Assist: introduce_named_lifetime
14 // Change an anonymous lifetime to a named lifetime.
17 // impl Cursor<'_<|>> {
18 // fn node(self) -> &SyntaxNode {
20 // Cursor::Replace(node) | Cursor::Before(node) => node,
27 // impl<'a> Cursor<'a> {
28 // fn node(self) -> &SyntaxNode {
30 // Cursor::Replace(node) | Cursor::Before(node) => node,
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) -> &<|>Foo
37 pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
38 let lifetime_token = ctx
39 .find_token_at_offset(SyntaxKind::LIFETIME)
40 .filter(|lifetime| lifetime.text() == "'_")?;
41 if let Some(fn_def) = lifetime_token.ancestors().find_map(ast::FnDef::cast) {
42 generate_fn_def_assist(acc, &fn_def, lifetime_token.text_range())
43 } else if let Some(impl_def) = lifetime_token.ancestors().find_map(ast::ImplDef::cast) {
44 generate_impl_def_assist(acc, &impl_def, lifetime_token.text_range())
50 /// Generate the assist for the fn def case
51 fn generate_fn_def_assist(
54 lifetime_loc: TextRange,
56 let param_list: ast::ParamList = fn_def.param_list()?;
57 let new_lifetime_param = generate_unique_lifetime_param_name(&fn_def.type_param_list())?;
58 let end_of_fn_ident = fn_def.name()?.ident_token()?.text_range().end();
60 // use the self if it's a reference and has no explicit lifetime
61 param_list.self_param().filter(|p| p.lifetime_token().is_none() && p.amp_token().is_some());
62 // compute the location which implicitly has the same lifetime as the anonymous lifetime
63 let loc_needing_lifetime = if let Some(self_param) = self_param {
64 // if we have a self reference, use that
65 Some(self_param.self_token()?.text_range().start())
67 // otherwise, if there's a single reference parameter without a named liftime, use that
68 let fn_params_without_lifetime: Vec<_> = param_list
70 .filter_map(|param| match param.ascribed_type() {
71 Some(ast::TypeRef::ReferenceType(ascribed_type))
72 if ascribed_type.lifetime_token() == None =>
74 Some(ascribed_type.amp_token()?.text_range().end())
79 match fn_params_without_lifetime.len() {
80 1 => Some(fn_params_without_lifetime.into_iter().nth(0)?),
82 // multiple unnnamed is invalid. assist is not applicable
86 acc.add(AssistId(ASSIST_NAME), ASSIST_LABEL, lifetime_loc, |builder| {
87 add_lifetime_param(fn_def, builder, end_of_fn_ident, new_lifetime_param);
88 builder.replace(lifetime_loc, format!("'{}", new_lifetime_param));
89 loc_needing_lifetime.map(|loc| builder.insert(loc, format!("'{} ", new_lifetime_param)));
93 /// Generate the assist for the impl def case
94 fn generate_impl_def_assist(
96 impl_def: &ast::ImplDef,
97 lifetime_loc: TextRange,
99 let new_lifetime_param = generate_unique_lifetime_param_name(&impl_def.type_param_list())?;
100 let end_of_impl_kw = impl_def.impl_token()?.text_range().end();
101 acc.add(AssistId(ASSIST_NAME), ASSIST_LABEL, lifetime_loc, |builder| {
102 add_lifetime_param(impl_def, builder, end_of_impl_kw, new_lifetime_param);
103 builder.replace(lifetime_loc, format!("'{}", new_lifetime_param));
107 /// Given a type parameter list, generate a unique lifetime parameter name
108 /// which is not in the list
109 fn generate_unique_lifetime_param_name(
110 existing_type_param_list: &Option<ast::TypeParamList>,
112 match existing_type_param_list {
113 Some(type_params) => {
114 let used_lifetime_params: FxHashSet<_> = type_params
116 .map(|p| p.syntax().text().to_string()[1..].to_owned())
118 (b'a'..=b'z').map(char::from).find(|c| !used_lifetime_params.contains(&c.to_string()))
124 /// Add the lifetime param to `builder`. If there are type parameters in `type_params_owner`, add it to the end. Otherwise
125 /// add new type params brackets with the lifetime parameter at `new_type_params_loc`.
126 fn add_lifetime_param<TypeParamsOwner: ast::TypeParamsOwner>(
127 type_params_owner: &TypeParamsOwner,
128 builder: &mut AssistBuilder,
129 new_type_params_loc: TextSize,
130 new_lifetime_param: char,
132 match type_params_owner.type_param_list() {
133 // add the new lifetime parameter to an existing type param list
134 Some(type_params) => {
136 (u32::from(type_params.syntax().text_range().end()) - 1).into(),
137 format!(", '{}", new_lifetime_param),
140 // create a new type param list containing only the new lifetime parameter
142 builder.insert(new_type_params_loc, format!("<'{}>", new_lifetime_param));
150 use crate::tests::{check_assist, check_assist_not_applicable};
153 fn test_example_case() {
155 introduce_named_lifetime,
156 r#"impl Cursor<'_<|>> {
157 fn node(self) -> &SyntaxNode {
159 Cursor::Replace(node) | Cursor::Before(node) => node,
163 r#"impl<'a> Cursor<'a> {
164 fn node(self) -> &SyntaxNode {
166 Cursor::Replace(node) | Cursor::Before(node) => node,
174 fn test_example_case_simplified() {
176 introduce_named_lifetime,
177 r#"impl Cursor<'_<|>> {"#,
178 r#"impl<'a> Cursor<'a> {"#,
183 fn test_example_case_cursor_after_tick() {
185 introduce_named_lifetime,
186 r#"impl Cursor<'<|>_> {"#,
187 r#"impl<'a> Cursor<'a> {"#,
192 fn test_impl_with_other_type_param() {
194 introduce_named_lifetime,
195 "impl<I> fmt::Display for SepByBuilder<'_<|>, I>
198 I::Item: fmt::Display,
200 "impl<I, 'a> fmt::Display for SepByBuilder<'a, I>
203 I::Item: fmt::Display,
209 fn test_example_case_cursor_before_tick() {
211 introduce_named_lifetime,
212 r#"impl Cursor<<|>'_> {"#,
213 r#"impl<'a> Cursor<'a> {"#,
218 fn test_not_applicable_cursor_position() {
219 check_assist_not_applicable(introduce_named_lifetime, r#"impl Cursor<'_><|> {"#);
220 check_assist_not_applicable(introduce_named_lifetime, r#"impl Cursor<|><'_> {"#);
224 fn test_not_applicable_lifetime_already_name() {
225 check_assist_not_applicable(introduce_named_lifetime, r#"impl Cursor<'a<|>> {"#);
226 check_assist_not_applicable(introduce_named_lifetime, r#"fn my_fun<'a>() -> X<'a<|>>"#);
230 fn test_with_type_parameter() {
232 introduce_named_lifetime,
233 r#"impl<T> Cursor<T, '_<|>>"#,
234 r#"impl<T, 'a> Cursor<T, 'a>"#,
239 fn test_with_existing_lifetime_name_conflict() {
241 introduce_named_lifetime,
242 r#"impl<'a, 'b> Cursor<'a, 'b, '_<|>>"#,
243 r#"impl<'a, 'b, 'c> Cursor<'a, 'b, 'c>"#,
248 fn test_function_return_value_anon_lifetime_param() {
250 introduce_named_lifetime,
251 r#"fn my_fun() -> X<'_<|>>"#,
252 r#"fn my_fun<'a>() -> X<'a>"#,
257 fn test_function_return_value_anon_reference_lifetime() {
259 introduce_named_lifetime,
260 r#"fn my_fun() -> &'_<|> X"#,
261 r#"fn my_fun<'a>() -> &'a X"#,
266 fn test_function_param_anon_lifetime() {
268 introduce_named_lifetime,
269 r#"fn my_fun(x: X<'_<|>>)"#,
270 r#"fn my_fun<'a>(x: X<'a>)"#,
275 fn test_function_add_lifetime_to_params() {
277 introduce_named_lifetime,
278 r#"fn my_fun(f: &Foo) -> X<'_<|>>"#,
279 r#"fn my_fun<'a>(f: &'a Foo) -> X<'a>"#,
284 fn test_function_add_lifetime_to_params_in_presence_of_other_lifetime() {
286 introduce_named_lifetime,
287 r#"fn my_fun<'other>(f: &Foo, b: &'other Bar) -> X<'_<|>>"#,
288 r#"fn my_fun<'other, 'a>(f: &'a Foo, b: &'other Bar) -> X<'a>"#,
293 fn test_function_not_applicable_without_self_and_multiple_unnamed_param_lifetimes() {
294 // this is not permitted under lifetime elision rules
295 check_assist_not_applicable(
296 introduce_named_lifetime,
297 r#"fn my_fun(f: &Foo, b: &Bar) -> X<'_<|>>"#,
302 fn test_function_add_lifetime_to_self_ref_param() {
304 introduce_named_lifetime,
305 r#"fn my_fun<'other>(&self, f: &Foo, b: &'other Bar) -> X<'_<|>>"#,
306 r#"fn my_fun<'other, 'a>(&'a self, f: &Foo, b: &'other Bar) -> X<'a>"#,
311 fn test_function_add_lifetime_to_param_with_non_ref_self() {
313 introduce_named_lifetime,
314 r#"fn my_fun<'other>(self, f: &Foo, b: &'other Bar) -> X<'_<|>>"#,
315 r#"fn my_fun<'other, 'a>(self, f: &'a Foo, b: &'other Bar) -> X<'a>"#,