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.rs178
1 files changed, 165 insertions, 13 deletions
diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs
index a07c647c2c..aea4ae0fd9 100644
--- a/crates/ide/src/rename.rs
+++ b/crates/ide/src/rename.rs
@@ -13,8 +13,11 @@ use ide_db::{
};
use itertools::Itertools;
use std::fmt::Write;
-use stdx::{always, never};
-use syntax::{AstNode, SyntaxKind, SyntaxNode, TextRange, TextSize, ast};
+use stdx::{always, format_to, never};
+use syntax::{
+ AstNode, SyntaxKind, SyntaxNode, TextRange, TextSize,
+ ast::{self, HasArgList, prec::ExprPrecedence},
+};
use ide_db::text_edit::TextEdit;
@@ -35,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
@@ -336,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,
@@ -413,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)
}
@@ -499,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)
@@ -3417,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);
+}
+ "#,
+ );
+ }
}