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 <pawelpalenica11@gmail.com>
This commit is contained in:
bors[bot] 2021-10-23 08:34:51 +00:00 committed by GitHub
commit fe7c516084
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 586 additions and 11 deletions

View file

@ -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()
}
"#,
);
}
}

View file

@ -91,16 +91,16 @@ pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
} }
Some(()) Some(())
} }
pub(crate) enum QualifyCandidate<'db> {
enum QualifyCandidate<'db> {
QualifierStart(ast::PathSegment, Option<ast::GenericArgList>), QualifierStart(ast::PathSegment, Option<ast::GenericArgList>),
UnqualifiedName(Option<ast::GenericArgList>), UnqualifiedName(Option<ast::GenericArgList>),
TraitAssocItem(ast::Path, ast::PathSegment), TraitAssocItem(ast::Path, ast::PathSegment),
TraitMethod(&'db RootDatabase, ast::MethodCallExpr), TraitMethod(&'db RootDatabase, ast::MethodCallExpr),
ImplMethod(&'db RootDatabase, ast::MethodCallExpr, hir::Function),
} }
impl QualifyCandidate<'_> { impl QualifyCandidate<'_> {
fn qualify( pub(crate) fn qualify(
&self, &self,
mut replacer: impl FnMut(String), mut replacer: impl FnMut(String),
import: &hir::ModPath, import: &hir::ModPath,
@ -122,24 +122,26 @@ impl QualifyCandidate<'_> {
QualifyCandidate::TraitMethod(db, mcall_expr) => { QualifyCandidate::TraitMethod(db, mcall_expr) => {
Self::qualify_trait_method(db, mcall_expr, replacer, import, item); 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, db: &RootDatabase,
mcall_expr: &ast::MethodCallExpr, mcall_expr: &ast::MethodCallExpr,
mut replacer: impl FnMut(String), mut replacer: impl FnMut(String),
import: ast::Path, import: ast::Path,
item: hir::ItemInNs, hir_fn: &hir::Function,
) -> Option<()> { ) -> Option<()> {
let receiver = mcall_expr.receiver()?; let receiver = mcall_expr.receiver()?;
let trait_method_name = mcall_expr.name_ref()?; let method_name = mcall_expr.name_ref()?;
let generics = let generics =
mcall_expr.generic_arg_list().as_ref().map_or_else(String::new, ToString::to_string); 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 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) = hir_fn.self_param(db).map(|sp| sp.access(db)) {
if let Some(self_access) = method.self_param(db).map(|sp| sp.access(db)) {
let receiver = match self_access { let receiver = match self_access {
hir::Access::Shared => make::expr_ref(receiver, false), hir::Access::Shared => make::expr_ref(receiver, false),
hir::Access::Exclusive => make::expr_ref(receiver, true), hir::Access::Exclusive => make::expr_ref(receiver, true),
@ -148,7 +150,7 @@ impl QualifyCandidate<'_> {
replacer(format!( replacer(format!(
"{}::{}{}{}", "{}::{}{}{}",
import, import,
trait_method_name, method_name,
generics, generics,
match arg_list { match arg_list {
Some(args) => make::arg_list(iter::once(receiver).chain(args)), Some(args) => make::arg_list(iter::once(receiver).chain(args)),
@ -158,6 +160,19 @@ impl QualifyCandidate<'_> {
} }
Some(()) 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( fn find_trait_method(

View file

@ -161,6 +161,7 @@ mod handlers {
mod promote_local_to_const; mod promote_local_to_const;
mod pull_assignment_up; mod pull_assignment_up;
mod qualify_path; mod qualify_path;
mod qualify_method_call;
mod raw_string; mod raw_string;
mod remove_dbg; mod remove_dbg;
mod remove_mut; mod remove_mut;
@ -242,6 +243,7 @@ mod handlers {
pull_assignment_up::pull_assignment_up, pull_assignment_up::pull_assignment_up,
promote_local_to_const::promote_local_to_const, promote_local_to_const::promote_local_to_const,
qualify_path::qualify_path, qualify_path::qualify_path,
qualify_method_call::qualify_method_call,
raw_string::add_hash, raw_string::add_hash,
raw_string::make_usual_string, raw_string::make_usual_string,
raw_string::remove_hash, raw_string::remove_hash,

View file

@ -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] #[test]
fn doctest_qualify_path() { fn doctest_qualify_path() {
check_doc_test( check_doc_test(

View file

@ -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 { Some(match item {
ItemInNs::Types(_) | ItemInNs::Values(_) => match item_as_assoc(db, item) { ItemInNs::Types(_) | ItemInNs::Values(_) => match item_as_assoc(db, item) {
Some(assoc_item) => match assoc_item.container(db) { Some(assoc_item) => match assoc_item.container(db) {