Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--crates/ide/src/rename.rs94
-rw-r--r--crates/syntax/src/syntax_editor/edits.rs115
2 files changed, 157 insertions, 52 deletions
diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs
index 2c6116f745..6d1e141d21 100644
--- a/crates/ide/src/rename.rs
+++ b/crates/ide/src/rename.rs
@@ -16,7 +16,7 @@ use std::fmt::Write;
use stdx::{always, format_to, never};
use syntax::{
AstNode, SyntaxKind, SyntaxNode, TextRange, TextSize,
- ast::{self, HasArgList, prec::ExprPrecedence},
+ ast::{self, HasArgList, make, prec::ExprPrecedence},
};
use ide_db::text_edit::TextEdit;
@@ -79,7 +79,10 @@ pub(crate) fn prepare_rename(
let sema = Semantics::new(db);
let source_file = sema.parse_guess_edition(position.file_id);
let syntax = source_file.syntax();
-
+ if let Some(lifetime_token) = syntax.token_at_offset(position.offset).find(|t| t.text() == "'_")
+ {
+ return Ok(RangeInfo::new(lifetime_token.text_range(), ()));
+ }
let res = find_definitions(&sema, syntax, position, &Name::new_symbol_root(sym::underscore))?
.filter(|(_, _, def, _, _)| def.range_for_rename(&sema).is_some())
.map(|(frange, kind, _, _, _)| {
@@ -133,6 +136,13 @@ pub(crate) fn rename(
let edition = file_id.edition(db);
let (new_name, kind) = IdentifierKind::classify(edition, new_name)?;
+ if kind == IdentifierKind::Lifetime
+ && let Some(lifetime_token) =
+ syntax.token_at_offset(position.offset).find(|t| t.text() == "'_")
+ {
+ let new_name_str = new_name.display(db, edition).to_string();
+ return rename_elided_lifetime(position, lifetime_token, &new_name_str);
+ }
let defs = find_definitions(&sema, syntax, position, &new_name)?;
let alias_fallback =
@@ -797,6 +807,30 @@ fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: String) -> O
Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text))
}
+fn rename_elided_lifetime(
+ position: FilePosition,
+ lifetime_token: syntax::SyntaxToken,
+ new_name: &str,
+) -> RenameResult<SourceChange> {
+ let parent = lifetime_token.parent().unwrap();
+ let root = parent.ancestors().last().unwrap();
+
+ let mut builder = SourceChangeBuilder::new(position.file_id);
+
+ let editor = builder.make_editor(&root);
+
+ editor.replace(lifetime_token, make::lifetime(new_name).syntax().clone());
+
+ if let Some(has_generic_params) = parent.ancestors().find_map(ast::AnyHasGenericParams::cast) {
+ let lifetime_param = make::lifetime_param(make::lifetime(new_name));
+ editor.add_generic_param(&has_generic_params, lifetime_param.into());
+ }
+
+ builder.add_file_edits(position.file_id, editor);
+
+ Ok(builder.finish())
+}
+
#[cfg(test)]
mod tests {
use expect_test::{Expect, expect};
@@ -3989,4 +4023,60 @@ mod foo {
"#,
);
}
+
+ #[test]
+ fn test_rename_elided_lifetime_fn_no_generics() {
+ check(
+ "'a",
+ r#"
+fn foo(x: &'_$0 str) {}
+"#,
+ r#"
+fn foo<'a>(x: &'a str) {}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_rename_elided_lifetime_fn_with_generics() {
+ check(
+ "'a",
+ r#"
+fn foo<T>(x: &'_$0 str, y: T) {}
+"#,
+ r#"
+fn foo<'a, T>(x: &'a str, y: T) {}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_rename_elided_lifetime_impl_no_generics() {
+ check(
+ "'a",
+ r#"
+struct Foo<'a>(&'a str);
+impl Foo<'_$0> {}
+"#,
+ r#"
+struct Foo<'a>(&'a str);
+impl<'a> Foo<'a> {}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_rename_elided_lifetime_impl_with_generics() {
+ check(
+ "'a",
+ r#"
+struct Foo<'a, T>(&'a str, T);
+impl<T> Foo<'_$0, T> {}
+"#,
+ r#"
+struct Foo<'a, T>(&'a str, T);
+impl<'a, T> Foo<'a, T> {}
+"#,
+ );
+ }
}
diff --git a/crates/syntax/src/syntax_editor/edits.rs b/crates/syntax/src/syntax_editor/edits.rs
index 28e8ceed70..a684c0bfdb 100644
--- a/crates/syntax/src/syntax_editor/edits.rs
+++ b/crates/syntax/src/syntax_editor/edits.rs
@@ -3,7 +3,7 @@
use crate::{
AstToken, Direction, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, T,
algo::neighbor,
- ast::{self, AstNode, Fn, GenericParam, HasGenericParams, HasName, edit::IndentLevel, make},
+ ast::{self, AstNode, HasGenericParams, HasName, edit::IndentLevel, make},
syntax_editor::{Position, SyntaxEditor},
};
@@ -109,64 +109,79 @@ impl GetOrCreateWhereClause for ast::Enum {
}
impl SyntaxEditor {
- /// Adds a new generic param to the function using `SyntaxEditor`
- pub fn add_generic_param(&self, function: &Fn, new_param: GenericParam) {
- match function.generic_param_list() {
- Some(generic_param_list) => match generic_param_list.generic_params().last() {
- Some(last_param) => {
- // There exists a generic param list and it's not empty
- let position = generic_param_list.r_angle_token().map_or_else(
- || Position::last_child_of(function.syntax()),
- Position::before,
- );
-
- if last_param
- .syntax()
- .next_sibling_or_token()
- .is_some_and(|it| it.kind() == SyntaxKind::COMMA)
- {
- self.insert(
- Position::after(last_param.syntax()),
- new_param.syntax().clone(),
- );
- self.insert(
- Position::after(last_param.syntax()),
- make::token(SyntaxKind::WHITESPACE),
- );
- self.insert(
- Position::after(last_param.syntax()),
- make::token(SyntaxKind::COMMA),
- );
+ /// Adds a new generic param to the node using `SyntaxEditor`
+ pub fn add_generic_param(
+ &self,
+ node: &impl ast::HasGenericParams,
+ new_param: ast::GenericParam,
+ ) {
+ let make = self.make();
+ match node.generic_param_list() {
+ Some(generic_param_list) => {
+ let is_lifetime = matches!(new_param, ast::GenericParam::LifetimeParam(_));
+
+ if let Some(first_param) = generic_param_list.generic_params().next() {
+ let last_lifetime = generic_param_list
+ .generic_params()
+ .filter(|p| matches!(p, ast::GenericParam::LifetimeParam(_)))
+ .last();
+
+ if is_lifetime {
+ if let Some(last_lt) = last_lifetime {
+ let elements = vec![
+ make.token(SyntaxKind::COMMA).into(),
+ make.token(SyntaxKind::WHITESPACE).into(),
+ new_param.syntax().clone().into(),
+ ];
+ self.insert_all(Position::after(last_lt.syntax()), elements);
+ } else {
+ // Insert before the first parameter
+ let elements = vec![
+ new_param.syntax().clone().into(),
+ make.token(SyntaxKind::COMMA).into(),
+ make.token(SyntaxKind::WHITESPACE).into(),
+ ];
+ self.insert_all(Position::before(first_param.syntax()), elements);
+ }
} else {
+ let last_param = generic_param_list.generic_params().last().unwrap();
let elements = vec![
- make::token(SyntaxKind::COMMA).into(),
- make::token(SyntaxKind::WHITESPACE).into(),
+ make.token(SyntaxKind::COMMA).into(),
+ make.token(SyntaxKind::WHITESPACE).into(),
new_param.syntax().clone().into(),
];
- self.insert_all(position, elements);
+ self.insert_all(Position::after(last_param.syntax()), elements);
+ }
+ } else {
+ if let Some(l_angle) = generic_param_list.l_angle_token() {
+ self.insert(Position::after(l_angle), new_param.syntax().clone());
}
}
- None => {
- // There exists a generic param list but it's empty
- let position = Position::after(generic_param_list.l_angle_token().unwrap());
- self.insert(position, new_param.syntax());
- }
- },
+ }
None => {
- // There was no generic param list
- let position = if let Some(name) = function.name() {
- Position::after(name.syntax)
- } else if let Some(fn_token) = function.fn_token() {
- Position::after(fn_token)
- } else if let Some(param_list) = function.param_list() {
- Position::before(param_list.syntax)
- } else {
- Position::last_child_of(function.syntax())
- };
+ let position =
+ if let Some(name) = node.syntax().children().find_map(ast::Name::cast) {
+ Position::after(name.syntax())
+ } else if let Some(impl_node) = ast::Impl::cast(node.syntax().clone()) {
+ impl_node
+ .impl_token()
+ .map_or_else(|| Position::last_child_of(node.syntax()), Position::after)
+ } else if let Some(fn_node) = ast::Fn::cast(node.syntax().clone()) {
+ if let Some(fn_token) = fn_node.fn_token() {
+ Position::after(fn_token)
+ } else if let Some(param_list) = fn_node.param_list() {
+ Position::before(param_list.syntax())
+ } else {
+ Position::last_child_of(node.syntax())
+ }
+ } else {
+ Position::last_child_of(node.syntax())
+ };
+
let elements = vec![
- make::token(SyntaxKind::L_ANGLE).into(),
+ make.token(SyntaxKind::L_ANGLE).into(),
new_param.syntax().clone().into(),
- make::token(SyntaxKind::R_ANGLE).into(),
+ make.token(SyntaxKind::R_ANGLE).into(),
];
self.insert_all(position, elements);
}