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(&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 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(&self); + } + pub struct TestStruct {} + impl TestTrait for TestStruct { + fn test_method(&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(&self); + } + pub struct TestStruct {} + impl TestTrait for TestStruct { + fn test_method(&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), UnqualifiedName(Option), 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 @@ -1531,6 +1531,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( 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 { +pub fn item_for_path_search(db: &RootDatabase, item: ItemInNs) -> Option { Some(match item { ItemInNs::Types(_) | ItemInNs::Values(_) => match item_as_assoc(db, item) { Some(assoc_item) => match assoc_item.container(db) {