Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide_completion/src/render/function.rs')
-rw-r--r--crates/ide_completion/src/render/function.rs173
1 files changed, 140 insertions, 33 deletions
diff --git a/crates/ide_completion/src/render/function.rs b/crates/ide_completion/src/render/function.rs
index c1908ba0c8..2b9f82fc54 100644
--- a/crates/ide_completion/src/render/function.rs
+++ b/crates/ide_completion/src/render/function.rs
@@ -1,20 +1,19 @@
//! Renderer for function calls.
use hir::{db::HirDatabase, AsAssocItem, HirDisplay};
-use ide_db::SymbolKind;
+use ide_db::{SnippetCap, SymbolKind};
use itertools::Itertools;
use stdx::format_to;
+use syntax::SmolStr;
use crate::{
- context::CompletionContext,
- item::{CompletionItem, CompletionItemKind, CompletionRelevance, ImportEdit},
- render::{
- builder_ext::Params, compute_exact_name_match, compute_ref_match, compute_type_match,
- RenderContext,
- },
+ context::{CompletionContext, PathCompletionCtx, PathKind},
+ item::{Builder, CompletionItem, CompletionItemKind, CompletionRelevance, ImportEdit},
+ patterns::ImmediateLocation,
+ render::{compute_exact_name_match, compute_ref_match, compute_type_match, RenderContext},
};
-enum FuncType {
+enum FuncKind {
Function,
Method(Option<hir::Name>),
}
@@ -26,7 +25,7 @@ pub(crate) fn render_fn(
func: hir::Function,
) -> CompletionItem {
let _p = profile::span("render_fn");
- render(ctx, local_name, func, FuncType::Function, import_to_add)
+ render(ctx, local_name, func, FuncKind::Function, import_to_add)
}
pub(crate) fn render_method(
@@ -37,23 +36,22 @@ pub(crate) fn render_method(
func: hir::Function,
) -> CompletionItem {
let _p = profile::span("render_method");
- render(ctx, local_name, func, FuncType::Method(receiver), import_to_add)
+ render(ctx, local_name, func, FuncKind::Method(receiver), import_to_add)
}
fn render(
ctx @ RenderContext { completion, .. }: RenderContext<'_>,
local_name: Option<hir::Name>,
func: hir::Function,
- func_type: FuncType,
+ func_kind: FuncKind,
import_to_add: Option<ImportEdit>,
) -> CompletionItem {
let db = completion.db;
let name = local_name.unwrap_or_else(|| func.name(db));
- let params = params(completion, func, &func_type);
- let call = match &func_type {
- FuncType::Method(Some(receiver)) => format!("{}.{}", receiver, &name).into(),
+ let call = match &func_kind {
+ FuncKind::Method(Some(receiver)) => format!("{}.{}", receiver, &name).into(),
_ => name.to_smol_str(),
};
let mut item = CompletionItem::new(
@@ -82,7 +80,7 @@ fn render(
// FIXME
// For now we don't properly calculate the edits for ref match
// completions on methods, so we've disabled them. See #8058.
- if matches!(func_type, FuncType::Function) {
+ if matches!(func_kind, FuncKind::Function) {
item.ref_match(ref_match);
}
}
@@ -90,7 +88,15 @@ fn render(
item.set_documentation(ctx.docs(func))
.set_deprecated(ctx.is_deprecated(func) || ctx.is_deprecated_assoc_item(func))
.detail(detail(db, func))
- .add_call_parens(completion, call, params);
+ .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);
+ }
+ _ => (),
+ }
if import_to_add.is_none() {
if let Some(actm) = func.as_assoc_item(db) {
@@ -103,11 +109,116 @@ fn render(
if let Some(import_to_add) = import_to_add {
item.add_import(import_to_add);
}
- item.lookup_by(name.to_smol_str());
-
item.build()
}
+pub(super) fn add_call_parens<'b>(
+ builder: &'b mut Builder,
+ ctx: &CompletionContext,
+ cap: SnippetCap,
+ name: SmolStr,
+ self_param: Option<hir::SelfParam>,
+ params: Vec<hir::Param>,
+) -> &'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 => f(&format_args!("${{{}:_}}", index + offset,)),
+ }
+ });
+ 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), .. }) => {
+ 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 ret_ty = func.ret_type(db);
let mut detail = String::new();
@@ -150,21 +261,17 @@ fn params_display(db: &dyn HirDatabase, func: hir::Function) -> String {
}
}
-fn params(ctx: &CompletionContext<'_>, func: hir::Function, func_type: &FuncType) -> Params {
- let (params, self_param) =
- if ctx.has_dot_receiver() || matches!(func_type, FuncType::Method(Some(_))) {
- (func.method_params(ctx.db).unwrap_or_default(), None)
- } else {
- let self_param = func.self_param(ctx.db);
-
- let mut assoc_params = func.assoc_fn_params(ctx.db);
- if self_param.is_some() {
- assoc_params.remove(0);
- }
- (assoc_params, self_param)
- };
-
- Params::Named(self_param, params)
+fn params(
+ ctx: &CompletionContext<'_>,
+ func: hir::Function,
+ func_kind: &FuncKind,
+) -> (Option<hir::SelfParam>, Vec<hir::Param>) {
+ 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)]