From bb00b09d22d4cd892ce7f4a605a2c8fae5d0095c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pawe=C5=82=20Palenica?= <pawelpalenica11@gmail.com>
Date: Wed, 20 Oct 2021 23:28:19 -0700
Subject: [PATCH] Add qualify method call assist

---
 .../src/handlers/qualify_method_call.rs       | 481 ++++++++++++++++++
 .../ide_assists/src/handlers/qualify_path.rs  |  45 +-
 crates/ide_assists/src/lib.rs                 |   2 +
 crates/ide_db/src/helpers/import_assets.rs    |   4 +-
 4 files changed, 515 insertions(+), 17 deletions(-)
 create mode 100644 crates/ide_assists/src/handlers/qualify_method_call.rs

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 00000000000..00b12411b08
--- /dev/null
+++ b/crates/ide_assists/src/handlers/qualify_method_call.rs
@@ -0,0 +1,481 @@
+use hir::{ItemInNs, ModuleDef};
+use ide_db::{assists::{AssistId, AssistKind}, helpers::import_assets::item_for_path_search};
+use syntax::{AstNode, ast};
+
+use crate::{assist_context::{AssistContext, Assists}, handlers::qualify_path::QualifyCandidate};
+
+// Assist: qualify_method_call
+//
+// If the name is resolvable, provides fully qualified path for it.
+//
+// ```
+// 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 call: ast::MethodCallExpr = ctx.find_node_at_offset()?;
+    let fn_name = &call.name_ref()?;
+    
+    // let callExpr = path_expr.syntax();
+    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 call `{}`", fn_name),
+        range,
+        |builder| {
+            qualify_candidate.qualify(
+                |replace_with: String| builder.replace(range, replace_with),
+                &receiver_path,
+                item_in_ns
+            )
+        }
+    );
+    Some(())
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::tests::{check_assist};
+    use super::*;
+
+    #[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)
+}
+"#,
+        );
+    }
+}
+
diff --git a/crates/ide_assists/src/handlers/qualify_path.rs b/crates/ide_assists/src/handlers/qualify_path.rs
index 0b33acc39be..ecfced83b14 100644
--- a/crates/ide_assists/src/handlers/qualify_path.rs
+++ b/crates/ide_assists/src/handlers/qualify_path.rs
@@ -1,10 +1,7 @@
 use std::iter;
 
 use hir::AsAssocItem;
-use ide_db::helpers::{
-    import_assets::{ImportCandidate, LocatedImport},
-    mod_path_to_ast,
-};
+use ide_db::helpers::{import_assets::{ImportCandidate, LocatedImport}, mod_path_to_ast};
 use ide_db::RootDatabase;
 use syntax::{
     ast,
@@ -37,6 +34,7 @@ use crate::{
 // ```
 pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
     let (import_assets, syntax_under_caret) = find_importable_node(ctx)?;
+
     let mut proposed_imports = import_assets.search_for_relative_paths(&ctx.sema);
     if proposed_imports.is_empty() {
         return None;
@@ -91,16 +89,17 @@ pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
     }
     Some(())
 }
-
-enum QualifyCandidate<'db> {
+#[derive(Debug)]
+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 +121,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 generics =
+        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 +149,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 +159,20 @@ 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);
+        Some(())
+    }
 }
 
 fn find_trait_method(
diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs
index dccd071dccf..2801c3c44bb 100644
--- a/crates/ide_assists/src/lib.rs
+++ b/crates/ide_assists/src/lib.rs
@@ -160,6 +160,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;
@@ -241,6 +242,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_db/src/helpers/import_assets.rs b/crates/ide_db/src/helpers/import_assets.rs
index d6bb19d1298..7170e14d848 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) {
@@ -619,6 +619,6 @@ fn path_import_candidate(
     })
 }
 
-fn item_as_assoc(db: &RootDatabase, item: ItemInNs) -> Option<AssocItem> {
+pub fn item_as_assoc(db: &RootDatabase, item: ItemInNs) -> Option<AssocItem> {
     item.as_module_def().and_then(|module_def| module_def.as_assoc_item(db))
 }