//! Renderer for function calls. use either::Either; use hir::{AsAssocItem, HasSource, HirDisplay}; use ide_db::SymbolKind; use itertools::Itertools; use syntax::ast; use crate::{ item::{CompletionItem, CompletionItemKind, CompletionRelevance, ImportEdit}, render::{ builder_ext::Params, compute_exact_name_match, compute_ref_match, compute_type_match, RenderContext, }, }; pub(crate) fn render_fn( ctx: RenderContext<'_>, import_to_add: Option, local_name: Option, fn_: hir::Function, ) -> Option { let _p = profile::span("render_fn"); Some(FunctionRender::new(ctx, None, local_name, fn_, false)?.render(import_to_add)) } pub(crate) fn render_method( ctx: RenderContext<'_>, import_to_add: Option, receiver: Option, local_name: Option, fn_: hir::Function, ) -> Option { let _p = profile::span("render_method"); Some(FunctionRender::new(ctx, receiver, local_name, fn_, true)?.render(import_to_add)) } #[derive(Debug)] struct FunctionRender<'a> { ctx: RenderContext<'a>, name: String, receiver: Option, func: hir::Function, /// NB: having `ast::Fn` here might or might not be a good idea. The problem /// with it is that, to get an `ast::`, you want to parse the corresponding /// source file. So, when flyimport completions suggest a bunch of /// functions, we spend quite some time parsing many files. /// /// We need ast because we want to access parameter names (patterns). We can /// add them to the hir of the function itself, but parameter names are not /// something hir cares otherwise. /// /// Alternatively we can reconstruct params from the function body, but that /// would require parsing anyway. /// /// It seems that just using `ast` is the best choice -- most of parses /// should be cached anyway. ast_node: ast::Fn, is_method: bool, } impl<'a> FunctionRender<'a> { fn new( ctx: RenderContext<'a>, receiver: Option, local_name: Option, fn_: hir::Function, is_method: bool, ) -> Option> { let name = local_name.unwrap_or_else(|| fn_.name(ctx.db())).to_string(); let ast_node = fn_.source(ctx.db())?.value; Some(FunctionRender { ctx, name, receiver, func: fn_, ast_node, is_method }) } fn render(self, import_to_add: Option) -> CompletionItem { let params = self.params(); let call = match &self.receiver { Some(receiver) => format!("{}.{}", receiver, &self.name), None => self.name.clone(), }; let mut item = CompletionItem::new(self.kind(), self.ctx.source_range(), call.clone()); item.set_documentation(self.ctx.docs(self.func)) .set_deprecated( self.ctx.is_deprecated(self.func) || self.ctx.is_deprecated_assoc_item(self.func), ) .detail(self.detail()) .add_call_parens(self.ctx.completion, call.clone(), params); if import_to_add.is_none() { let db = self.ctx.db(); if let Some(actm) = self.func.as_assoc_item(db) { if let Some(trt) = actm.containing_trait_or_trait_impl(db) { item.trait_name(trt.name(db).to_string()); } } } if let Some(import_to_add) = import_to_add { item.add_import(import_to_add); } item.lookup_by(self.name); let ret_type = self.func.ret_type(self.ctx.db()); item.set_relevance(CompletionRelevance { type_match: compute_type_match(self.ctx.completion, &ret_type), exact_name_match: compute_exact_name_match(self.ctx.completion, &call), ..CompletionRelevance::default() }); if let Some(ref_match) = compute_ref_match(self.ctx.completion, &ret_type) { // FIXME // For now we don't properly calculate the edits for ref match // completions on methods, so we've disabled them. See #8058. if !self.is_method { item.ref_match(ref_match); } } item.build() } fn detail(&self) -> String { let ret_ty = self.func.ret_type(self.ctx.db()); let ret = if ret_ty.is_unit() { // Omit the return type if it is the unit type String::new() } else { format!(" {}", self.ty_display()) }; format!("fn({}){}", self.params_display(), ret) } fn params_display(&self) -> String { if let Some(self_param) = self.func.self_param(self.ctx.db()) { let params = self .func .assoc_fn_params(self.ctx.db()) .into_iter() .skip(1) // skip the self param because we are manually handling that .map(|p| p.ty().display(self.ctx.db()).to_string()); std::iter::once(self_param.display(self.ctx.db()).to_owned()).chain(params).join(", ") } else { let params = self .func .assoc_fn_params(self.ctx.db()) .into_iter() .map(|p| p.ty().display(self.ctx.db()).to_string()) .join(", "); params } } fn ty_display(&self) -> String { let ret_ty = self.func.ret_type(self.ctx.db()); format!("-> {}", ret_ty.display(self.ctx.db())) } fn params(&self) -> Params { let ast_params = match self.ast_node.param_list() { Some(it) => it, None => return Params::Named(Vec::new()), }; let params = ast_params.params().map(Either::Right); let params = if self.ctx.completion.has_dot_receiver() || self.receiver.is_some() { params.zip(self.func.method_params(self.ctx.db()).unwrap_or_default()).collect() } else { ast_params .self_param() .map(Either::Left) .into_iter() .chain(params) .zip(self.func.assoc_fn_params(self.ctx.db())) .collect() }; Params::Named(params) } fn kind(&self) -> CompletionItemKind { if self.func.self_param(self.ctx.db()).is_some() { CompletionItemKind::Method } else { SymbolKind::Function.into() } } } #[cfg(test)] mod tests { use crate::{ tests::{check_edit, check_edit_with_config, TEST_CONFIG}, CompletionConfig, }; #[test] fn inserts_parens_for_function_calls() { cov_mark::check!(inserts_parens_for_function_calls); check_edit( "no_args", r#" fn no_args() {} fn main() { no_$0 } "#, r#" fn no_args() {} fn main() { no_args()$0 } "#, ); check_edit( "with_args", r#" fn with_args(x: i32, y: String) {} fn main() { with_$0 } "#, r#" fn with_args(x: i32, y: String) {} fn main() { with_args(${1:x}, ${2:y})$0 } "#, ); check_edit( "foo", r#" struct S; impl S { fn foo(&self) {} } fn bar(s: &S) { s.f$0 } "#, r#" struct S; impl S { fn foo(&self) {} } fn bar(s: &S) { s.foo()$0 } "#, ); check_edit( "foo", r#" struct S {} impl S { fn foo(&self, x: i32) {} } fn bar(s: &S) { s.f$0 } "#, r#" struct S {} impl S { fn foo(&self, x: i32) {} } fn bar(s: &S) { s.foo(${1:x})$0 } "#, ); check_edit( "foo", r#" struct S {} impl S { fn foo(&self, x: i32) { $0 } } "#, r#" struct S {} impl S { fn foo(&self, x: i32) { self.foo(${1:x})$0 } } "#, ); } #[test] fn parens_for_method_call_as_assoc_fn() { check_edit( "foo", r#" struct S; impl S { fn foo(&self) {} } fn main() { S::f$0 } "#, r#" struct S; impl S { fn foo(&self) {} } fn main() { S::foo(${1:&self})$0 } "#, ); } #[test] fn suppress_arg_snippets() { cov_mark::check!(suppress_arg_snippets); check_edit_with_config( CompletionConfig { add_call_argument_snippets: false, ..TEST_CONFIG }, "with_args", r#" fn with_args(x: i32, y: String) {} fn main() { with_$0 } "#, r#" fn with_args(x: i32, y: String) {} fn main() { with_args($0) } "#, ); } #[test] fn strips_underscores_from_args() { check_edit( "foo", r#" fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {} fn main() { f$0 } "#, r#" fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {} 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$0 } "#, 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$0 } "#, 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.$0 } "#, 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$0 } "#, r#" fn take_mutably(mut x: &i32) {} fn main() { take_mutably(${1:x})$0 } "#, ); } }