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.
This commit is contained in:
bors 2023-12-01 16:20:36 +00:00
commit e402c494b7
2 changed files with 185 additions and 23 deletions

View File

@ -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 @@ fn foo(&self) { self.$0 }
#[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 @@ fn foobar(&self) {
"#]],
);
}
#[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)()
}
"#,
)
}
}

View File

@ -18,9 +18,10 @@
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 new() []
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,30 +1646,92 @@ 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()",
},
]
"#]],
);
}
#[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(