diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index cf93a3b9f4..d83fb16d10 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -1325,7 +1325,7 @@ impl Function {
/// Get this function's return type
pub fn ret_type(self, db: &dyn HirDatabase) -> Type {
let resolver = self.id.resolver(db.upcast());
- let krate = self.id.lookup(db.upcast()).container.module(db.upcast()).krate();
+ let krate = self.krate_id(db);
let ret_type = &db.function_data(self.id).ret_type;
let ctx = hir_ty::TyLoweringContext::new(db, &resolver);
let ty = ctx.lower_ty(ret_type);
@@ -1341,7 +1341,7 @@ impl Function {
pub fn assoc_fn_params(self, db: &dyn HirDatabase) -> Vec {
let resolver = self.id.resolver(db.upcast());
- let krate = self.id.lookup(db.upcast()).container.module(db.upcast()).krate();
+ let krate = self.krate_id(db);
let ctx = hir_ty::TyLoweringContext::new(db, &resolver);
let environment = db.trait_environment(self.id.into());
db.function_data(self.id)
@@ -1359,9 +1359,25 @@ impl Function {
if self.self_param(db).is_none() {
return None;
}
- let mut res = self.assoc_fn_params(db);
- res.remove(0);
- Some(res)
+ Some(self.params_without_self(db))
+ }
+
+ pub fn params_without_self(self, db: &dyn HirDatabase) -> Vec {
+ let resolver = self.id.resolver(db.upcast());
+ let krate = self.krate_id(db);
+ let ctx = hir_ty::TyLoweringContext::new(db, &resolver);
+ let environment = db.trait_environment(self.id.into());
+ let skip = if db.function_data(self.id).has_self_param() { 1 } else { 0 };
+ db.function_data(self.id)
+ .params
+ .iter()
+ .enumerate()
+ .skip(skip)
+ .map(|(idx, (_, type_ref))| {
+ let ty = Type { krate, env: environment.clone(), ty: ctx.lower_ty(type_ref) };
+ Param { func: self, ty, idx }
+ })
+ .collect()
}
pub fn is_unsafe(self, db: &dyn HirDatabase) -> bool {
@@ -1410,6 +1426,10 @@ impl Function {
result
}
+
+ fn krate_id(self, db: &dyn HirDatabase) -> CrateId {
+ self.id.lookup(db.upcast()).module(db.upcast()).krate()
+ }
}
// Note: logically, this belongs to `hir_ty`, but we are not using it there yet.
diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs
index bf841ee2b7..a203ebd45a 100644
--- a/crates/ide_completion/src/context.rs
+++ b/crates/ide_completion/src/context.rs
@@ -59,7 +59,7 @@ pub(super) enum PathKind {
#[derive(Debug)]
pub(crate) struct PathCompletionCtx {
/// If this is a call with () already there
- has_call_parens: bool,
+ pub(super) has_call_parens: bool,
/// Whether this path stars with a `::`.
pub(super) is_absolute_path: bool,
/// The qualifier of the current path if it exists.
diff --git a/crates/ide_completion/src/render.rs b/crates/ide_completion/src/render.rs
index e8ebb3e337..8624a8c3d2 100644
--- a/crates/ide_completion/src/render.rs
+++ b/crates/ide_completion/src/render.rs
@@ -10,8 +10,6 @@ pub(crate) mod type_alias;
pub(crate) mod struct_literal;
pub(crate) mod compound;
-mod builder_ext;
-
use hir::{AsAssocItem, HasAttrs, HirDisplay, ScopeDef};
use ide_db::{helpers::item_name, RootDatabase, SnippetCap, SymbolKind};
use syntax::{SmolStr, SyntaxKind, TextRange};
diff --git a/crates/ide_completion/src/render/builder_ext.rs b/crates/ide_completion/src/render/builder_ext.rs
deleted file mode 100644
index 70767a2a9c..0000000000
--- a/crates/ide_completion/src/render/builder_ext.rs
+++ /dev/null
@@ -1,129 +0,0 @@
-//! Extensions for `Builder` structure required for item rendering.
-
-use itertools::Itertools;
-use syntax::SmolStr;
-
-use crate::{context::PathKind, item::Builder, patterns::ImmediateLocation, CompletionContext};
-
-#[derive(Debug)]
-pub(super) enum Params {
- Named(Option, Vec),
- #[allow(dead_code)]
- Anonymous(usize),
-}
-
-impl Params {
- pub(super) fn len(&self) -> usize {
- match self {
- Params::Named(selv, params) => params.len() + if selv.is_some() { 1 } else { 0 },
- Params::Anonymous(len) => *len,
- }
- }
-
- pub(super) fn is_empty(&self) -> bool {
- self.len() == 0
- }
-}
-
-impl Builder {
- fn should_add_parens(&self, ctx: &CompletionContext) -> bool {
- if !ctx.config.add_call_parenthesis {
- return false;
- }
- if let Some(PathKind::Use) = ctx.path_kind() {
- cov_mark::hit!(no_parens_in_use_item);
- return false;
- }
- if matches!(ctx.path_kind(), Some(PathKind::Expr | PathKind::Pat) if ctx.path_is_call())
- | 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 {
- if ty.is_fn() {
- cov_mark::hit!(no_call_parens_if_fn_ptr_needed);
- return false;
- }
- }
-
- // Nothing prevents us from adding parentheses
- true
- }
-
- pub(super) fn add_call_parens(
- &mut self,
- ctx: &CompletionContext,
- name: SmolStr,
- params: Params,
- ) -> &mut Builder {
- if !self.should_add_parens(ctx) {
- return self;
- }
-
- let cap = match ctx.config.snippet_cap {
- Some(it) => it,
- None => return self,
- };
- // If not an import, add parenthesis automatically.
- cov_mark::hit!(inserts_parens_for_function_calls);
-
- let (snippet, label) = if params.is_empty() {
- (format!("{}()$0", name), format!("{}()", name))
- } else {
- self.trigger_call_info();
- let snippet = match (ctx.config.add_call_argument_snippets, params) {
- (true, Params::Named(self_param, params)) => {
- 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)
- }
- }
- }
- _ => {
- cov_mark::hit!(suppress_arg_snippets);
- format!("{}($0)", name)
- }
- };
-
- (snippet, format!("{}(…)", name))
- };
- self.lookup_by(name).label(label).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) && local.ty(ctx.db) == derefed_ty {
- return if ty.is_mutable_reference() { "&mut " } else { "&" };
- }
- }
- }
- ""
-}
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),
}
@@ -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,
func: hir::Function,
- func_type: FuncType,
+ func_kind: FuncKind,
import_to_add: Option,
) -> 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,
+ 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 => 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, 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)]