Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide/src/rename.rs')
-rw-r--r--crates/ide/src/rename.rs253
1 files changed, 209 insertions, 44 deletions
diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs
index fb84e8e6b4..aea4ae0fd9 100644
--- a/crates/ide/src/rename.rs
+++ b/crates/ide/src/rename.rs
@@ -12,8 +12,12 @@ use ide_db::{
source_change::SourceChangeBuilder,
};
use itertools::Itertools;
-use stdx::{always, never};
-use syntax::{AstNode, SyntaxKind, SyntaxNode, TextRange, TextSize, ast};
+use std::fmt::Write;
+use stdx::{always, format_to, never};
+use syntax::{
+ AstNode, SyntaxKind, SyntaxNode, TextRange, TextSize,
+ ast::{self, HasArgList, prec::ExprPrecedence},
+};
use ide_db::text_edit::TextEdit;
@@ -34,13 +38,8 @@ pub(crate) fn prepare_rename(
let syntax = source_file.syntax();
let res = find_definitions(&sema, syntax, position, &Name::new_symbol_root(sym::underscore))?
- .map(|(frange, kind, def, _, _)| {
- // ensure all ranges are valid
-
- if def.range_for_rename(&sema).is_none() {
- bail!("No references found at position")
- }
-
+ .filter(|(_, _, def, _, _)| def.range_for_rename(&sema).is_some())
+ .map(|(frange, kind, _, _, _)| {
always!(
frange.range.contains_inclusive(position.offset)
&& frange.file_id == position.file_id
@@ -335,6 +334,85 @@ fn find_definitions(
}
}
+fn transform_assoc_fn_into_method_call(
+ sema: &Semantics<'_, RootDatabase>,
+ source_change: &mut SourceChange,
+ f: hir::Function,
+) {
+ let calls = Definition::Function(f).usages(sema).all();
+ for (file_id, calls) in calls {
+ for call in calls {
+ let Some(fn_name) = call.name.as_name_ref() else { continue };
+ let Some(path) = fn_name.syntax().parent().and_then(ast::PathSegment::cast) else {
+ continue;
+ };
+ let path = path.parent_path();
+ // The `PathExpr` is the direct parent, above it is the `CallExpr`.
+ let Some(call) =
+ path.syntax().parent().and_then(|it| ast::CallExpr::cast(it.parent()?))
+ else {
+ continue;
+ };
+
+ let Some(arg_list) = call.arg_list() else { continue };
+ let mut args = arg_list.args();
+ let Some(mut self_arg) = args.next() else { continue };
+ let second_arg = args.next();
+
+ // Strip (de)references, as they will be taken automatically by auto(de)ref.
+ loop {
+ let self_ = match &self_arg {
+ ast::Expr::RefExpr(self_) => self_.expr(),
+ ast::Expr::ParenExpr(self_) => self_.expr(),
+ ast::Expr::PrefixExpr(self_)
+ if self_.op_kind() == Some(ast::UnaryOp::Deref) =>
+ {
+ self_.expr()
+ }
+ _ => break,
+ };
+ self_arg = match self_ {
+ Some(it) => it,
+ None => break,
+ };
+ }
+
+ let self_needs_parens =
+ self_arg.precedence().needs_parentheses_in(ExprPrecedence::Postfix);
+
+ let replace_start = path.syntax().text_range().start();
+ let replace_end = match second_arg {
+ Some(second_arg) => second_arg.syntax().text_range().start(),
+ None => arg_list
+ .r_paren_token()
+ .map(|it| it.text_range().start())
+ .unwrap_or_else(|| arg_list.syntax().text_range().end()),
+ };
+ let replace_range = TextRange::new(replace_start, replace_end);
+
+ let Some(macro_mapped_self) = sema.original_range_opt(self_arg.syntax()) else {
+ continue;
+ };
+ let mut replacement = String::new();
+ if self_needs_parens {
+ replacement.push('(');
+ }
+ replacement.push_str(macro_mapped_self.text(sema.db));
+ if self_needs_parens {
+ replacement.push(')');
+ }
+ replacement.push('.');
+ format_to!(replacement, "{fn_name}");
+ replacement.push('(');
+
+ source_change.insert_source_edit(
+ file_id.file_id(sema.db),
+ TextEdit::replace(replace_range, replacement),
+ );
+ }
+ }
+}
+
fn rename_to_self(
sema: &Semantics<'_, RootDatabase>,
local: hir::Local,
@@ -412,6 +490,7 @@ fn rename_to_self(
file_id.original_file(sema.db).file_id(sema.db),
TextEdit::replace(param_source.syntax().text_range(), String::from(self_param)),
);
+ transform_assoc_fn_into_method_call(sema, &mut source_change, fn_def);
Ok(source_change)
}
@@ -459,35 +538,22 @@ fn rename_self_to_param(
}
fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: String) -> Option<TextEdit> {
- fn target_type_name(impl_def: &ast::Impl) -> Option<String> {
- if let Some(ast::Type::PathType(p)) = impl_def.self_ty() {
- return Some(p.path()?.segment()?.name_ref()?.text().to_string());
- }
- None
+ let mut replacement_text = new_name;
+ replacement_text.push_str(": ");
+
+ if self_param.amp_token().is_some() {
+ replacement_text.push('&');
+ }
+ if let Some(lifetime) = self_param.lifetime() {
+ write!(replacement_text, "{lifetime} ").unwrap();
+ }
+ if self_param.amp_token().and(self_param.mut_token()).is_some() {
+ replacement_text.push_str("mut ");
}
- match self_param.syntax().ancestors().find_map(ast::Impl::cast) {
- Some(impl_def) => {
- let type_name = target_type_name(&impl_def)?;
+ replacement_text.push_str("Self");
- let mut replacement_text = new_name;
- replacement_text.push_str(": ");
- match (self_param.amp_token(), self_param.mut_token()) {
- (Some(_), None) => replacement_text.push('&'),
- (Some(_), Some(_)) => replacement_text.push_str("&mut "),
- (_, _) => (),
- };
- replacement_text.push_str(type_name.as_str());
-
- Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text))
- }
- None => {
- cov_mark::hit!(rename_self_outside_of_methods);
- let mut replacement_text = new_name;
- replacement_text.push_str(": _");
- Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text))
- }
- }
+ Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text))
}
#[cfg(test)]
@@ -511,10 +577,10 @@ mod tests {
) {
let ra_fixture_after = &trim_indent(ra_fixture_after);
let (analysis, position) = fixture::position(ra_fixture_before);
- if !ra_fixture_after.starts_with("error: ") {
- if let Err(err) = analysis.prepare_rename(position).unwrap() {
- panic!("Prepare rename to '{new_name}' was failed: {err}")
- }
+ if !ra_fixture_after.starts_with("error: ")
+ && let Err(err) = analysis.prepare_rename(position).unwrap()
+ {
+ panic!("Prepare rename to '{new_name}' was failed: {err}")
}
let rename_result = analysis
.rename(position, new_name)
@@ -2069,7 +2135,7 @@ impl Foo {
struct Foo { i: i32 }
impl Foo {
- fn f(foo: &mut Foo) -> i32 {
+ fn f(foo: &mut Self) -> i32 {
foo.i
}
}
@@ -2095,7 +2161,33 @@ impl Foo {
struct Foo { i: i32 }
impl Foo {
- fn f(foo: Foo) -> i32 {
+ fn f(foo: Self) -> i32 {
+ foo.i
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_owned_self_to_parameter_with_lifetime() {
+ cov_mark::check!(rename_self_to_param);
+ check(
+ "foo",
+ r#"
+struct Foo<'a> { i: &'a i32 }
+
+impl<'a> Foo<'a> {
+ fn f(&'a $0self) -> i32 {
+ self.i
+ }
+}
+"#,
+ r#"
+struct Foo<'a> { i: &'a i32 }
+
+impl<'a> Foo<'a> {
+ fn f(foo: &'a Self) -> i32 {
foo.i
}
}
@@ -2105,7 +2197,6 @@ impl Foo {
#[test]
fn test_self_outside_of_methods() {
- cov_mark::check!(rename_self_outside_of_methods);
check(
"foo",
r#"
@@ -2114,7 +2205,7 @@ fn f($0self) -> i32 {
}
"#,
r#"
-fn f(foo: _) -> i32 {
+fn f(foo: Self) -> i32 {
foo.i
}
"#,
@@ -2159,7 +2250,7 @@ impl Foo {
struct Foo { i: i32 }
impl Foo {
- fn f(foo: &Foo) -> i32 {
+ fn f(foo: &Self) -> i32 {
let self_var = 1;
foo.i
}
@@ -3404,4 +3495,78 @@ fn other_place() { Quux::Bar$0; }
"#,
);
}
+
+ #[test]
+ fn rename_to_self_callers() {
+ check(
+ "self",
+ r#"
+//- minicore: add
+struct Foo;
+impl core::ops::Add for Foo {
+ type Target = Foo;
+ fn add(self, _: Self) -> Foo { Foo }
+}
+
+impl Foo {
+ fn foo(th$0is: &Self) {}
+}
+
+fn bar(v: &Foo) {
+ Foo::foo(v);
+}
+
+fn baz() {
+ Foo::foo(&Foo);
+ Foo::foo(Foo + Foo);
+}
+ "#,
+ r#"
+struct Foo;
+impl core::ops::Add for Foo {
+ type Target = Foo;
+ fn add(self, _: Self) -> Foo { Foo }
+}
+
+impl Foo {
+ fn foo(&self) {}
+}
+
+fn bar(v: &Foo) {
+ v.foo();
+}
+
+fn baz() {
+ Foo.foo();
+ (Foo + Foo).foo();
+}
+ "#,
+ );
+ // Multiple arguments:
+ check(
+ "self",
+ r#"
+struct Foo;
+
+impl Foo {
+ fn foo(th$0is: &Self, v: i32) {}
+}
+
+fn bar(v: Foo) {
+ Foo::foo(&v, 123);
+}
+ "#,
+ r#"
+struct Foo;
+
+impl Foo {
+ fn foo(&self, v: i32) {}
+}
+
+fn bar(v: Foo) {
+ v.foo(123);
+}
+ "#,
+ );
+ }
}