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)) }