Skip to content

Commit 0e81331

Browse files
committed
feat: allow renaming of elided lifetimes (Fixes #19260)
1 parent 33f097b commit 0e81331

1 file changed

Lines changed: 55 additions & 2 deletions

File tree

crates/ide/src/rename.rs

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use std::fmt::Write;
1616
use stdx::{always, format_to, never};
1717
use syntax::{
1818
AstNode, SyntaxKind, SyntaxNode, TextRange, TextSize,
19-
ast::{self, HasArgList, prec::ExprPrecedence},
19+
ast::{self, AnyHasGenericParams, HasArgList, HasGenericParams, HasName, prec::ExprPrecedence},
2020
};
2121

2222
use ide_db::text_edit::TextEdit;
@@ -79,7 +79,10 @@ pub(crate) fn prepare_rename(
7979
let sema = Semantics::new(db);
8080
let source_file = sema.parse_guess_edition(position.file_id);
8181
let syntax = source_file.syntax();
82-
82+
if let Some(lifetime_token) = syntax.token_at_offset(position.offset).find(|t| t.text() == "'_")
83+
{
84+
return Ok(RangeInfo::new(lifetime_token.text_range(), ()));
85+
}
8386
let res = find_definitions(&sema, syntax, position, &Name::new_symbol_root(sym::underscore))?
8487
.filter(|(_, _, def, _, _)| def.range_for_rename(&sema).is_some())
8588
.map(|(frange, kind, _, _, _)| {
@@ -133,6 +136,13 @@ pub(crate) fn rename(
133136

134137
let edition = file_id.edition(db);
135138
let (new_name, kind) = IdentifierKind::classify(edition, new_name)?;
139+
if kind == IdentifierKind::Lifetime
140+
&& let Some(lifetime_token) =
141+
syntax.token_at_offset(position.offset).find(|t| t.text() == "'_")
142+
{
143+
let new_name_str = new_name.display(db, edition).to_string();
144+
return rename_elided_lifetime(position, lifetime_token, &new_name_str);
145+
}
136146

137147
let defs = find_definitions(&sema, syntax, position, &new_name)?;
138148
let alias_fallback =
@@ -797,6 +807,49 @@ fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: String) -> O
797807
Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text))
798808
}
799809

810+
fn rename_elided_lifetime(
811+
position: FilePosition,
812+
lifetime_token: syntax::SyntaxToken,
813+
new_name: &str,
814+
) -> RenameResult<SourceChange> {
815+
let mut edit = ide_db::text_edit::TextEdit::builder();
816+
817+
// 1. Replace the `'_` with the new lifetime name
818+
edit.replace(lifetime_token.text_range(), new_name.to_owned());
819+
820+
// 2. Find the parent fn or impl using AnyHasGenericParams
821+
let parent = lifetime_token.parent().unwrap();
822+
if let Some(has_generic_params) = parent.ancestors().find_map(AnyHasGenericParams::cast) {
823+
if let Some(generic_list) = has_generic_params.generic_param_list() {
824+
// Generics list < > already exists, insert inside it
825+
if let Some(l_angle) = generic_list.l_angle_token() {
826+
let insert_text = if generic_list.generic_params().count() == 0 {
827+
new_name.to_owned()
828+
} else {
829+
format!("{}, ", new_name)
830+
};
831+
edit.insert(l_angle.text_range().end(), insert_text);
832+
}
833+
} else {
834+
// No generics exist yet, we must create the < >
835+
let syntax = has_generic_params.syntax();
836+
if let Some(fn_def) = ast::Fn::cast(syntax.clone())
837+
&& let Some(name) = fn_def.name()
838+
{
839+
edit.insert(name.syntax().text_range().end(), format!("<{}>", new_name));
840+
} else if let Some(impl_def) = ast::Impl::cast(syntax.clone())
841+
&& let Some(impl_kw) = impl_def.impl_token()
842+
{
843+
edit.insert(impl_kw.text_range().end(), format!("<{}>", new_name));
844+
}
845+
}
846+
}
847+
848+
let mut source_change = SourceChange::default();
849+
source_change.insert_source_edit(position.file_id, edit.finish());
850+
Ok(source_change)
851+
}
852+
800853
#[cfg(test)]
801854
mod tests {
802855
use expect_test::{Expect, expect};

0 commit comments

Comments
 (0)