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