From 0e6d6033b5893cb3d38f96915eb35a4e1b211e7f Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Thu, 22 Jan 2026 19:04:36 +0530 Subject: [PATCH 1/2] migrate introduce_named_lifetime to SyntaxEditor --- .../src/handlers/introduce_named_lifetime.rs | 242 ++++++++++++------ 1 file changed, 157 insertions(+), 85 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/introduce_named_lifetime.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/introduce_named_lifetime.rs index 264e3767a232..76d47b9276b1 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/introduce_named_lifetime.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/introduce_named_lifetime.rs @@ -1,11 +1,11 @@ -use ide_db::FxHashSet; +use ide_db::{FileId, FxHashSet}; use syntax::{ - AstNode, TextRange, - ast::{self, HasGenericParams, edit_in_place::GenericParamsOwnerEdit, make}, - ted::{self, Position}, + AstNode, T, TextRange, + ast::{self, HasGenericParams, HasName, syntax_factory::SyntaxFactory}, + syntax_editor::{Element, Position, SyntaxEditor}, }; -use crate::{AssistContext, AssistId, Assists, assist_context::SourceChangeBuilder}; +use crate::{AssistContext, AssistId, Assists}; static ASSIST_NAME: &str = "introduce_named_lifetime"; static ASSIST_LABEL: &str = "Introduce named lifetime"; @@ -38,91 +38,23 @@ pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext<'_ // FIXME: should also add support for the case fun(f: &Foo) -> &$0Foo let lifetime = ctx.find_node_at_offset::().filter(|lifetime| lifetime.text() == "'_")?; + let file_id = ctx.vfs_file_id(); let lifetime_loc = lifetime.lifetime_ident_token()?.text_range(); if let Some(fn_def) = lifetime.syntax().ancestors().find_map(ast::Fn::cast) { - generate_fn_def_assist(acc, fn_def, lifetime_loc, lifetime) + generate_fn_def_assist(acc, fn_def, lifetime_loc, lifetime, file_id) } else if let Some(impl_def) = lifetime.syntax().ancestors().find_map(ast::Impl::cast) { - generate_impl_def_assist(acc, impl_def, lifetime_loc, lifetime) + generate_impl_def_assist(acc, impl_def, lifetime_loc, lifetime, file_id) } else { None } } -/// Generate the assist for the fn def case -fn generate_fn_def_assist( - acc: &mut Assists, - fn_def: ast::Fn, - lifetime_loc: TextRange, - lifetime: ast::Lifetime, -) -> Option<()> { - let param_list: ast::ParamList = fn_def.param_list()?; - let new_lifetime_param = generate_unique_lifetime_param_name(fn_def.generic_param_list())?; - let self_param = - // use the self if it's a reference and has no explicit lifetime - param_list.self_param().filter(|p| p.lifetime().is_none() && p.amp_token().is_some()); - // compute the location which implicitly has the same lifetime as the anonymous lifetime - let loc_needing_lifetime = if let Some(self_param) = self_param { - // if we have a self reference, use that - Some(NeedsLifetime::SelfParam(self_param)) - } else { - // otherwise, if there's a single reference parameter without a named lifetime, use that - let fn_params_without_lifetime: Vec<_> = param_list - .params() - .filter_map(|param| match param.ty() { - Some(ast::Type::RefType(ascribed_type)) if ascribed_type.lifetime().is_none() => { - Some(NeedsLifetime::RefType(ascribed_type)) - } - _ => None, - }) - .collect(); - match fn_params_without_lifetime.len() { - 1 => Some(fn_params_without_lifetime.into_iter().next()?), - 0 => None, - // multiple unnamed is invalid. assist is not applicable - _ => return None, - } - }; - acc.add(AssistId::refactor(ASSIST_NAME), ASSIST_LABEL, lifetime_loc, |builder| { - let fn_def = builder.make_mut(fn_def); - let lifetime = builder.make_mut(lifetime); - let loc_needing_lifetime = - loc_needing_lifetime.and_then(|it| it.make_mut(builder).to_position()); - - fn_def.get_or_create_generic_param_list().add_generic_param( - make::lifetime_param(new_lifetime_param.clone()).clone_for_update().into(), - ); - ted::replace(lifetime.syntax(), new_lifetime_param.clone_for_update().syntax()); - if let Some(position) = loc_needing_lifetime { - ted::insert(position, new_lifetime_param.clone_for_update().syntax()); - } - }) -} - -/// Generate the assist for the impl def case -fn generate_impl_def_assist( - acc: &mut Assists, - impl_def: ast::Impl, - lifetime_loc: TextRange, - lifetime: ast::Lifetime, -) -> Option<()> { - let new_lifetime_param = generate_unique_lifetime_param_name(impl_def.generic_param_list())?; - acc.add(AssistId::refactor(ASSIST_NAME), ASSIST_LABEL, lifetime_loc, |builder| { - let impl_def = builder.make_mut(impl_def); - let lifetime = builder.make_mut(lifetime); - - impl_def.get_or_create_generic_param_list().add_generic_param( - make::lifetime_param(new_lifetime_param.clone()).clone_for_update().into(), - ); - ted::replace(lifetime.syntax(), new_lifetime_param.clone_for_update().syntax()); - }) -} - /// Given a type parameter list, generate a unique lifetime parameter name /// which is not in the list fn generate_unique_lifetime_param_name( existing_type_param_list: Option, -) -> Option { +) -> Option { match existing_type_param_list { Some(type_params) => { let used_lifetime_params: FxHashSet<_> = @@ -131,7 +63,85 @@ fn generate_unique_lifetime_param_name( } None => Some("'a".to_owned()), } - .map(|it| make::lifetime(&it)) +} + +fn generate_fn_def_assist( + acc: &mut Assists, + fn_def: ast::Fn, + lifetime_loc: TextRange, + lifetime: ast::Lifetime, + file_id: FileId, +) -> Option<()> { + let param_list = fn_def.param_list()?; + let new_lifetime_name = generate_unique_lifetime_param_name(fn_def.generic_param_list())?; + let self_param = + param_list.self_param().filter(|p| p.lifetime().is_none() && p.amp_token().is_some()); + + let loc_needing_lifetime = if let Some(self_param) = self_param { + Some(NeedsLifetime::SelfParam(self_param)) + } else { + let unnamed_refs: Vec<_> = param_list + .params() + .filter_map(|param| match param.ty() { + Some(ast::Type::RefType(ref_type)) if ref_type.lifetime().is_none() => { + Some(NeedsLifetime::RefType(ref_type)) + } + _ => None, + }) + .collect(); + + match unnamed_refs.len() { + 1 => Some(unnamed_refs.into_iter().next()?), + 0 => None, + _ => return None, + } + }; + + acc.add(AssistId::refactor(ASSIST_NAME), ASSIST_LABEL, lifetime_loc, |edit| { + let root = fn_def.syntax().ancestors().last().unwrap().clone(); + let mut editor = SyntaxEditor::new(root); + let factory = SyntaxFactory::with_mappings(); + + if let Some(generic_list) = fn_def.generic_param_list() { + insert_lifetime_param(&mut editor, &factory, &generic_list, &new_lifetime_name); + } else { + insert_new_generic_param_list_fn(&mut editor, &factory, &fn_def, &new_lifetime_name); + } + + editor.replace(lifetime.syntax(), factory.lifetime(&new_lifetime_name).syntax()); + + if let Some(pos) = loc_needing_lifetime.and_then(|l| l.to_position()) { + editor.insert_all( + pos, + vec![ + factory.lifetime(&new_lifetime_name).syntax().clone().into(), + factory.whitespace(" ").into(), + ], + ); + } + + edit.add_file_edits(file_id, editor); + }) +} + +fn insert_new_generic_param_list_fn( + editor: &mut SyntaxEditor, + factory: &SyntaxFactory, + fn_def: &ast::Fn, + lifetime_name: &str, +) -> Option<()> { + let name = fn_def.name()?; + + editor.insert_all( + Position::after(name.syntax()), + vec![ + factory.token(T![<]).syntax_element(), + factory.lifetime(lifetime_name).syntax().syntax_element(), + factory.token(T![>]).syntax_element(), + ], + ); + + Some(()) } enum NeedsLifetime { @@ -140,13 +150,6 @@ enum NeedsLifetime { } impl NeedsLifetime { - fn make_mut(self, builder: &mut SourceChangeBuilder) -> Self { - match self { - Self::SelfParam(it) => Self::SelfParam(builder.make_mut(it)), - Self::RefType(it) => Self::RefType(builder.make_mut(it)), - } - } - fn to_position(self) -> Option { match self { Self::SelfParam(it) => Some(Position::after(it.amp_token()?)), @@ -155,6 +158,75 @@ impl NeedsLifetime { } } +fn generate_impl_def_assist( + acc: &mut Assists, + impl_def: ast::Impl, + lifetime_loc: TextRange, + lifetime: ast::Lifetime, + file_id: FileId, +) -> Option<()> { + let new_lifetime_name = generate_unique_lifetime_param_name(impl_def.generic_param_list())?; + + acc.add(AssistId::refactor(ASSIST_NAME), ASSIST_LABEL, lifetime_loc, |edit| { + let root = impl_def.syntax().ancestors().last().unwrap().clone(); + let mut editor = SyntaxEditor::new(root); + let factory = SyntaxFactory::without_mappings(); + + if let Some(generic_list) = impl_def.generic_param_list() { + insert_lifetime_param(&mut editor, &factory, &generic_list, &new_lifetime_name); + } else { + insert_new_generic_param_list_imp(&mut editor, &factory, &impl_def, &new_lifetime_name); + } + + editor.replace(lifetime.syntax(), factory.lifetime(&new_lifetime_name).syntax()); + + edit.add_file_edits(file_id, editor); + }) +} + +fn insert_new_generic_param_list_imp( + editor: &mut SyntaxEditor, + factory: &SyntaxFactory, + impl_: &ast::Impl, + lifetime_name: &str, +) -> Option<()> { + let impl_kw = impl_.impl_token()?; + + editor.insert_all( + Position::after(impl_kw), + vec![ + factory.token(T![<]).syntax_element(), + factory.lifetime(lifetime_name).syntax().syntax_element(), + factory.token(T![>]).syntax_element(), + ], + ); + + Some(()) +} + +fn insert_lifetime_param( + editor: &mut SyntaxEditor, + factory: &SyntaxFactory, + generic_list: &ast::GenericParamList, + lifetime_name: &str, +) -> Option<()> { + let r_angle = generic_list.r_angle_token()?; + let needs_comma = generic_list.generic_params().next().is_some(); + + let mut elements = Vec::new(); + + if needs_comma { + elements.push(factory.token(T![,]).syntax_element()); + elements.push(factory.whitespace(" ").syntax_element()); + } + + let lifetime = factory.lifetime(lifetime_name); + elements.push(lifetime.syntax().clone().into()); + + editor.insert_all(Position::before(r_angle), elements); + Some(()) +} + #[cfg(test)] mod tests { use super::*; From 9c7cc624069425e0acedc7bef2714bb174ccdc2f Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Tue, 10 Feb 2026 17:13:01 +0530 Subject: [PATCH 2/2] make generate_unique_lifetime_param_name return Option --- .../src/handlers/introduce_named_lifetime.rs | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/introduce_named_lifetime.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/introduce_named_lifetime.rs index 76d47b9276b1..854e9561d29a 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/introduce_named_lifetime.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/introduce_named_lifetime.rs @@ -1,7 +1,8 @@ use ide_db::{FileId, FxHashSet}; use syntax::{ - AstNode, T, TextRange, + AstNode, SmolStr, T, TextRange, ToSmolStr, ast::{self, HasGenericParams, HasName, syntax_factory::SyntaxFactory}, + format_smolstr, syntax_editor::{Element, Position, SyntaxEditor}, }; @@ -53,16 +54,14 @@ pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext<'_ /// Given a type parameter list, generate a unique lifetime parameter name /// which is not in the list fn generate_unique_lifetime_param_name( - existing_type_param_list: Option, -) -> Option { - match existing_type_param_list { - Some(type_params) => { - let used_lifetime_params: FxHashSet<_> = - type_params.lifetime_params().map(|p| p.syntax().text().to_string()).collect(); - ('a'..='z').map(|it| format!("'{it}")).find(|it| !used_lifetime_params.contains(it)) - } - None => Some("'a".to_owned()), - } + existing_params: Option, +) -> Option { + let used_lifetime_param: FxHashSet = existing_params + .iter() + .flat_map(|params| params.lifetime_params()) + .map(|p| p.syntax().text().to_smolstr()) + .collect(); + ('a'..='z').map(|c| format_smolstr!("'{c}")).find(|lt| !used_lifetime_param.contains(lt)) } fn generate_fn_def_assist(