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.rs | 652 |
1 files changed, 652 insertions, 0 deletions
diff --git a/crates/ide-completion/src/render/function.rs b/crates/ide-completion/src/render/function.rs new file mode 100644 index 0000000000..d8183660e8 --- /dev/null +++ b/crates/ide-completion/src/render/function.rs @@ -0,0 +1,652 @@ +//! Renderer for function calls. + +use hir::{db::HirDatabase, AsAssocItem, HirDisplay}; +use ide_db::{SnippetCap, SymbolKind}; +use itertools::Itertools; +use stdx::{format_to, to_lower_snake_case}; +use syntax::SmolStr; + +use crate::{ + context::{CompletionContext, PathCompletionCtx, PathKind}, + item::{Builder, CompletionItem, CompletionItemKind, CompletionRelevance}, + patterns::ImmediateLocation, + render::{compute_exact_name_match, compute_ref_match, compute_type_match, RenderContext}, +}; + +enum FuncKind { + Function, + Method(Option<hir::Name>), +} + +pub(crate) fn render_fn( + ctx: RenderContext<'_>, + local_name: Option<hir::Name>, + func: hir::Function, +) -> Builder { + let _p = profile::span("render_fn"); + render(ctx, local_name, func, FuncKind::Function) +} + +pub(crate) fn render_method( + ctx: RenderContext<'_>, + receiver: Option<hir::Name>, + local_name: Option<hir::Name>, + func: hir::Function, +) -> Builder { + let _p = profile::span("render_method"); + render(ctx, local_name, func, FuncKind::Method(receiver)) +} + +fn render( + ctx @ RenderContext { completion, .. }: RenderContext<'_>, + local_name: Option<hir::Name>, + func: hir::Function, + func_kind: FuncKind, +) -> Builder { + let db = completion.db; + + let name = local_name.unwrap_or_else(|| func.name(db)); + + let call = match &func_kind { + FuncKind::Method(Some(receiver)) => format!("{}.{}", receiver, &name).into(), + _ => name.to_smol_str(), + }; + let mut item = CompletionItem::new( + if func.self_param(db).is_some() { + CompletionItemKind::Method + } else { + CompletionItemKind::SymbolKind(SymbolKind::Function) + }, + ctx.source_range(), + call.clone(), + ); + + let ret_type = func.ret_type(db); + let is_op_method = func + .as_assoc_item(ctx.db()) + .and_then(|trait_| trait_.containing_trait_or_trait_impl(ctx.db())) + .map_or(false, |trait_| completion.is_ops_trait(trait_)); + item.set_relevance(CompletionRelevance { + type_match: compute_type_match(completion, &ret_type), + exact_name_match: compute_exact_name_match(completion, &call), + is_op_method, + ..ctx.completion_relevance() + }); + + if let Some(ref_match) = compute_ref_match(completion, &ret_type) { + // FIXME For now we don't properly calculate the edits for ref match + // completions on methods or qualified paths, so we've disabled them. + // See #8058. + if matches!(func_kind, FuncKind::Function) && ctx.completion.path_qual().is_none() { + item.ref_match(ref_match); + } + } + + item.set_documentation(ctx.docs(func)) + .set_deprecated(ctx.is_deprecated(func) || ctx.is_deprecated_assoc_item(func)) + .detail(detail(db, func)) + .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); + } + _ => (), + } + + match ctx.import_to_add { + Some(import_to_add) => { + item.add_import(import_to_add); + } + None => { + if let Some(actm) = 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()); + } + } + } + } + item +} + +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 => { + let name = match param.ty().as_adt() { + None => "_".to_string(), + Some(adt) => adt + .name(ctx.db) + .as_text() + .map(|s| to_lower_snake_case(s.as_str())) + .unwrap_or_else(|| "_".to_string()), + }; + f(&format_args!("${{{}:{}}}", index + offset, name)) + } + } + }); + 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 | PathKind::Type), .. }) => { + 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 mut ret_ty = func.ret_type(db); + let mut detail = String::new(); + + if func.is_const(db) { + format_to!(detail, "const "); + } + if func.is_async(db) { + format_to!(detail, "async "); + if let Some(async_ret) = func.async_ret_type(db) { + ret_ty = async_ret; + } + } + if func.is_unsafe_to_call(db) { + format_to!(detail, "unsafe "); + } + + format_to!(detail, "fn({})", params_display(db, func)); + if !ret_ty.is_unit() { + format_to!(detail, " -> {}", ret_ty.display(db)); + } + detail +} + +fn params_display(db: &dyn HirDatabase, func: hir::Function) -> String { + if let Some(self_param) = func.self_param(db) { + let assoc_fn_params = func.assoc_fn_params(db); + let params = assoc_fn_params + .iter() + .skip(1) // skip the self param because we are manually handling that + .map(|p| p.ty().display(db)); + format!( + "{}{}", + self_param.display(db), + params.format_with("", |display, f| { + f(&", ")?; + f(&display) + }) + ) + } else { + let assoc_fn_params = func.assoc_fn_params(db); + assoc_fn_params.iter().map(|p| p.ty().display(db)).join(", ") + } +} + +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)] +mod tests { + use crate::{ + tests::{check_edit, check_edit_with_config, TEST_CONFIG}, + CompletionConfig, + }; + + #[test] + fn inserts_parens_for_function_calls() { + cov_mark::check!(inserts_parens_for_function_calls); + check_edit( + "no_args", + r#" +fn no_args() {} +fn main() { no_$0 } +"#, + r#" +fn no_args() {} +fn main() { no_args()$0 } +"#, + ); + + check_edit( + "with_args", + r#" +fn with_args(x: i32, y: String) {} +fn main() { with_$0 } +"#, + r#" +fn with_args(x: i32, y: String) {} +fn main() { with_args(${1:x}, ${2:y})$0 } +"#, + ); + + check_edit( + "foo", + r#" +struct S; +impl S { + fn foo(&self) {} +} +fn bar(s: &S) { s.f$0 } +"#, + r#" +struct S; +impl S { + fn foo(&self) {} +} +fn bar(s: &S) { s.foo()$0 } +"#, + ); + + check_edit( + "foo", + r#" +struct S {} +impl S { + fn foo(&self, x: i32) {} +} +fn bar(s: &S) { + s.f$0 +} +"#, + r#" +struct S {} +impl S { + 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() { + check_edit( + "foo", + r#" +struct S; +impl S { + fn foo(&self) {} +} +fn main() { S::f$0 } +"#, + r#" +struct S; +impl S { + fn foo(&self) {} +} +fn main() { S::foo(${1:&self})$0 } +"#, + ); + } + + #[test] + fn suppress_arg_snippets() { + cov_mark::check!(suppress_arg_snippets); + check_edit_with_config( + CompletionConfig { add_call_argument_snippets: false, ..TEST_CONFIG }, + "with_args", + r#" +fn with_args(x: i32, y: String) {} +fn main() { with_$0 } +"#, + r#" +fn with_args(x: i32, y: String) {} +fn main() { with_args($0) } +"#, + ); + } + + #[test] + fn strips_underscores_from_args() { + check_edit( + "foo", + r#" +fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {} +fn main() { f$0 } +"#, + r#" +fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {} +fn main() { foo(${1:foo}, ${2:bar}, ${3:ho_ge_})$0 } +"#, + ); + } + + #[test] + fn insert_ref_when_matching_local_in_scope() { + check_edit( + "ref_arg", + r#" +struct Foo {} +fn ref_arg(x: &Foo) {} +fn main() { + let x = Foo {}; + ref_ar$0 +} +"#, + r#" +struct Foo {} +fn ref_arg(x: &Foo) {} +fn main() { + let x = Foo {}; + ref_arg(${1:&x})$0 +} +"#, + ); + } + + #[test] + fn insert_mut_ref_when_matching_local_in_scope() { + check_edit( + "ref_arg", + r#" +struct Foo {} +fn ref_arg(x: &mut Foo) {} +fn main() { + let x = Foo {}; + ref_ar$0 +} +"#, + r#" +struct Foo {} +fn ref_arg(x: &mut Foo) {} +fn main() { + let x = Foo {}; + ref_arg(${1:&mut x})$0 +} +"#, + ); + } + + #[test] + fn insert_ref_when_matching_local_in_scope_for_method() { + check_edit( + "apply_foo", + r#" +struct Foo {} +struct Bar {} +impl Bar { + fn apply_foo(&self, x: &Foo) {} +} + +fn main() { + let x = Foo {}; + let y = Bar {}; + y.$0 +} +"#, + r#" +struct Foo {} +struct Bar {} +impl Bar { + fn apply_foo(&self, x: &Foo) {} +} + +fn main() { + let x = Foo {}; + let y = Bar {}; + y.apply_foo(${1:&x})$0 +} +"#, + ); + } + + #[test] + fn trim_mut_keyword_in_func_completion() { + check_edit( + "take_mutably", + r#" +fn take_mutably(mut x: &i32) {} + +fn main() { + take_m$0 +} +"#, + r#" +fn take_mutably(mut x: &i32) {} + +fn main() { + take_mutably(${1:x})$0 +} +"#, + ); + } + + #[test] + fn complete_pattern_args_with_type_name_if_adt() { + check_edit( + "qux", + r#" +struct Foo { + bar: i32 +} + +fn qux(Foo { bar }: Foo) { + println!("{}", bar); +} + +fn main() { + qu$0 +} +"#, + r#" +struct Foo { + bar: i32 +} + +fn qux(Foo { bar }: Foo) { + println!("{}", bar); +} + +fn main() { + qux(${1:foo})$0 +} +"#, + ); + } + + #[test] + fn complete_fn_param() { + // has mut kw + check_edit( + "mut bar: u32", + r#" +fn f(foo: (), mut bar: u32) {} +fn g(foo: (), mut ba$0) +"#, + r#" +fn f(foo: (), mut bar: u32) {} +fn g(foo: (), mut bar: u32) +"#, + ); + + // has type param + check_edit( + "mut bar: u32", + r#" +fn g(foo: (), mut ba$0: u32) +fn f(foo: (), mut bar: u32) {} +"#, + r#" +fn g(foo: (), mut bar: u32) +fn f(foo: (), mut bar: u32) {} +"#, + ); + } + + #[test] + fn complete_fn_mut_param_add_comma() { + // add leading and trailing comma + check_edit( + ", mut bar: u32,", + r#" +fn f(foo: (), mut bar: u32) {} +fn g(foo: ()mut ba$0 baz: ()) +"#, + r#" +fn f(foo: (), mut bar: u32) {} +fn g(foo: (), mut bar: u32, baz: ()) +"#, + ); + } + + #[test] + fn complete_fn_mut_param_has_attribute() { + check_edit( + r#"#[baz = "qux"] mut bar: u32"#, + r#" +fn f(foo: (), #[baz = "qux"] mut bar: u32) {} +fn g(foo: (), mut ba$0) +"#, + r#" +fn f(foo: (), #[baz = "qux"] mut bar: u32) {} +fn g(foo: (), #[baz = "qux"] mut bar: u32) +"#, + ); + + check_edit( + r#"#[baz = "qux"] mut bar: u32"#, + r#" +fn f(foo: (), #[baz = "qux"] mut bar: u32) {} +fn g(foo: (), #[baz = "qux"] mut ba$0) +"#, + r#" +fn f(foo: (), #[baz = "qux"] mut bar: u32) {} +fn g(foo: (), #[baz = "qux"] mut bar: u32) +"#, + ); + + check_edit( + r#", #[baz = "qux"] mut bar: u32"#, + r#" +fn f(foo: (), #[baz = "qux"] mut bar: u32) {} +fn g(foo: ()#[baz = "qux"] mut ba$0) +"#, + r#" +fn f(foo: (), #[baz = "qux"] mut bar: u32) {} +fn g(foo: (), #[baz = "qux"] mut bar: u32) +"#, + ); + } +} |