aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_assists/src/handlers
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_assists/src/handlers')
-rw-r--r--crates/ide_assists/src/handlers/introduce_named_lifetime.rs112
1 files changed, 76 insertions, 36 deletions
diff --git a/crates/ide_assists/src/handlers/introduce_named_lifetime.rs b/crates/ide_assists/src/handlers/introduce_named_lifetime.rs
index 02782eb6d..9f4f71d6c 100644
--- a/crates/ide_assists/src/handlers/introduce_named_lifetime.rs
+++ b/crates/ide_assists/src/handlers/introduce_named_lifetime.rs
@@ -1,7 +1,8 @@
1use rustc_hash::FxHashSet; 1use rustc_hash::FxHashSet;
2use syntax::{ 2use syntax::{
3 ast::{self, GenericParamsOwner, NameOwner}, 3 ast::{self, edit_in_place::GenericParamsOwnerEdit, make, GenericParamsOwner},
4 AstNode, TextRange, TextSize, 4 ted::{self, Position},
5 AstNode, TextRange,
5}; 6};
6 7
7use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists}; 8use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists};
@@ -37,10 +38,12 @@ static ASSIST_LABEL: &str = "Introduce named lifetime";
37pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 38pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
38 let lifetime = 39 let lifetime =
39 ctx.find_node_at_offset::<ast::Lifetime>().filter(|lifetime| lifetime.text() == "'_")?; 40 ctx.find_node_at_offset::<ast::Lifetime>().filter(|lifetime| lifetime.text() == "'_")?;
41 let lifetime_loc = lifetime.lifetime_ident_token()?.text_range();
42
40 if let Some(fn_def) = lifetime.syntax().ancestors().find_map(ast::Fn::cast) { 43 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()) 44 generate_fn_def_assist(acc, fn_def, lifetime_loc, lifetime)
42 } else if let Some(impl_def) = lifetime.syntax().ancestors().find_map(ast::Impl::cast) { 45 } 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()) 46 generate_impl_def_assist(acc, impl_def, lifetime_loc, lifetime)
44 } else { 47 } else {
45 None 48 None
46 } 49 }
@@ -49,26 +52,26 @@ pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) -
49/// Generate the assist for the fn def case 52/// Generate the assist for the fn def case
50fn generate_fn_def_assist( 53fn generate_fn_def_assist(
51 acc: &mut Assists, 54 acc: &mut Assists,
52 fn_def: &ast::Fn, 55 fn_def: ast::Fn,
53 lifetime_loc: TextRange, 56 lifetime_loc: TextRange,
57 lifetime: ast::Lifetime,
54) -> Option<()> { 58) -> Option<()> {
55 let param_list: ast::ParamList = fn_def.param_list()?; 59 let param_list: ast::ParamList = fn_def.param_list()?;
56 let new_lifetime_param = generate_unique_lifetime_param_name(&fn_def.generic_param_list())?; 60 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 = 61 let self_param =
59 // use the self if it's a reference and has no explicit lifetime 62 // 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()); 63 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 64 // 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 { 65 let loc_needing_lifetime = if let Some(self_param) = self_param {
63 // if we have a self reference, use that 66 // if we have a self reference, use that
64 Some(self_param.name()?.syntax().text_range().start()) 67 Some(NeedsLifetime::SelfParam(self_param))
65 } else { 68 } else {
66 // otherwise, if there's a single reference parameter without a named liftime, use that 69 // otherwise, if there's a single reference parameter without a named liftime, use that
67 let fn_params_without_lifetime: Vec<_> = param_list 70 let fn_params_without_lifetime: Vec<_> = param_list
68 .params() 71 .params()
69 .filter_map(|param| match param.ty() { 72 .filter_map(|param| match param.ty() {
70 Some(ast::Type::RefType(ascribed_type)) if ascribed_type.lifetime().is_none() => { 73 Some(ast::Type::RefType(ascribed_type)) if ascribed_type.lifetime().is_none() => {
71 Some(ascribed_type.amp_token()?.text_range().end()) 74 Some(NeedsLifetime::RefType(ascribed_type))
72 } 75 }
73 _ => None, 76 _ => None,
74 }) 77 })
@@ -81,30 +84,46 @@ fn generate_fn_def_assist(
81 } 84 }
82 }; 85 };
83 acc.add(AssistId(ASSIST_NAME, AssistKind::Refactor), ASSIST_LABEL, lifetime_loc, |builder| { 86 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); 87 let fn_def = builder.make_ast_mut(fn_def);
85 builder.replace(lifetime_loc, format!("'{}", new_lifetime_param)); 88 let lifetime = builder.make_ast_mut(lifetime);
86 loc_needing_lifetime.map(|loc| builder.insert(loc, format!("'{} ", new_lifetime_param))); 89 let loc_needing_lifetime =
90 loc_needing_lifetime.and_then(|it| it.make_mut(builder).to_position());
91
92 add_lifetime_param(fn_def.get_or_create_generic_param_list(), new_lifetime_param);
93 ted::replace(
94 lifetime.syntax(),
95 make_ast_lifetime(new_lifetime_param).clone_for_update().syntax(),
96 );
97 loc_needing_lifetime.map(|position| {
98 ted::insert(position, make_ast_lifetime(new_lifetime_param).clone_for_update().syntax())
99 });
87 }) 100 })
88} 101}
89 102
90/// Generate the assist for the impl def case 103/// Generate the assist for the impl def case
91fn generate_impl_def_assist( 104fn generate_impl_def_assist(
92 acc: &mut Assists, 105 acc: &mut Assists,
93 impl_def: &ast::Impl, 106 impl_def: ast::Impl,
94 lifetime_loc: TextRange, 107 lifetime_loc: TextRange,
108 lifetime: ast::Lifetime,
95) -> Option<()> { 109) -> Option<()> {
96 let new_lifetime_param = generate_unique_lifetime_param_name(&impl_def.generic_param_list())?; 110 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| { 111 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); 112 let impl_def = builder.make_ast_mut(impl_def);
100 builder.replace(lifetime_loc, format!("'{}", new_lifetime_param)); 113 let lifetime = builder.make_ast_mut(lifetime);
114
115 add_lifetime_param(impl_def.get_or_create_generic_param_list(), new_lifetime_param);
116 ted::replace(
117 lifetime.syntax(),
118 make_ast_lifetime(new_lifetime_param).clone_for_update().syntax(),
119 );
101 }) 120 })
102} 121}
103 122
104/// Given a type parameter list, generate a unique lifetime parameter name 123/// Given a type parameter list, generate a unique lifetime parameter name
105/// which is not in the list 124/// which is not in the list
106fn generate_unique_lifetime_param_name( 125fn generate_unique_lifetime_param_name(
107 existing_type_param_list: &Option<ast::GenericParamList>, 126 existing_type_param_list: Option<ast::GenericParamList>,
108) -> Option<char> { 127) -> Option<char> {
109 match existing_type_param_list { 128 match existing_type_param_list {
110 Some(type_params) => { 129 Some(type_params) => {
@@ -118,25 +137,37 @@ fn generate_unique_lifetime_param_name(
118 } 137 }
119} 138}
120 139
121/// Add the lifetime param to `builder`. If there are type parameters in `type_params_owner`, add it to the end. Otherwise 140fn add_lifetime_param(type_params: ast::GenericParamList, new_lifetime_param: char) {
122/// add new type params brackets with the lifetime parameter at `new_type_params_loc`. 141 let generic_param =
123fn add_lifetime_param<TypeParamsOwner: ast::GenericParamsOwner>( 142 make::generic_param(format!("'{}", new_lifetime_param), None).clone_for_update();
124 type_params_owner: &TypeParamsOwner, 143 type_params.add_generic_param(generic_param);
125 builder: &mut AssistBuilder, 144}
126 new_type_params_loc: TextSize, 145
127 new_lifetime_param: char, 146fn make_ast_lifetime(new_lifetime_param: char) -> ast::Lifetime {
128) { 147 make::generic_param(format!("'{}", new_lifetime_param), None)
129 match type_params_owner.generic_param_list() { 148 .syntax()
130 // add the new lifetime parameter to an existing type param list 149 .descendants()
131 Some(type_params) => { 150 .find_map(ast::Lifetime::cast)
132 builder.insert( 151 .unwrap()
133 (u32::from(type_params.syntax().text_range().end()) - 1).into(), 152}
134 format!(", '{}", new_lifetime_param), 153
135 ); 154enum NeedsLifetime {
155 SelfParam(ast::SelfParam),
156 RefType(ast::RefType),
157}
158
159impl NeedsLifetime {
160 fn make_mut(self, builder: &mut AssistBuilder) -> Self {
161 match self {
162 Self::SelfParam(it) => Self::SelfParam(builder.make_ast_mut(it)),
163 Self::RefType(it) => Self::RefType(builder.make_ast_mut(it)),
136 } 164 }
137 // create a new type param list containing only the new lifetime parameter 165 }
138 None => { 166
139 builder.insert(new_type_params_loc, format!("<'{}>", new_lifetime_param)); 167 fn to_position(self) -> Option<Position> {
168 match self {
169 Self::SelfParam(it) => Some(Position::after(it.amp_token()?)),
170 Self::RefType(it) => Some(Position::after(it.amp_token()?)),
140 } 171 }
141 } 172 }
142} 173}
@@ -312,4 +343,13 @@ mod tests {
312 r#"fn my_fun<'other, 'a>(self, f: &'a Foo, b: &'other Bar) -> X<'a>"#, 343 r#"fn my_fun<'other, 'a>(self, f: &'a Foo, b: &'other Bar) -> X<'a>"#,
313 ); 344 );
314 } 345 }
346
347 #[test]
348 fn test_function_add_lifetime_to_self_ref_mut() {
349 check_assist(
350 introduce_named_lifetime,
351 r#"fn foo(&mut self) -> &'_$0 ()"#,
352 r#"fn foo<'a>(&'a mut self) -> &'a ()"#,
353 );
354 }
315} 355}