]> git.lizzy.rs Git - rust.git/blobdiff - crates/ide_completion/src/render/function.rs
Replace some String usages with SmolStr in completions
[rust.git] / crates / ide_completion / src / render / function.rs
index 63bd669260199ffb8db4ae1e8da15ee7b7fb6772..f598b414a73b4238b11c9974bec2a5d1698b0083 100644 (file)
 //! Renderer for function calls.
 
-use hir::{HasSource, HirDisplay};
+use either::Either;
+use hir::{AsAssocItem, HasSource, HirDisplay};
 use ide_db::SymbolKind;
 use itertools::Itertools;
-use syntax::ast::Fn;
+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_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<hir::Name>,
     fn_: hir::Function,
 ) -> Option<CompletionItem> {
     let _p = profile::span("render_fn");
-    Some(FunctionRender::new(ctx, local_name, fn_, false)?.render(import_to_add))
+    Some(FunctionRender::new(ctx, None, local_name, fn_, false)?.render(import_to_add))
 }
 
-pub(crate) fn render_method<'a>(
-    ctx: RenderContext<'a>,
+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, local_name, fn_, true)?.render(import_to_add))
+    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>,
+        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, is_method })
+        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 {
             type_match: compute_type_match(self.ctx.completion, &ret_type),
-            exact_name_match: compute_exact_name_match(self.ctx.completion, &self.name),
+            exact_name_match: compute_exact_name_match(self.ctx.completion, &call),
             ..CompletionRelevance::default()
         });
 
@@ -129,47 +159,25 @@ fn ty_display(&self) -> String {
         format!("-> {}", ret_ty.display(self.ctx.db()))
     }
 
-    fn add_arg(&self, arg: &str, ty: &hir::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);
-                }
-            }
-        }
-        arg.to_string()
-    }
-
     fn params(&self) -> Params {
         let ast_params = match self.ast_node.param_list() {
             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)
     }
 
@@ -185,7 +193,7 @@ fn kind(&self) -> CompletionItemKind {
 #[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,
     };
 
@@ -253,13 +261,32 @@ 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() {
-        cov_mark::check!(parens_for_method_call_as_assoc_fn);
         check_edit(
             "foo",
             r#"