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); item.add_to(acc, ctx.db);
} }
if let DotAccessKind::Method { .. } = dot_access.kind { let is_field_access = matches!(dot_access.kind, DotAccessKind::Field { .. });
cov_mark::hit!(test_no_struct_field_completion_for_method_call);
} else { complete_fields(
complete_fields( acc,
acc, ctx,
ctx, receiver_ty,
receiver_ty, |acc, field, ty| acc.add_field(ctx, dot_access, None, field, &ty),
|acc, field, ty| acc.add_field(ctx, dot_access, None, field, &ty), |acc, field, ty| acc.add_tuple_field(ctx, 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)); 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), |acc, field, ty| acc.add_tuple_field(ctx, Some(hir::known::SELF_PARAM), field, &ty),
true,
); );
complete_methods(ctx, &ty, |func| { complete_methods(ctx, &ty, |func| {
acc.add_method( acc.add_method(
@ -104,18 +105,23 @@ fn complete_fields(
receiver: &hir::Type, receiver: &hir::Type,
mut named_field: impl FnMut(&mut Completions, hir::Field, hir::Type), mut named_field: impl FnMut(&mut Completions, hir::Field, hir::Type),
mut tuple_index: impl FnMut(&mut Completions, usize, hir::Type), mut tuple_index: impl FnMut(&mut Completions, usize, hir::Type),
is_field_access: bool,
) { ) {
let mut seen_names = FxHashSet::default(); let mut seen_names = FxHashSet::default();
for receiver in receiver.autoderef(ctx.db) { for receiver in receiver.autoderef(ctx.db) {
for (field, ty) in receiver.fields(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); named_field(acc, field, ty);
} }
} }
for (i, ty) in receiver.tuple_fields(ctx.db).into_iter().enumerate() { 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 // Tuples are always the last type in a deref chain, so just check if the name is
// already seen without inserting into the hashset. // 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 fields are always public (tuple struct fields are handled above).
tuple_index(acc, i, ty); tuple_index(acc, i, ty);
} }
@ -250,7 +256,6 @@ fn foo(&self) { self.$0 }
#[test] #[test]
fn test_no_struct_field_completion_for_method_call() { fn test_no_struct_field_completion_for_method_call() {
cov_mark::check!(test_no_struct_field_completion_for_method_call);
check( check(
r#" r#"
struct A { the_field: u32 } 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, RootDatabase, SnippetCap, SymbolKind,
}; };
use syntax::{AstNode, SmolStr, SyntaxKind, TextRange}; use syntax::{AstNode, SmolStr, SyntaxKind, TextRange};
use text_edit::TextEdit;
use crate::{ use crate::{
context::{DotAccess, PathCompletionCtx, PathKind, PatternContext}, context::{DotAccess, DotAccessKind, PathCompletionCtx, PathKind, PatternContext},
item::{Builder, CompletionRelevanceTypeMatch}, item::{Builder, CompletionRelevanceTypeMatch},
render::{ render::{
function::render_fn, function::render_fn,
@ -147,7 +148,42 @@ pub(crate) fn render_field(
.set_documentation(field.docs(db)) .set_documentation(field.docs(db))
.set_deprecated(is_deprecated) .set_deprecated(is_deprecated)
.lookup_by(name); .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(receiver) = &dot_access.receiver {
if let Some(original) = ctx.completion.sema.original_ast_node(receiver.clone()) { if let Some(original) = ctx.completion.sema.original_ast_node(receiver.clone()) {
if let Some(ref_match) = compute_ref_match(ctx.completion, ty) { if let Some(ref_match) = compute_ref_match(ctx.completion, ty) {
@ -1600,7 +1636,7 @@ fn new() []
fn struct_field_method_ref() { fn struct_field_method_ref() {
check_kinds( check_kinds(
r#" r#"
struct Foo { bar: u32 } struct Foo { bar: u32, qux: fn() }
impl Foo { fn baz(&self) -> u32 { 0 } } impl Foo { fn baz(&self) -> u32 { 0 } }
fn foo(f: Foo) { let _: &u32 = f.b$0 } fn foo(f: Foo) { let _: &u32 = f.b$0 }
@ -1610,30 +1646,92 @@ fn foo(f: Foo) { let _: &u32 = f.b$0 }
[ [
CompletionItem { CompletionItem {
label: "baz()", label: "baz()",
source_range: 98..99, source_range: 109..110,
delete: 98..99, delete: 109..110,
insert: "baz()$0", insert: "baz()$0",
kind: Method, kind: Method,
lookup: "baz", lookup: "baz",
detail: "fn(&self) -> u32", detail: "fn(&self) -> u32",
ref_match: "&@96", ref_match: "&@107",
}, },
CompletionItem { CompletionItem {
label: "bar", label: "bar",
source_range: 98..99, source_range: 109..110,
delete: 98..99, delete: 109..110,
insert: "bar", insert: "bar",
kind: SymbolKind( kind: SymbolKind(
Field, Field,
), ),
detail: "u32", 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] #[test]
fn qualified_path_ref() { fn qualified_path_ref() {
check_kinds( check_kinds(