Unnamed repository; edit this file 'description' to name the repository.
Auto merge of #15879 - dfireBird:fix-14656, r=Veykril
Implement completion for the callable fields. Fixes #14656 PR is opened with basic changes. It could be improved by having a new `SymbolKind` for the callable fields and implementing a separate render function similar to the `render_method` for the new `SymbolKind`. It could also be done without any changes to the `SymbolKind` of course, have the new function called based on the type of field. I prefer the former method. Please give any thoughts or changes you think is appropriate for this method. I could start working on that in this same PR.
bors 2023-12-01
parent 6e6a0b0 · parent b7effe5 · commit e402c49
-rw-r--r--crates/ide-completion/src/completions/dot.rs92
-rw-r--r--crates/ide-completion/src/render.rs116
2 files changed, 185 insertions, 23 deletions
diff --git a/crates/ide-completion/src/completions/dot.rs b/crates/ide-completion/src/completions/dot.rs
index 5bcc867fe1..57e0646109 100644
--- a/crates/ide-completion/src/completions/dot.rs
+++ b/crates/ide-completion/src/completions/dot.rs
@@ -26,17 +26,17 @@ pub(crate) fn complete_dot(
item.add_to(acc, ctx.db);
}
- if let DotAccessKind::Method { .. } = dot_access.kind {
- cov_mark::hit!(test_no_struct_field_completion_for_method_call);
- } else {
- complete_fields(
- acc,
- ctx,
- receiver_ty,
- |acc, field, ty| acc.add_field(ctx, dot_access, None, field, &ty),
- |acc, field, ty| acc.add_tuple_field(ctx, None, field, &ty),
- );
- }
+ let is_field_access = matches!(dot_access.kind, DotAccessKind::Field { .. });
+
+ complete_fields(
+ acc,
+ ctx,
+ receiver_ty,
+ |acc, field, ty| acc.add_field(ctx, dot_access, None, field, &ty),
+ |acc, field, ty| acc.add_tuple_field(ctx, None, field, &ty),
+ is_field_access,
+ );
+
complete_methods(ctx, receiver_ty, |func| acc.add_method(ctx, dot_access, func, None, None));
}
@@ -82,6 +82,7 @@ pub(crate) fn complete_undotted_self(
)
},
|acc, field, ty| acc.add_tuple_field(ctx, Some(hir::known::SELF_PARAM), field, &ty),
+ true,
);
complete_methods(ctx, &ty, |func| {
acc.add_method(
@@ -104,18 +105,23 @@ fn complete_fields(
receiver: &hir::Type,
mut named_field: impl FnMut(&mut Completions, hir::Field, hir::Type),
mut tuple_index: impl FnMut(&mut Completions, usize, hir::Type),
+ is_field_access: bool,
) {
let mut seen_names = FxHashSet::default();
for receiver in receiver.autoderef(ctx.db) {
for (field, ty) in receiver.fields(ctx.db) {
- if seen_names.insert(field.name(ctx.db)) {
+ if seen_names.insert(field.name(ctx.db))
+ && (is_field_access || ty.is_fn() || ty.is_closure())
+ {
named_field(acc, field, ty);
}
}
for (i, ty) in receiver.tuple_fields(ctx.db).into_iter().enumerate() {
// Tuples are always the last type in a deref chain, so just check if the name is
// already seen without inserting into the hashset.
- if !seen_names.contains(&hir::Name::new_tuple_field(i)) {
+ if !seen_names.contains(&hir::Name::new_tuple_field(i))
+ && (is_field_access || ty.is_fn() || ty.is_closure())
+ {
// Tuple fields are always public (tuple struct fields are handled above).
tuple_index(acc, i, ty);
}
@@ -250,7 +256,6 @@ impl A {
#[test]
fn test_no_struct_field_completion_for_method_call() {
- cov_mark::check!(test_no_struct_field_completion_for_method_call);
check(
r#"
struct A { the_field: u32 }
@@ -1172,4 +1177,63 @@ impl<B: Bar, F: core::ops::Deref<Target = B>> Foo<F> {
"#]],
);
}
+
+ #[test]
+ fn test_struct_function_field_completion() {
+ check(
+ r#"
+struct S { va_field: u32, fn_field: fn() }
+fn foo() { S { va_field: 0, fn_field: || {} }.fi$0() }
+"#,
+ expect![[r#"
+ fd fn_field fn()
+ "#]],
+ );
+
+ check_edit(
+ "fn_field",
+ r#"
+struct S { va_field: u32, fn_field: fn() }
+fn foo() { S { va_field: 0, fn_field: || {} }.fi$0() }
+"#,
+ r#"
+struct S { va_field: u32, fn_field: fn() }
+fn foo() { (S { va_field: 0, fn_field: || {} }.fn_field)() }
+"#,
+ );
+ }
+
+ #[test]
+ fn test_tuple_function_field_completion() {
+ check(
+ r#"
+struct B(u32, fn())
+fn foo() {
+ let b = B(0, || {});
+ b.$0()
+}
+"#,
+ expect![[r#"
+ fd 1 fn()
+ "#]],
+ );
+
+ check_edit(
+ "1",
+ r#"
+struct B(u32, fn())
+fn foo() {
+ let b = B(0, || {});
+ b.$0()
+}
+"#,
+ r#"
+struct B(u32, fn())
+fn foo() {
+ let b = B(0, || {});
+ (b.1)()
+}
+"#,
+ )
+ }
}
diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs
index 00a9081985..048730c078 100644
--- a/crates/ide-completion/src/render.rs
+++ b/crates/ide-completion/src/render.rs
@@ -18,9 +18,10 @@ use ide_db::{
RootDatabase, SnippetCap, SymbolKind,
};
use syntax::{AstNode, SmolStr, SyntaxKind, TextRange};
+use text_edit::TextEdit;
use crate::{
- context::{DotAccess, PathCompletionCtx, PathKind, PatternContext},
+ context::{DotAccess, DotAccessKind, PathCompletionCtx, PathKind, PatternContext},
item::{Builder, CompletionRelevanceTypeMatch},
render::{
function::render_fn,
@@ -147,7 +148,42 @@ pub(crate) fn render_field(
.set_documentation(field.docs(db))
.set_deprecated(is_deprecated)
.lookup_by(name);
- item.insert_text(field_with_receiver(db, receiver.as_ref(), &escaped_name));
+
+ let is_field_access = matches!(dot_access.kind, DotAccessKind::Field { .. });
+ if !is_field_access || ty.is_fn() || ty.is_closure() {
+ let mut builder = TextEdit::builder();
+ // Using TextEdit, insert '(' before the struct name and ')' before the
+ // dot access, then comes the field name and optionally insert function
+ // call parens.
+
+ builder.replace(
+ ctx.source_range(),
+ field_with_receiver(db, receiver.as_ref(), &escaped_name).into(),
+ );
+
+ let expected_fn_type =
+ ctx.completion.expected_type.as_ref().is_some_and(|ty| ty.is_fn() || ty.is_closure());
+
+ if !expected_fn_type {
+ if let Some(receiver) = &dot_access.receiver {
+ if let Some(receiver) = ctx.completion.sema.original_ast_node(receiver.clone()) {
+ builder.insert(receiver.syntax().text_range().start(), "(".to_string());
+ builder.insert(ctx.source_range().end(), ")".to_string());
+ }
+ }
+
+ let is_parens_needed =
+ !matches!(dot_access.kind, DotAccessKind::Method { has_parens: true });
+
+ if is_parens_needed {
+ builder.insert(ctx.source_range().end(), "()".to_string());
+ }
+ }
+
+ item.text_edit(builder.finish());
+ } else {
+ item.insert_text(field_with_receiver(db, receiver.as_ref(), &escaped_name));
+ }
if let Some(receiver) = &dot_access.receiver {
if let Some(original) = ctx.completion.sema.original_ast_node(receiver.clone()) {
if let Some(ref_match) = compute_ref_match(ctx.completion, ty) {
@@ -1600,7 +1636,7 @@ fn main() {
fn struct_field_method_ref() {
check_kinds(
r#"
-struct Foo { bar: u32 }
+struct Foo { bar: u32, qux: fn() }
impl Foo { fn baz(&self) -> u32 { 0 } }
fn foo(f: Foo) { let _: &u32 = f.b$0 }
@@ -1610,24 +1646,44 @@ fn foo(f: Foo) { let _: &u32 = f.b$0 }
[
CompletionItem {
label: "baz()",
- source_range: 98..99,
- delete: 98..99,
+ source_range: 109..110,
+ delete: 109..110,
insert: "baz()$0",
kind: Method,
lookup: "baz",
detail: "fn(&self) -> u32",
- ref_match: "&@96",
+ ref_match: "&@107",
},
CompletionItem {
label: "bar",
- source_range: 98..99,
- delete: 98..99,
+ source_range: 109..110,
+ delete: 109..110,
insert: "bar",
kind: SymbolKind(
Field,
),
detail: "u32",
- ref_match: "&@96",
+ ref_match: "&@107",
+ },
+ CompletionItem {
+ label: "qux",
+ source_range: 109..110,
+ text_edit: TextEdit {
+ indels: [
+ Indel {
+ insert: "(",
+ delete: 107..107,
+ },
+ Indel {
+ insert: "qux)()",
+ delete: 109..110,
+ },
+ ],
+ },
+ kind: SymbolKind(
+ Field,
+ ),
+ detail: "fn()",
},
]
"#]],
@@ -1635,6 +1691,48 @@ fn foo(f: Foo) { let _: &u32 = f.b$0 }
}
#[test]
+ fn expected_fn_type_ref() {
+ check_kinds(
+ r#"
+struct S { field: fn() }
+
+fn foo() {
+ let foo: fn() = S { fields: || {}}.fi$0;
+}
+"#,
+ &[CompletionItemKind::SymbolKind(SymbolKind::Field)],
+ expect![[r#"
+ [
+ CompletionItem {
+ label: "field",
+ source_range: 76..78,
+ delete: 76..78,
+ insert: "field",
+ kind: SymbolKind(
+ Field,
+ ),
+ detail: "fn()",
+ relevance: CompletionRelevance {
+ exact_name_match: false,
+ type_match: Some(
+ Exact,
+ ),
+ is_local: false,
+ is_item_from_trait: false,
+ is_name_already_imported: false,
+ requires_import: false,
+ is_op_method: false,
+ is_private_editable: false,
+ postfix_match: None,
+ is_definite: false,
+ },
+ },
+ ]
+ "#]],
+ )
+ }
+
+ #[test]
fn qualified_path_ref() {
check_kinds(
r#"