1 //! Renderer for function calls.
3 use hir::{db::HirDatabase, AsAssocItem, HirDisplay};
4 use ide_db::{SnippetCap, SymbolKind};
5 use itertools::Itertools;
6 use stdx::{format_to, to_lower_snake_case};
7 use syntax::{AstNode, SmolStr};
10 context::{CompletionContext, DotAccess, DotAccessKind, PathCompletionCtx, PathKind},
11 item::{Builder, CompletionItem, CompletionItemKind, CompletionRelevance},
12 render::{compute_exact_name_match, compute_ref_match, compute_type_match, RenderContext},
18 Function(&'ctx PathCompletionCtx),
19 Method(&'ctx DotAccess, Option<hir::Name>),
22 pub(crate) fn render_fn(
23 ctx: RenderContext<'_>,
24 path_ctx: &PathCompletionCtx,
25 local_name: Option<hir::Name>,
28 let _p = profile::span("render_fn");
29 render(ctx, local_name, func, FuncKind::Function(path_ctx))
32 pub(crate) fn render_method(
33 ctx: RenderContext<'_>,
34 dot_access: &DotAccess,
35 receiver: Option<hir::Name>,
36 local_name: Option<hir::Name>,
39 let _p = profile::span("render_method");
40 render(ctx, local_name, func, FuncKind::Method(dot_access, receiver))
44 ctx @ RenderContext { completion, .. }: RenderContext<'_>,
45 local_name: Option<hir::Name>,
47 func_kind: FuncKind<'_>,
49 let db = completion.db;
51 let name = local_name.unwrap_or_else(|| func.name(db));
53 let (call, escaped_call) = match &func_kind {
54 FuncKind::Method(_, Some(receiver)) => (
55 format!("{}.{}", receiver, &name).into(),
56 format!("{}.{}", receiver.escaped(), name.escaped()).into(),
58 _ => (name.to_smol_str(), name.escaped().to_smol_str()),
60 let mut item = CompletionItem::new(
61 if func.self_param(db).is_some() {
62 CompletionItemKind::Method
64 CompletionItemKind::SymbolKind(SymbolKind::Function)
70 let ret_type = func.ret_type(db);
71 let is_op_method = func
72 .as_assoc_item(ctx.db())
73 .and_then(|trait_| trait_.containing_trait_or_trait_impl(ctx.db()))
74 .map_or(false, |trait_| completion.is_ops_trait(trait_));
75 item.set_relevance(CompletionRelevance {
76 type_match: compute_type_match(completion, &ret_type),
77 exact_name_match: compute_exact_name_match(completion, &call),
79 ..ctx.completion_relevance()
82 if let Some(ref_match) = compute_ref_match(completion, &ret_type) {
84 FuncKind::Function(path_ctx) => {
85 item.ref_match(ref_match, path_ctx.path.syntax().text_range().start());
87 FuncKind::Method(DotAccess { receiver: Some(receiver), .. }, _) => {
88 item.ref_match(ref_match, receiver.syntax().text_range().start());
94 item.set_documentation(ctx.docs(func))
95 .set_deprecated(ctx.is_deprecated(func) || ctx.is_deprecated_assoc_item(func))
96 .detail(detail(db, func))
97 .lookup_by(name.to_smol_str());
99 match ctx.completion.config.snippet_cap {
101 let complete_params = match func_kind {
102 FuncKind::Function(PathCompletionCtx {
103 kind: PathKind::Expr { .. },
104 has_call_parens: false,
110 DotAccessKind::Method { has_parens: false } | DotAccessKind::Field { .. },
117 if let Some(has_dot_receiver) = complete_params {
118 if let Some((self_param, params)) =
119 params(ctx.completion, func, &func_kind, has_dot_receiver)
136 match ctx.import_to_add {
137 Some(import_to_add) => {
138 item.add_import(import_to_add);
141 if let Some(actm) = func.as_assoc_item(db) {
142 if let Some(trt) = actm.containing_trait_or_trait_impl(db) {
143 item.trait_name(trt.name(db).to_smol_str());
151 pub(super) fn add_call_parens<'b>(
152 builder: &'b mut Builder,
153 ctx: &CompletionContext<'_>,
156 escaped_name: SmolStr,
157 self_param: Option<hir::SelfParam>,
158 params: Vec<hir::Param>,
159 ) -> &'b mut Builder {
160 cov_mark::hit!(inserts_parens_for_function_calls);
162 let (snippet, label_suffix) = if self_param.is_none() && params.is_empty() {
163 (format!("{}()$0", escaped_name), "()")
165 builder.trigger_call_info();
166 let snippet = if let Some(CallableSnippets::FillArguments) = ctx.config.callable {
167 let offset = if self_param.is_some() { 2 } else { 1 };
168 let function_params_snippet =
169 params.iter().enumerate().format_with(", ", |(index, param), f| {
170 match param.name(ctx.db) {
172 let smol_str = n.to_smol_str();
173 let text = smol_str.as_str().trim_start_matches('_');
174 let ref_ = ref_of_param(ctx, text, param.ty());
175 f(&format_args!("${{{}:{}{}}}", index + offset, ref_, text))
178 let name = match param.ty().as_adt() {
179 None => "_".to_string(),
183 .map(|s| to_lower_snake_case(s.as_str()))
184 .unwrap_or_else(|| "_".to_string()),
186 f(&format_args!("${{{}:{}}}", index + offset, name))
191 Some(self_param) => {
193 "{}(${{1:{}}}{}{})$0",
195 self_param.display(ctx.db),
196 if params.is_empty() { "" } else { ", " },
197 function_params_snippet
201 format!("{}({})$0", escaped_name, function_params_snippet)
205 cov_mark::hit!(suppress_arg_snippets);
206 format!("{}($0)", escaped_name)
211 builder.label(SmolStr::from_iter([&name, label_suffix])).insert_snippet(cap, snippet)
214 fn ref_of_param(ctx: &CompletionContext<'_>, arg: &str, ty: &hir::Type) -> &'static str {
215 if let Some(derefed_ty) = ty.remove_ref() {
216 for (name, local) in ctx.locals.iter() {
217 if name.as_text().as_deref() == Some(arg) {
218 return if local.ty(ctx.db) == derefed_ty {
219 if ty.is_mutable_reference() {
233 fn detail(db: &dyn HirDatabase, func: hir::Function) -> String {
234 let mut ret_ty = func.ret_type(db);
235 let mut detail = String::new();
237 if func.is_const(db) {
238 format_to!(detail, "const ");
240 if func.is_async(db) {
241 format_to!(detail, "async ");
242 if let Some(async_ret) = func.async_ret_type(db) {
246 if func.is_unsafe_to_call(db) {
247 format_to!(detail, "unsafe ");
250 format_to!(detail, "fn({})", params_display(db, func));
251 if !ret_ty.is_unit() {
252 format_to!(detail, " -> {}", ret_ty.display(db));
257 fn params_display(db: &dyn HirDatabase, func: hir::Function) -> String {
258 if let Some(self_param) = func.self_param(db) {
259 let assoc_fn_params = func.assoc_fn_params(db);
260 let params = assoc_fn_params
262 .skip(1) // skip the self param because we are manually handling that
263 .map(|p| p.ty().display(db));
266 self_param.display(db),
267 params.format_with("", |display, f| {
273 let assoc_fn_params = func.assoc_fn_params(db);
274 assoc_fn_params.iter().map(|p| p.ty().display(db)).join(", ")
279 ctx: &CompletionContext<'_>,
281 func_kind: &FuncKind<'_>,
282 has_dot_receiver: bool,
283 ) -> Option<(Option<hir::SelfParam>, Vec<hir::Param>)> {
284 if ctx.config.callable.is_none() {
288 // Don't add parentheses if the expected type is some function reference.
289 if let Some(ty) = &ctx.expected_type {
290 // FIXME: check signature matches?
292 cov_mark::hit!(no_call_parens_if_fn_ptr_needed);
297 let self_param = if has_dot_receiver || matches!(func_kind, FuncKind::Method(_, Some(_))) {
300 func.self_param(ctx.db)
302 Some((self_param, func.params_without_self(ctx.db)))
308 tests::{check_edit, check_edit_with_config, TEST_CONFIG},
309 CallableSnippets, CompletionConfig,
313 fn inserts_parens_for_function_calls() {
314 cov_mark::check!(inserts_parens_for_function_calls);
323 fn main() { no_args()$0 }
330 fn with_args(x: i32, y: String) {}
331 fn main() { with_$0 }
334 fn with_args(x: i32, y: String) {}
335 fn main() { with_args(${1:x}, ${2:y})$0 }
346 fn bar(s: &S) { s.f$0 }
353 fn bar(s: &S) { s.foo()$0 }
362 fn foo(&self, x: i32) {}
371 fn foo(&self, x: i32) {}
384 fn foo(&self, x: i32) {
392 fn foo(&self, x: i32) {
401 fn parens_for_method_call_as_assoc_fn() {
416 fn main() { S::foo(${1:&self})$0 }
422 fn suppress_arg_snippets() {
423 cov_mark::check!(suppress_arg_snippets);
424 check_edit_with_config(
425 CompletionConfig { callable: Some(CallableSnippets::AddParentheses), ..TEST_CONFIG },
428 fn with_args(x: i32, y: String) {}
429 fn main() { with_$0 }
432 fn with_args(x: i32, y: String) {}
433 fn main() { with_args($0) }
439 fn strips_underscores_from_args() {
443 fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {}
447 fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {}
448 fn main() { foo(${1:foo}, ${2:bar}, ${3:ho_ge_})$0 }
454 fn insert_ref_when_matching_local_in_scope() {
459 fn ref_arg(x: &Foo) {}
467 fn ref_arg(x: &Foo) {}
477 fn insert_mut_ref_when_matching_local_in_scope() {
482 fn ref_arg(x: &mut Foo) {}
490 fn ref_arg(x: &mut Foo) {}
493 ref_arg(${1:&mut x})$0
500 fn insert_ref_when_matching_local_in_scope_for_method() {
507 fn apply_foo(&self, x: &Foo) {}
520 fn apply_foo(&self, x: &Foo) {}
526 y.apply_foo(${1:&x})$0
533 fn trim_mut_keyword_in_func_completion() {
537 fn take_mutably(mut x: &i32) {}
544 fn take_mutably(mut x: &i32) {}
547 take_mutably(${1:x})$0
554 fn complete_pattern_args_with_type_name_if_adt() {
562 fn qux(Foo { bar }: Foo) {
575 fn qux(Foo { bar }: Foo) {
587 fn complete_fn_param() {
592 fn f(foo: (), mut bar: u32) {}
593 fn g(foo: (), mut ba$0)
596 fn f(foo: (), mut bar: u32) {}
597 fn g(foo: (), mut bar: u32)
605 fn g(foo: (), mut ba$0: u32)
606 fn f(foo: (), mut bar: u32) {}
609 fn g(foo: (), mut bar: u32)
610 fn f(foo: (), mut bar: u32) {}
616 fn complete_fn_mut_param_add_comma() {
617 // add leading and trailing comma
621 fn f(foo: (), mut bar: u32) {}
622 fn g(foo: ()mut ba$0 baz: ())
625 fn f(foo: (), mut bar: u32) {}
626 fn g(foo: (), mut bar: u32, baz: ())
632 fn complete_fn_mut_param_has_attribute() {
634 r#"#[baz = "qux"] mut bar: u32"#,
636 fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
637 fn g(foo: (), mut ba$0)
640 fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
641 fn g(foo: (), #[baz = "qux"] mut bar: u32)
646 r#"#[baz = "qux"] mut bar: u32"#,
648 fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
649 fn g(foo: (), #[baz = "qux"] mut ba$0)
652 fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
653 fn g(foo: (), #[baz = "qux"] mut bar: u32)
658 r#", #[baz = "qux"] mut bar: u32"#,
660 fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
661 fn g(foo: ()#[baz = "qux"] mut ba$0)
664 fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
665 fn g(foo: (), #[baz = "qux"] mut bar: u32)