5846: Add references to fn args during completion r=matklad a=adamrk

When completing a function call, if there is an argument taken as a ref or mut ref which matches the name and type of a variable in scope, we will insert a `&` or `&mut` when filling in the function arguments. This addresses https://github.com/rust-analyzer/rust-analyzer/issues/5449.

E.g. 
```rust
fn foo(x: &i32) {}
fn main() {
  let x = 5;
  foo # completing foo here generates `foo(&x)` now instead of `foo(x)`
}
```

Co-authored-by: adamrk <ark.email@gmail.com>
This commit is contained in:
bors[bot] 2020-09-24 12:23:28 +00:00 committed by GitHub
commit 9d3483a74d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 161 additions and 8 deletions

View file

@ -709,11 +709,23 @@ impl Function {
} }
pub fn params(self, db: &dyn HirDatabase) -> Vec<Param> { pub fn params(self, db: &dyn HirDatabase) -> Vec<Param> {
let resolver = self.id.resolver(db.upcast());
let ctx = hir_ty::TyLoweringContext::new(db, &resolver);
let environment = TraitEnvironment::lower(db, &resolver);
db.function_data(self.id) db.function_data(self.id)
.params .params
.iter() .iter()
.skip(if self.self_param(db).is_some() { 1 } else { 0 }) .skip(if self.self_param(db).is_some() { 1 } else { 0 })
.map(|_| Param { _ty: () }) .map(|type_ref| {
let ty = Type {
krate: self.id.lookup(db.upcast()).container.module(db.upcast()).krate,
ty: InEnvironment {
value: Ty::from_hir_ext(&ctx, type_ref).0,
environment: environment.clone(),
},
};
Param { ty }
})
.collect() .collect()
} }
@ -742,15 +754,21 @@ impl From<Mutability> for Access {
} }
} }
pub struct Param {
ty: Type,
}
impl Param {
pub fn ty(&self) -> &Type {
&self.ty
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SelfParam { pub struct SelfParam {
func: FunctionId, func: FunctionId,
} }
pub struct Param {
_ty: (),
}
impl SelfParam { impl SelfParam {
pub fn access(self, db: &dyn HirDatabase) -> Access { pub fn access(self, db: &dyn HirDatabase) -> Access {
let func_data = db.function_data(self.func); let func_data = db.function_data(self.func);
@ -1276,6 +1294,14 @@ impl Type {
) )
} }
pub fn remove_ref(&self) -> Option<Type> {
if let Ty::Apply(ApplicationTy { ctor: TypeCtor::Ref(_), .. }) = self.ty.value {
self.ty.value.substs().map(|substs| self.derived(substs[0].clone()))
} else {
None
}
}
pub fn is_unknown(&self) -> bool { pub fn is_unknown(&self) -> bool {
matches!(self.ty.value, Ty::Unknown) matches!(self.ty.value, Ty::Unknown)
} }

View file

@ -1,7 +1,7 @@
//! FIXME: write short doc here //! FIXME: write short doc here
use base_db::SourceDatabase; use base_db::SourceDatabase;
use hir::{Semantics, SemanticsScope, Type}; use hir::{Local, ScopeDef, Semantics, SemanticsScope, Type};
use ide_db::RootDatabase; use ide_db::RootDatabase;
use syntax::{ use syntax::{
algo::{find_covering_element, find_node_at_offset}, algo::{find_covering_element, find_node_at_offset},
@ -91,6 +91,7 @@ pub(crate) struct CompletionContext<'a> {
pub(super) impl_as_prev_sibling: bool, pub(super) impl_as_prev_sibling: bool,
pub(super) is_match_arm: bool, pub(super) is_match_arm: bool,
pub(super) has_item_list_or_source_file_parent: bool, pub(super) has_item_list_or_source_file_parent: bool,
pub(super) locals: Vec<(String, Local)>,
} }
impl<'a> CompletionContext<'a> { impl<'a> CompletionContext<'a> {
@ -119,6 +120,12 @@ impl<'a> CompletionContext<'a> {
original_file.syntax().token_at_offset(position.offset).left_biased()?; original_file.syntax().token_at_offset(position.offset).left_biased()?;
let token = sema.descend_into_macros(original_token.clone()); let token = sema.descend_into_macros(original_token.clone());
let scope = sema.scope_at_offset(&token.parent(), position.offset); let scope = sema.scope_at_offset(&token.parent(), position.offset);
let mut locals = vec![];
scope.process_all_names(&mut |name, scope| {
if let ScopeDef::Local(local) = scope {
locals.push((name.to_string(), local));
}
});
let mut ctx = CompletionContext { let mut ctx = CompletionContext {
sema, sema,
scope, scope,
@ -167,6 +174,7 @@ impl<'a> CompletionContext<'a> {
if_is_prev: false, if_is_prev: false,
is_match_arm: false, is_match_arm: false,
has_item_list_or_source_file_parent: false, has_item_list_or_source_file_parent: false,
locals,
}; };
let mut original_file = original_file.syntax().clone(); let mut original_file = original_file.syntax().clone();

View file

@ -191,6 +191,17 @@ impl Completions {
func: hir::Function, func: hir::Function,
local_name: Option<String>, local_name: Option<String>,
) { ) {
fn add_arg(arg: &str, ty: &Type, ctx: &CompletionContext) -> String {
if let Some(derefed_ty) = ty.remove_ref() {
for (name, local) in ctx.locals.iter() {
if name == arg && local.ty(ctx.db) == derefed_ty {
return (if ty.is_mutable_reference() { "&mut " } else { "&" }).to_string()
+ &arg.to_string();
}
}
}
arg.to_string()
};
let name = local_name.unwrap_or_else(|| func.name(ctx.db).to_string()); let name = local_name.unwrap_or_else(|| func.name(ctx.db).to_string());
let ast_node = func.source(ctx.db).value; let ast_node = func.source(ctx.db).value;
@ -205,12 +216,20 @@ impl Completions {
.set_deprecated(is_deprecated(func, ctx.db)) .set_deprecated(is_deprecated(func, ctx.db))
.detail(function_declaration(&ast_node)); .detail(function_declaration(&ast_node));
let params_ty = func.params(ctx.db);
let params = ast_node let params = ast_node
.param_list() .param_list()
.into_iter() .into_iter()
.flat_map(|it| it.params()) .flat_map(|it| it.params())
.flat_map(|it| it.pat()) .zip(params_ty)
.map(|pat| pat.to_string().trim_start_matches('_').into()) .flat_map(|(it, param_ty)| {
if let Some(pat) = it.pat() {
let name = pat.to_string();
let arg = name.trim_start_matches("mut ").trim_start_matches('_');
return Some(add_arg(arg, param_ty.ty(), ctx));
}
None
})
.collect(); .collect();
builder = builder.add_call_parens(ctx, name, Params::Named(params)); builder = builder.add_call_parens(ctx, name, Params::Named(params));
@ -863,6 +882,106 @@ fn main() { foo(${1:foo}, ${2:bar}, ${3:ho_ge_})$0 }
); );
} }
#[test]
fn insert_ref_when_matching_local_in_scope() {
check_edit(
"ref_arg",
r#"
struct Foo {}
fn ref_arg(x: &Foo) {}
fn main() {
let x = Foo {};
ref_ar<|>
}
"#,
r#"
struct Foo {}
fn ref_arg(x: &Foo) {}
fn main() {
let x = Foo {};
ref_arg(${1:&x})$0
}
"#,
);
}
#[test]
fn insert_mut_ref_when_matching_local_in_scope() {
check_edit(
"ref_arg",
r#"
struct Foo {}
fn ref_arg(x: &mut Foo) {}
fn main() {
let x = Foo {};
ref_ar<|>
}
"#,
r#"
struct Foo {}
fn ref_arg(x: &mut Foo) {}
fn main() {
let x = Foo {};
ref_arg(${1:&mut x})$0
}
"#,
);
}
#[test]
fn insert_ref_when_matching_local_in_scope_for_method() {
check_edit(
"apply_foo",
r#"
struct Foo {}
struct Bar {}
impl Bar {
fn apply_foo(&self, x: &Foo) {}
}
fn main() {
let x = Foo {};
let y = Bar {};
y.<|>
}
"#,
r#"
struct Foo {}
struct Bar {}
impl Bar {
fn apply_foo(&self, x: &Foo) {}
}
fn main() {
let x = Foo {};
let y = Bar {};
y.apply_foo(${1:&x})$0
}
"#,
);
}
#[test]
fn trim_mut_keyword_in_func_completion() {
check_edit(
"take_mutably",
r#"
fn take_mutably(mut x: &i32) {}
fn main() {
take_m<|>
}
"#,
r#"
fn take_mutably(mut x: &i32) {}
fn main() {
take_mutably(${1:x})$0
}
"#,
);
}
#[test] #[test]
fn inserts_parens_for_tuple_enums() { fn inserts_parens_for_tuple_enums() {
mark::check!(inserts_parens_for_tuple_enums); mark::check!(inserts_parens_for_tuple_enums);