//! Renderer for function calls.
-use hir::{HasSource, HirDisplay, Type};
+use either::Either;
+use hir::{AsAssocItem, HasSource, HirDisplay};
use ide_db::SymbolKind;
-use syntax::ast::Fn;
+use itertools::Itertools;
+use syntax::ast;
use crate::{
- item::{CompletionItem, CompletionItemKind, CompletionKind, CompletionRelevance, ImportEdit},
+ item::{CompletionItem, CompletionItemKind, CompletionRelevance, ImportEdit},
render::{
- builder_ext::Params, compute_exact_name_match, compute_exact_type_match, compute_ref_match,
+ builder_ext::Params, compute_exact_name_match, compute_ref_match, compute_type_match,
RenderContext,
},
};
-pub(crate) fn render_fn<'a>(
- ctx: RenderContext<'a>,
+pub(crate) fn render_fn(
+ ctx: RenderContext<'_>,
import_to_add: Option<ImportEdit>,
- local_name: Option<String>,
+ local_name: Option<hir::Name>,
fn_: hir::Function,
) -> Option<CompletionItem> {
let _p = profile::span("render_fn");
- Some(FunctionRender::new(ctx, local_name, fn_)?.render(import_to_add))
+ Some(FunctionRender::new(ctx, None, local_name, fn_, false)?.render(import_to_add))
+}
+
+pub(crate) fn render_method(
+ ctx: RenderContext<'_>,
+ import_to_add: Option<ImportEdit>,
+ receiver: Option<hir::Name>,
+ local_name: Option<hir::Name>,
+ fn_: hir::Function,
+) -> Option<CompletionItem> {
+ 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,
+ name: hir::Name,
+ receiver: Option<hir::Name>,
func: hir::Function,
- ast_node: Fn,
+ /// 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>,
- local_name: Option<String>,
+ receiver: Option<hir::Name>,
+ local_name: Option<hir::Name>,
fn_: hir::Function,
+ is_method: bool,
) -> Option<FunctionRender<'a>> {
- let name = local_name.unwrap_or_else(|| fn_.name(ctx.db()).to_string());
+ let name = local_name.unwrap_or_else(|| fn_.name(ctx.db()));
let ast_node = fn_.source(ctx.db())?.value;
- Some(FunctionRender { ctx, name, func: fn_, ast_node })
+ Some(FunctionRender { ctx, name, receiver, func: fn_, ast_node, is_method })
}
fn render(self, import_to_add: Option<ImportEdit>) -> CompletionItem {
let params = self.params();
- let mut item = CompletionItem::new(
- CompletionKind::Reference,
- self.ctx.source_range(),
- self.name.clone(),
- );
- item.kind(self.kind())
- .set_documentation(self.ctx.docs(self.func))
+ let call = match &self.receiver {
+ Some(receiver) => format!("{}.{}", receiver, &self.name),
+ None => self.name.to_string(),
+ };
+ 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, self.name.clone(), params)
- .add_import(import_to_add);
+ .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_smol_str());
+ }
+ }
+ }
+
+ if let Some(import_to_add) = import_to_add {
+ item.add_import(import_to_add);
+ }
+ item.lookup_by(self.name.to_smol_str());
let ret_type = self.func.ret_type(self.ctx.db());
item.set_relevance(CompletionRelevance {
- exact_type_match: compute_exact_type_match(self.ctx.completion, &ret_type),
- exact_name_match: compute_exact_name_match(self.ctx.completion, self.name.clone()),
+ 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) {
- item.ref_match(ref_match);
+ // 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 ty = self.func.ret_type(self.ctx.db());
- format!("-> {}", ty.display(self.ctx.db()))
+ 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 add_arg(&self, arg: &str, ty: &Type) -> String {
- if let Some(derefed_ty) = ty.remove_ref() {
- for (name, local) in self.ctx.completion.locals.iter() {
- if name == arg && local.ty(self.ctx.db()) == derefed_ty {
- let mutability = if ty.is_mutable_reference() { "&mut " } else { "&" };
- return format!("{}{}", mutability, arg);
- }
- }
+ 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
}
- arg.to_string()
+ }
+
+ 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 {
Some(it) => it,
None => return Params::Named(Vec::new()),
};
+ let params = ast_params.params().map(Either::Right);
- let mut params_pats = Vec::new();
- let params_ty = if self.ctx.completion.dot_receiver.is_some() {
- self.func.method_params(self.ctx.db()).unwrap_or_default()
+ 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 {
- if let Some(s) = ast_params.self_param() {
- cov_mark::hit!(parens_for_method_call_as_assoc_fn);
- params_pats.push(Some(s.to_string()));
- }
- self.func.assoc_fn_params(self.ctx.db())
+ ast_params
+ .self_param()
+ .map(Either::Left)
+ .into_iter()
+ .chain(params)
+ .zip(self.func.assoc_fn_params(self.ctx.db()))
+ .collect()
};
- params_pats
- .extend(ast_params.params().into_iter().map(|it| it.pat().map(|it| it.to_string())));
-
- let params = params_pats
- .into_iter()
- .zip(params_ty)
- .flat_map(|(pat, param_ty)| {
- let pat = pat?;
- let name = pat;
- let arg = name.trim_start_matches("mut ").trim_start_matches('_');
- Some(self.add_arg(arg, param_ty.ty()))
- })
- .collect();
+
Params::Named(params)
}
#[cfg(test)]
mod tests {
use crate::{
- test_utils::{check_edit, check_edit_with_config, TEST_CONFIG},
+ tests::{check_edit, check_edit_with_config, TEST_CONFIG},
CompletionConfig,
};
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() {
- cov_mark::check!(parens_for_method_call_as_assoc_fn);
check_edit(
"foo",
r#"