Unnamed repository; edit this file 'description' to name the repository.
Merge #10602
10602: Add qualify method call assist r=Veykril a=qepasa This adds `qualify_method_call` assist that allows to replace a method (or trait) call that resolves with its fully qualified path. For example, for stuct method: ```rust struct Foo; impl Foo { fn foo(&self) {} } ``` ``` let foo = Foo {}; foo.fo$0o(); ``` becomes ```rust let foo = Foo {}; Foo::foo(&foo); ``` for a trait method: ```rust struct Foo; trait FooTrait { fn foo(&self) {} } impl FooTrait for Foo { fn foo(&self) {} } ``` following call: ```rust let foo = Foo {}; foo.fo$0o(); ``` becomes: ```rust let foo = Foo {}; FooTrait::foo(&foo); ``` fixes #10453 Co-authored-by: PaweÅ‚ Palenica <[email protected]>
bors[bot] 2021-10-23
parent a75353e · parent bfc86f6 · commit fe7c516
-rw-r--r--crates/ide_assists/src/handlers/qualify_method_call.rs531
-rw-r--r--crates/ide_assists/src/handlers/qualify_path.rs35
-rw-r--r--crates/ide_assists/src/lib.rs2
-rw-r--r--crates/ide_assists/src/tests/generated.rs27
-rw-r--r--crates/ide_db/src/helpers/import_assets.rs2
5 files changed, 586 insertions, 11 deletions
diff --git a/crates/ide_assists/src/handlers/qualify_method_call.rs b/crates/ide_assists/src/handlers/qualify_method_call.rs
new file mode 100644
index 0000000000..43878879ed
--- /dev/null
+++ b/crates/ide_assists/src/handlers/qualify_method_call.rs
@@ -0,0 +1,531 @@
+use hir::{ItemInNs, ModuleDef};
+use ide_db::{
+ assists::{AssistId, AssistKind},
+ helpers::import_assets::item_for_path_search,
+};
+use syntax::{ast, AstNode};
+
+use crate::{
+ assist_context::{AssistContext, Assists},
+ handlers::qualify_path::QualifyCandidate,
+};
+
+// Assist: qualify_method_call
+//
+// Replaces the method call with a qualified function call.
+//
+// ```
+// struct Foo;
+// impl Foo {
+// fn foo(&self) {}
+// }
+// fn main() {
+// let foo = Foo;
+// foo.fo$0o();
+// }
+// ```
+// ->
+// ```
+// struct Foo;
+// impl Foo {
+// fn foo(&self) {}
+// }
+// fn main() {
+// let foo = Foo;
+// Foo::foo(&foo);
+// }
+// ```
+pub(crate) fn qualify_method_call(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
+ let name: ast::NameRef = ctx.find_node_at_offset()?;
+ let call = name.syntax().parent().and_then(ast::MethodCallExpr::cast)?;
+
+ let ident = name.ident_token()?;
+
+ let range = call.syntax().text_range();
+ let resolved_call = ctx.sema.resolve_method_call(&call)?;
+
+ let current_module = ctx.sema.scope(&call.syntax()).module()?;
+ let target_module_def = ModuleDef::from(resolved_call);
+ let item_in_ns = ItemInNs::from(target_module_def);
+ let receiver_path = current_module
+ .find_use_path(ctx.sema.db, item_for_path_search(ctx.sema.db, item_in_ns)?)?;
+
+ let qualify_candidate = QualifyCandidate::ImplMethod(ctx.sema.db, call, resolved_call);
+
+ acc.add(
+ AssistId("qualify_method_call", AssistKind::RefactorInline),
+ format!("Qualify `{}` method call", ident.text()),
+ range,
+ |builder| {
+ qualify_candidate.qualify(
+ |replace_with: String| builder.replace(range, replace_with),
+ &receiver_path,
+ item_in_ns,
+ )
+ },
+ );
+ Some(())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ #[test]
+ fn struct_method() {
+ check_assist(
+ qualify_method_call,
+ r#"
+struct Foo;
+impl Foo {
+ fn foo(&self) {}
+}
+
+fn main() {
+ let foo = Foo {};
+ foo.fo$0o()
+}
+"#,
+ r#"
+struct Foo;
+impl Foo {
+ fn foo(&self) {}
+}
+
+fn main() {
+ let foo = Foo {};
+ Foo::foo(&foo)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn struct_method_multi_params() {
+ check_assist(
+ qualify_method_call,
+ r#"
+struct Foo;
+impl Foo {
+ fn foo(&self, p1: i32, p2: u32) {}
+}
+
+fn main() {
+ let foo = Foo {};
+ foo.fo$0o(9, 9u)
+}
+"#,
+ r#"
+struct Foo;
+impl Foo {
+ fn foo(&self, p1: i32, p2: u32) {}
+}
+
+fn main() {
+ let foo = Foo {};
+ Foo::foo(&foo, 9, 9u)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn struct_method_consume() {
+ check_assist(
+ qualify_method_call,
+ r#"
+struct Foo;
+impl Foo {
+ fn foo(self, p1: i32, p2: u32) {}
+}
+
+fn main() {
+ let foo = Foo {};
+ foo.fo$0o(9, 9u)
+}
+"#,
+ r#"
+struct Foo;
+impl Foo {
+ fn foo(self, p1: i32, p2: u32) {}
+}
+
+fn main() {
+ let foo = Foo {};
+ Foo::foo(foo, 9, 9u)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn struct_method_exclusive() {
+ check_assist(
+ qualify_method_call,
+ r#"
+struct Foo;
+impl Foo {
+ fn foo(&mut self, p1: i32, p2: u32) {}
+}
+
+fn main() {
+ let foo = Foo {};
+ foo.fo$0o(9, 9u)
+}
+"#,
+ r#"
+struct Foo;
+impl Foo {
+ fn foo(&mut self, p1: i32, p2: u32) {}
+}
+
+fn main() {
+ let foo = Foo {};
+ Foo::foo(&mut foo, 9, 9u)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn struct_method_cross_crate() {
+ check_assist(
+ qualify_method_call,
+ r#"
+//- /main.rs crate:main deps:dep
+fn main() {
+ let foo = dep::test_mod::Foo {};
+ foo.fo$0o(9, 9u)
+}
+//- /dep.rs crate:dep
+pub mod test_mod {
+ pub struct Foo;
+ impl Foo {
+ pub fn foo(&mut self, p1: i32, p2: u32) {}
+ }
+}
+"#,
+ r#"
+fn main() {
+ let foo = dep::test_mod::Foo {};
+ dep::test_mod::Foo::foo(&mut foo, 9, 9u)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn struct_method_generic() {
+ check_assist(
+ qualify_method_call,
+ r#"
+struct Foo;
+impl Foo {
+ fn foo<T>(&self) {}
+}
+
+fn main() {
+ let foo = Foo {};
+ foo.fo$0o::<()>()
+}
+"#,
+ r#"
+struct Foo;
+impl Foo {
+ fn foo<T>(&self) {}
+}
+
+fn main() {
+ let foo = Foo {};
+ Foo::foo::<()>(&foo)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn trait_method() {
+ check_assist(
+ qualify_method_call,
+ r#"
+mod test_mod {
+ pub trait TestTrait {
+ fn test_method(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method(&self) {}
+ }
+}
+
+use test_mod::*;
+
+fn main() {
+ let test_struct = test_mod::TestStruct {};
+ test_struct.test_meth$0od()
+}
+"#,
+ r#"
+mod test_mod {
+ pub trait TestTrait {
+ fn test_method(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method(&self) {}
+ }
+}
+
+use test_mod::*;
+
+fn main() {
+ let test_struct = test_mod::TestStruct {};
+ TestTrait::test_method(&test_struct)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn trait_method_multi_params() {
+ check_assist(
+ qualify_method_call,
+ r#"
+mod test_mod {
+ pub trait TestTrait {
+ fn test_method(&self, p1: i32, p2: u32);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method(&self, p1: i32, p2: u32) {}
+ }
+}
+
+use test_mod::*;
+
+fn main() {
+ let test_struct = test_mod::TestStruct {};
+ test_struct.test_meth$0od(12, 32u)
+}
+"#,
+ r#"
+mod test_mod {
+ pub trait TestTrait {
+ fn test_method(&self, p1: i32, p2: u32);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method(&self, p1: i32, p2: u32) {}
+ }
+}
+
+use test_mod::*;
+
+fn main() {
+ let test_struct = test_mod::TestStruct {};
+ TestTrait::test_method(&test_struct, 12, 32u)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn trait_method_consume() {
+ check_assist(
+ qualify_method_call,
+ r#"
+mod test_mod {
+ pub trait TestTrait {
+ fn test_method(self, p1: i32, p2: u32);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method(self, p1: i32, p2: u32) {}
+ }
+}
+
+use test_mod::*;
+
+fn main() {
+ let test_struct = test_mod::TestStruct {};
+ test_struct.test_meth$0od(12, 32u)
+}
+"#,
+ r#"
+mod test_mod {
+ pub trait TestTrait {
+ fn test_method(self, p1: i32, p2: u32);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method(self, p1: i32, p2: u32) {}
+ }
+}
+
+use test_mod::*;
+
+fn main() {
+ let test_struct = test_mod::TestStruct {};
+ TestTrait::test_method(test_struct, 12, 32u)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn trait_method_exclusive() {
+ check_assist(
+ qualify_method_call,
+ r#"
+mod test_mod {
+ pub trait TestTrait {
+ fn test_method(&mut self, p1: i32, p2: u32);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method(&mut self, p1: i32, p2: u32);
+ }
+}
+
+use test_mod::*;
+
+fn main() {
+ let test_struct = test_mod::TestStruct {};
+ test_struct.test_meth$0od(12, 32u)
+}
+"#,
+ r#"
+mod test_mod {
+ pub trait TestTrait {
+ fn test_method(&mut self, p1: i32, p2: u32);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method(&mut self, p1: i32, p2: u32);
+ }
+}
+
+use test_mod::*;
+
+fn main() {
+ let test_struct = test_mod::TestStruct {};
+ TestTrait::test_method(&mut test_struct, 12, 32u)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn trait_method_cross_crate() {
+ check_assist(
+ qualify_method_call,
+ r#"
+//- /main.rs crate:main deps:dep
+fn main() {
+ let foo = dep::test_mod::Foo {};
+ foo.fo$0o(9, 9u)
+}
+//- /dep.rs crate:dep
+pub mod test_mod {
+ pub struct Foo;
+ impl Foo {
+ pub fn foo(&mut self, p1: i32, p2: u32) {}
+ }
+}
+"#,
+ r#"
+fn main() {
+ let foo = dep::test_mod::Foo {};
+ dep::test_mod::Foo::foo(&mut foo, 9, 9u)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn trait_method_generic() {
+ check_assist(
+ qualify_method_call,
+ r#"
+mod test_mod {
+ pub trait TestTrait {
+ fn test_method<T>(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method<T>(&self) {}
+ }
+}
+
+use test_mod::*;
+
+fn main() {
+ let test_struct = TestStruct {};
+ test_struct.test_meth$0od::<()>()
+}
+"#,
+ r#"
+mod test_mod {
+ pub trait TestTrait {
+ fn test_method<T>(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method<T>(&self) {}
+ }
+}
+
+use test_mod::*;
+
+fn main() {
+ let test_struct = TestStruct {};
+ TestTrait::test_method::<()>(&test_struct)
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn struct_method_over_stuct_instance() {
+ check_assist_not_applicable(
+ qualify_method_call,
+ r#"
+struct Foo;
+impl Foo {
+ fn foo(&self) {}
+}
+
+fn main() {
+ let foo = Foo {};
+ f$0oo.foo()
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn trait_method_over_stuct_instance() {
+ check_assist_not_applicable(
+ qualify_method_call,
+ r#"
+mod test_mod {
+ pub trait TestTrait {
+ fn test_method(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn test_method(&self) {}
+ }
+}
+
+use test_mod::*;
+
+fn main() {
+ let test_struct = test_mod::TestStruct {};
+ tes$0t_struct.test_method()
+}
+"#,
+ );
+ }
+}
diff --git a/crates/ide_assists/src/handlers/qualify_path.rs b/crates/ide_assists/src/handlers/qualify_path.rs
index 0b33acc39b..29f2c785bc 100644
--- a/crates/ide_assists/src/handlers/qualify_path.rs
+++ b/crates/ide_assists/src/handlers/qualify_path.rs
@@ -91,16 +91,16 @@ pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
}
Some(())
}
-
-enum QualifyCandidate<'db> {
+pub(crate) enum QualifyCandidate<'db> {
QualifierStart(ast::PathSegment, Option<ast::GenericArgList>),
UnqualifiedName(Option<ast::GenericArgList>),
TraitAssocItem(ast::Path, ast::PathSegment),
TraitMethod(&'db RootDatabase, ast::MethodCallExpr),
+ ImplMethod(&'db RootDatabase, ast::MethodCallExpr, hir::Function),
}
impl QualifyCandidate<'_> {
- fn qualify(
+ pub(crate) fn qualify(
&self,
mut replacer: impl FnMut(String),
import: &hir::ModPath,
@@ -122,24 +122,26 @@ impl QualifyCandidate<'_> {
QualifyCandidate::TraitMethod(db, mcall_expr) => {
Self::qualify_trait_method(db, mcall_expr, replacer, import, item);
}
+ QualifyCandidate::ImplMethod(db, mcall_expr, hir_fn) => {
+ Self::qualify_fn_call(db, mcall_expr, replacer, import, hir_fn);
+ }
}
}
- fn qualify_trait_method(
+ fn qualify_fn_call(
db: &RootDatabase,
mcall_expr: &ast::MethodCallExpr,
mut replacer: impl FnMut(String),
import: ast::Path,
- item: hir::ItemInNs,
+ hir_fn: &hir::Function,
) -> Option<()> {
let receiver = mcall_expr.receiver()?;
- let trait_method_name = mcall_expr.name_ref()?;
+ let method_name = mcall_expr.name_ref()?;
let generics =
mcall_expr.generic_arg_list().as_ref().map_or_else(String::new, ToString::to_string);
let arg_list = mcall_expr.arg_list().map(|arg_list| arg_list.args());
- let trait_ = item_as_trait(db, item)?;
- let method = find_trait_method(db, trait_, &trait_method_name)?;
- if let Some(self_access) = method.self_param(db).map(|sp| sp.access(db)) {
+
+ if let Some(self_access) = hir_fn.self_param(db).map(|sp| sp.access(db)) {
let receiver = match self_access {
hir::Access::Shared => make::expr_ref(receiver, false),
hir::Access::Exclusive => make::expr_ref(receiver, true),
@@ -148,7 +150,7 @@ impl QualifyCandidate<'_> {
replacer(format!(
"{}::{}{}{}",
import,
- trait_method_name,
+ method_name,
generics,
match arg_list {
Some(args) => make::arg_list(iter::once(receiver).chain(args)),
@@ -158,6 +160,19 @@ impl QualifyCandidate<'_> {
}
Some(())
}
+
+ fn qualify_trait_method(
+ db: &RootDatabase,
+ mcall_expr: &ast::MethodCallExpr,
+ replacer: impl FnMut(String),
+ import: ast::Path,
+ item: hir::ItemInNs,
+ ) -> Option<()> {
+ let trait_method_name = mcall_expr.name_ref()?;
+ let trait_ = item_as_trait(db, item)?;
+ let method = find_trait_method(db, trait_, &trait_method_name)?;
+ Self::qualify_fn_call(db, mcall_expr, replacer, import, &method)
+ }
}
fn find_trait_method(
diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs
index e477be8cab..8f9ae6c154 100644
--- a/crates/ide_assists/src/lib.rs
+++ b/crates/ide_assists/src/lib.rs
@@ -161,6 +161,7 @@ mod handlers {
mod promote_local_to_const;
mod pull_assignment_up;
mod qualify_path;
+ mod qualify_method_call;
mod raw_string;
mod remove_dbg;
mod remove_mut;
@@ -242,6 +243,7 @@ mod handlers {
pull_assignment_up::pull_assignment_up,
promote_local_to_const::promote_local_to_const,
qualify_path::qualify_path,
+ qualify_method_call::qualify_method_call,
raw_string::add_hash,
raw_string::make_usual_string,
raw_string::remove_hash,
diff --git a/crates/ide_assists/src/tests/generated.rs b/crates/ide_assists/src/tests/generated.rs
index 7778d96aa6..43d30e84b3 100644
--- a/crates/ide_assists/src/tests/generated.rs
+++ b/crates/ide_assists/src/tests/generated.rs
@@ -1532,6 +1532,33 @@ fn main() {
}
#[test]
+fn doctest_qualify_method_call() {
+ check_doc_test(
+ "qualify_method_call",
+ r#####"
+struct Foo;
+impl Foo {
+ fn foo(&self) {}
+}
+fn main() {
+ let foo = Foo;
+ foo.fo$0o();
+}
+"#####,
+ r#####"
+struct Foo;
+impl Foo {
+ fn foo(&self) {}
+}
+fn main() {
+ let foo = Foo;
+ Foo::foo(&foo);
+}
+"#####,
+ )
+}
+
+#[test]
fn doctest_qualify_path() {
check_doc_test(
"qualify_path",
diff --git a/crates/ide_db/src/helpers/import_assets.rs b/crates/ide_db/src/helpers/import_assets.rs
index d6bb19d129..f1e8ee4935 100644
--- a/crates/ide_db/src/helpers/import_assets.rs
+++ b/crates/ide_db/src/helpers/import_assets.rs
@@ -372,7 +372,7 @@ fn import_for_item(
})
}
-fn item_for_path_search(db: &RootDatabase, item: ItemInNs) -> Option<ItemInNs> {
+pub fn item_for_path_search(db: &RootDatabase, item: ItemInNs) -> Option<ItemInNs> {
Some(match item {
ItemInNs::Types(_) | ItemInNs::Values(_) => match item_as_assoc(db, item) {
Some(assoc_item) => match assoc_item.container(db) {