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:
commit
e402c494b7
@ -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)()
|
||||
}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
Loading…
Reference in New Issue
Block a user