//! Renderer for function calls. use hir::{db::HirDatabase, AsAssocItem, HirDisplay}; use ide_db::{SnippetCap, SymbolKind}; use itertools::Itertools; use stdx::{format_to, to_lower_snake_case}; use syntax::SmolStr; use crate::{ context::{CompletionContext, PathCompletionCtx, PathKind}, item::{Builder, CompletionItem, CompletionItemKind, CompletionRelevance}, patterns::ImmediateLocation, render::{compute_exact_name_match, compute_ref_match, compute_type_match, RenderContext}, }; enum FuncKind { Function, Method(Option), } pub(crate) fn render_fn( ctx: RenderContext<'_>, local_name: Option, func: hir::Function, ) -> Builder { let _p = profile::span("render_fn"); render(ctx, local_name, func, FuncKind::Function) } pub(crate) fn render_method( ctx: RenderContext<'_>, receiver: Option, local_name: Option, func: hir::Function, ) -> Builder { let _p = profile::span("render_method"); render(ctx, local_name, func, FuncKind::Method(receiver)) } fn render( ctx @ RenderContext { completion, .. }: RenderContext<'_>, local_name: Option, func: hir::Function, func_kind: FuncKind, ) -> Builder { let db = completion.db; let name = local_name.unwrap_or_else(|| func.name(db)); let call = match &func_kind { FuncKind::Method(Some(receiver)) => format!("{}.{}", receiver, &name).into(), _ => name.to_smol_str(), }; let mut item = CompletionItem::new( if func.self_param(db).is_some() { CompletionItemKind::Method } else { CompletionItemKind::SymbolKind(SymbolKind::Function) }, ctx.source_range(), call.clone(), ); let ret_type = func.ret_type(db); let is_op_method = func .as_assoc_item(ctx.db()) .and_then(|trait_| trait_.containing_trait_or_trait_impl(ctx.db())) .map_or(false, |trait_| completion.is_ops_trait(trait_)); item.set_relevance(CompletionRelevance { type_match: compute_type_match(completion, &ret_type), exact_name_match: compute_exact_name_match(completion, &call), is_op_method, ..ctx.completion_relevance() }); if let Some(ref_match) = compute_ref_match(completion, &ret_type) { // FIXME For now we don't properly calculate the edits for ref match // completions on methods or qualified paths, so we've disabled them. // See #8058. if matches!(func_kind, FuncKind::Function) && ctx.completion.path_qual().is_none() { item.ref_match(ref_match); } } item.set_documentation(ctx.docs(func)) .set_deprecated(ctx.is_deprecated(func) || ctx.is_deprecated_assoc_item(func)) .detail(detail(db, func)) .lookup_by(name.to_smol_str()); match completion.config.snippet_cap { Some(cap) if should_add_parens(completion) => { let (self_param, params) = params(completion, func, &func_kind); add_call_parens(&mut item, completion, cap, call, self_param, params); } _ => (), } match ctx.import_to_add { Some(import_to_add) => { item.add_import(import_to_add); } None => { if let Some(actm) = func.as_assoc_item(db) { if let Some(trt) = actm.containing_trait_or_trait_impl(db) { item.trait_name(trt.name(db).to_smol_str()); } } } } item } pub(super) fn add_call_parens<'b>( builder: &'b mut Builder, ctx: &CompletionContext, cap: SnippetCap, name: SmolStr, self_param: Option, params: Vec, ) -> &'b mut Builder { cov_mark::hit!(inserts_parens_for_function_calls); let (snippet, label_suffix) = if self_param.is_none() && params.is_empty() { (format!("{}()$0", name), "()") } else { builder.trigger_call_info(); let snippet = if ctx.config.add_call_argument_snippets { let offset = if self_param.is_some() { 2 } else { 1 }; let function_params_snippet = params.iter().enumerate().format_with(", ", |(index, param), f| { match param.name(ctx.db) { Some(n) => { let smol_str = n.to_smol_str(); let text = smol_str.as_str().trim_start_matches('_'); let ref_ = ref_of_param(ctx, text, param.ty()); f(&format_args!("${{{}:{}{}}}", index + offset, ref_, text)) } None => { let name = match param.ty().as_adt() { None => "_".to_string(), Some(adt) => adt .name(ctx.db) .as_text() .map(|s| to_lower_snake_case(s.as_str())) .unwrap_or_else(|| "_".to_string()), }; f(&format_args!("${{{}:{}}}", index + offset, name)) } } }); match self_param { Some(self_param) => { format!( "{}(${{1:{}}}{}{})$0", name, self_param.display(ctx.db), if params.is_empty() { "" } else { ", " }, function_params_snippet ) } None => { format!("{}({})$0", name, function_params_snippet) } } } else { cov_mark::hit!(suppress_arg_snippets); format!("{}($0)", name) }; (snippet, "(…)") }; builder.label(SmolStr::from_iter([&name, label_suffix])).insert_snippet(cap, snippet) } fn ref_of_param(ctx: &CompletionContext, arg: &str, ty: &hir::Type) -> &'static str { if let Some(derefed_ty) = ty.remove_ref() { for (name, local) in ctx.locals.iter() { if name.as_text().as_deref() == Some(arg) { return if local.ty(ctx.db) == derefed_ty { if ty.is_mutable_reference() { "&mut " } else { "&" } } else { "" }; } } } "" } fn should_add_parens(ctx: &CompletionContext) -> bool { if !ctx.config.add_call_parenthesis { return false; } match ctx.path_context { Some(PathCompletionCtx { kind: Some(PathKind::Expr), has_call_parens: true, .. }) => { return false } Some(PathCompletionCtx { kind: Some(PathKind::Use | PathKind::Type), .. }) => { cov_mark::hit!(no_parens_in_use_item); return false; } _ => {} }; if matches!( ctx.completion_location, Some(ImmediateLocation::MethodCall { has_parens: true, .. }) ) { return false; } // Don't add parentheses if the expected type is some function reference. if let Some(ty) = &ctx.expected_type { // FIXME: check signature matches? if ty.is_fn() { cov_mark::hit!(no_call_parens_if_fn_ptr_needed); return false; } } // Nothing prevents us from adding parentheses true } fn detail(db: &dyn HirDatabase, func: hir::Function) -> String { let mut ret_ty = func.ret_type(db); let mut detail = String::new(); if func.is_const(db) { format_to!(detail, "const "); } if func.is_async(db) { format_to!(detail, "async "); if let Some(async_ret) = func.async_ret_type(db) { ret_ty = async_ret; } } if func.is_unsafe_to_call(db) { format_to!(detail, "unsafe "); } format_to!(detail, "fn({})", params_display(db, func)); if !ret_ty.is_unit() { format_to!(detail, " -> {}", ret_ty.display(db)); } detail } fn params_display(db: &dyn HirDatabase, func: hir::Function) -> String { if let Some(self_param) = func.self_param(db) { let assoc_fn_params = func.assoc_fn_params(db); let params = assoc_fn_params .iter() .skip(1) // skip the self param because we are manually handling that .map(|p| p.ty().display(db)); format!( "{}{}", self_param.display(db), params.format_with("", |display, f| { f(&", ")?; f(&display) }) ) } else { let assoc_fn_params = func.assoc_fn_params(db); assoc_fn_params.iter().map(|p| p.ty().display(db)).join(", ") } } fn params( ctx: &CompletionContext<'_>, func: hir::Function, func_kind: &FuncKind, ) -> (Option, Vec) { let self_param = if ctx.has_dot_receiver() || matches!(func_kind, FuncKind::Method(Some(_))) { None } else { func.self_param(ctx.db) }; (self_param, func.params_without_self(ctx.db)) } #[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 } "#, ); } #[test] fn complete_pattern_args_with_type_name_if_adt() { check_edit( "qux", r#" struct Foo { bar: i32 } fn qux(Foo { bar }: Foo) { println!("{}", bar); } fn main() { qu$0 } "#, r#" struct Foo { bar: i32 } fn qux(Foo { bar }: Foo) { println!("{}", bar); } fn main() { qux(${1:foo})$0 } "#, ); } #[test] fn complete_fn_param() { // has mut kw check_edit( "mut bar", r#" fn f(foo: (), mut bar: u32) {} fn g(foo: (), mut ba$0) "#, r#" fn f(foo: (), mut bar: u32) {} fn g(foo: (), mut bar: u32) "#, ); // has type param check_edit( "mut bar", r#" fn g(foo: (), mut ba$0: u32) fn f(foo: (), mut bar: u32) {} "#, r#" fn g(foo: (), mut bar: u32) fn f(foo: (), mut bar: u32) {} "#, ); } #[test] fn complete_fn_mut_param_add_comma() { // add leading and trailing comma check_edit( "mut bar", r#" fn f(foo: (), mut bar: u32) {} fn g(foo: ()mut ba$0 baz: ()) "#, r#" fn f(foo: (), mut bar: u32) {} fn g(foo: (), mut bar: u32, baz: ()) "#, ); } #[test] fn complete_fn_mut_param_has_attribute() { check_edit( "mut bar", r#" fn f(foo: (), #[baz = "qux"] mut bar: u32) {} fn g(foo: (), mut ba$0) "#, r#" fn f(foo: (), #[baz = "qux"] mut bar: u32) {} fn g(foo: (), #[baz = "qux"] mut bar: u32) "#, ); check_edit( "mut bar", r#" fn f(foo: (), #[baz = "qux"] mut bar: u32) {} fn g(foo: (), #[baz = "qux"] mut ba$0) "#, r#" fn f(foo: (), #[baz = "qux"] mut bar: u32) {} fn g(foo: (), #[baz = "qux"] mut bar: u32) "#, ); } }