Skip to content

Commit e402c49

Browse files
committed
Auto merge of rust-lang#15879 - dfireBird:fix-14656, r=Veykril
Implement completion for the callable fields. Fixes rust-lang#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.
2 parents 6e6a0b0 + b7effe5 commit e402c49

File tree

2 files changed

+185
-23
lines changed

2 files changed

+185
-23
lines changed

crates/ide-completion/src/completions/dot.rs

+78-14
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,17 @@ pub(crate) fn complete_dot(
2626
item.add_to(acc, ctx.db);
2727
}
2828

29-
if let DotAccessKind::Method { .. } = dot_access.kind {
30-
cov_mark::hit!(test_no_struct_field_completion_for_method_call);
31-
} else {
32-
complete_fields(
33-
acc,
34-
ctx,
35-
receiver_ty,
36-
|acc, field, ty| acc.add_field(ctx, dot_access, None, field, &ty),
37-
|acc, field, ty| acc.add_tuple_field(ctx, None, field, &ty),
38-
);
39-
}
29+
let is_field_access = matches!(dot_access.kind, DotAccessKind::Field { .. });
30+
31+
complete_fields(
32+
acc,
33+
ctx,
34+
receiver_ty,
35+
|acc, field, ty| acc.add_field(ctx, dot_access, None, field, &ty),
36+
|acc, field, ty| acc.add_tuple_field(ctx, None, field, &ty),
37+
is_field_access,
38+
);
39+
4040
complete_methods(ctx, receiver_ty, |func| acc.add_method(ctx, dot_access, func, None, None));
4141
}
4242

@@ -82,6 +82,7 @@ pub(crate) fn complete_undotted_self(
8282
)
8383
},
8484
|acc, field, ty| acc.add_tuple_field(ctx, Some(hir::known::SELF_PARAM), field, &ty),
85+
true,
8586
);
8687
complete_methods(ctx, &ty, |func| {
8788
acc.add_method(
@@ -104,18 +105,23 @@ fn complete_fields(
104105
receiver: &hir::Type,
105106
mut named_field: impl FnMut(&mut Completions, hir::Field, hir::Type),
106107
mut tuple_index: impl FnMut(&mut Completions, usize, hir::Type),
108+
is_field_access: bool,
107109
) {
108110
let mut seen_names = FxHashSet::default();
109111
for receiver in receiver.autoderef(ctx.db) {
110112
for (field, ty) in receiver.fields(ctx.db) {
111-
if seen_names.insert(field.name(ctx.db)) {
113+
if seen_names.insert(field.name(ctx.db))
114+
&& (is_field_access || ty.is_fn() || ty.is_closure())
115+
{
112116
named_field(acc, field, ty);
113117
}
114118
}
115119
for (i, ty) in receiver.tuple_fields(ctx.db).into_iter().enumerate() {
116120
// Tuples are always the last type in a deref chain, so just check if the name is
117121
// already seen without inserting into the hashset.
118-
if !seen_names.contains(&hir::Name::new_tuple_field(i)) {
122+
if !seen_names.contains(&hir::Name::new_tuple_field(i))
123+
&& (is_field_access || ty.is_fn() || ty.is_closure())
124+
{
119125
// Tuple fields are always public (tuple struct fields are handled above).
120126
tuple_index(acc, i, ty);
121127
}
@@ -250,7 +256,6 @@ impl A {
250256

251257
#[test]
252258
fn test_no_struct_field_completion_for_method_call() {
253-
cov_mark::check!(test_no_struct_field_completion_for_method_call);
254259
check(
255260
r#"
256261
struct A { the_field: u32 }
@@ -1172,4 +1177,63 @@ impl<B: Bar, F: core::ops::Deref<Target = B>> Foo<F> {
11721177
"#]],
11731178
);
11741179
}
1180+
1181+
#[test]
1182+
fn test_struct_function_field_completion() {
1183+
check(
1184+
r#"
1185+
struct S { va_field: u32, fn_field: fn() }
1186+
fn foo() { S { va_field: 0, fn_field: || {} }.fi$0() }
1187+
"#,
1188+
expect![[r#"
1189+
fd fn_field fn()
1190+
"#]],
1191+
);
1192+
1193+
check_edit(
1194+
"fn_field",
1195+
r#"
1196+
struct S { va_field: u32, fn_field: fn() }
1197+
fn foo() { S { va_field: 0, fn_field: || {} }.fi$0() }
1198+
"#,
1199+
r#"
1200+
struct S { va_field: u32, fn_field: fn() }
1201+
fn foo() { (S { va_field: 0, fn_field: || {} }.fn_field)() }
1202+
"#,
1203+
);
1204+
}
1205+
1206+
#[test]
1207+
fn test_tuple_function_field_completion() {
1208+
check(
1209+
r#"
1210+
struct B(u32, fn())
1211+
fn foo() {
1212+
let b = B(0, || {});
1213+
b.$0()
1214+
}
1215+
"#,
1216+
expect![[r#"
1217+
fd 1 fn()
1218+
"#]],
1219+
);
1220+
1221+
check_edit(
1222+
"1",
1223+
r#"
1224+
struct B(u32, fn())
1225+
fn foo() {
1226+
let b = B(0, || {});
1227+
b.$0()
1228+
}
1229+
"#,
1230+
r#"
1231+
struct B(u32, fn())
1232+
fn foo() {
1233+
let b = B(0, || {});
1234+
(b.1)()
1235+
}
1236+
"#,
1237+
)
1238+
}
11751239
}

crates/ide-completion/src/render.rs

+107-9
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ use ide_db::{
1818
RootDatabase, SnippetCap, SymbolKind,
1919
};
2020
use syntax::{AstNode, SmolStr, SyntaxKind, TextRange};
21+
use text_edit::TextEdit;
2122

2223
use crate::{
23-
context::{DotAccess, PathCompletionCtx, PathKind, PatternContext},
24+
context::{DotAccess, DotAccessKind, PathCompletionCtx, PathKind, PatternContext},
2425
item::{Builder, CompletionRelevanceTypeMatch},
2526
render::{
2627
function::render_fn,
@@ -147,7 +148,42 @@ pub(crate) fn render_field(
147148
.set_documentation(field.docs(db))
148149
.set_deprecated(is_deprecated)
149150
.lookup_by(name);
150-
item.insert_text(field_with_receiver(db, receiver.as_ref(), &escaped_name));
151+
152+
let is_field_access = matches!(dot_access.kind, DotAccessKind::Field { .. });
153+
if !is_field_access || ty.is_fn() || ty.is_closure() {
154+
let mut builder = TextEdit::builder();
155+
// Using TextEdit, insert '(' before the struct name and ')' before the
156+
// dot access, then comes the field name and optionally insert function
157+
// call parens.
158+
159+
builder.replace(
160+
ctx.source_range(),
161+
field_with_receiver(db, receiver.as_ref(), &escaped_name).into(),
162+
);
163+
164+
let expected_fn_type =
165+
ctx.completion.expected_type.as_ref().is_some_and(|ty| ty.is_fn() || ty.is_closure());
166+
167+
if !expected_fn_type {
168+
if let Some(receiver) = &dot_access.receiver {
169+
if let Some(receiver) = ctx.completion.sema.original_ast_node(receiver.clone()) {
170+
builder.insert(receiver.syntax().text_range().start(), "(".to_string());
171+
builder.insert(ctx.source_range().end(), ")".to_string());
172+
}
173+
}
174+
175+
let is_parens_needed =
176+
!matches!(dot_access.kind, DotAccessKind::Method { has_parens: true });
177+
178+
if is_parens_needed {
179+
builder.insert(ctx.source_range().end(), "()".to_string());
180+
}
181+
}
182+
183+
item.text_edit(builder.finish());
184+
} else {
185+
item.insert_text(field_with_receiver(db, receiver.as_ref(), &escaped_name));
186+
}
151187
if let Some(receiver) = &dot_access.receiver {
152188
if let Some(original) = ctx.completion.sema.original_ast_node(receiver.clone()) {
153189
if let Some(ref_match) = compute_ref_match(ctx.completion, ty) {
@@ -1600,7 +1636,7 @@ fn main() {
16001636
fn struct_field_method_ref() {
16011637
check_kinds(
16021638
r#"
1603-
struct Foo { bar: u32 }
1639+
struct Foo { bar: u32, qux: fn() }
16041640
impl Foo { fn baz(&self) -> u32 { 0 } }
16051641
16061642
fn foo(f: Foo) { let _: &u32 = f.b$0 }
@@ -1610,30 +1646,92 @@ fn foo(f: Foo) { let _: &u32 = f.b$0 }
16101646
[
16111647
CompletionItem {
16121648
label: "baz()",
1613-
source_range: 98..99,
1614-
delete: 98..99,
1649+
source_range: 109..110,
1650+
delete: 109..110,
16151651
insert: "baz()$0",
16161652
kind: Method,
16171653
lookup: "baz",
16181654
detail: "fn(&self) -> u32",
1619-
ref_match: "&@96",
1655+
ref_match: "&@107",
16201656
},
16211657
CompletionItem {
16221658
label: "bar",
1623-
source_range: 98..99,
1624-
delete: 98..99,
1659+
source_range: 109..110,
1660+
delete: 109..110,
16251661
insert: "bar",
16261662
kind: SymbolKind(
16271663
Field,
16281664
),
16291665
detail: "u32",
1630-
ref_match: "&@96",
1666+
ref_match: "&@107",
1667+
},
1668+
CompletionItem {
1669+
label: "qux",
1670+
source_range: 109..110,
1671+
text_edit: TextEdit {
1672+
indels: [
1673+
Indel {
1674+
insert: "(",
1675+
delete: 107..107,
1676+
},
1677+
Indel {
1678+
insert: "qux)()",
1679+
delete: 109..110,
1680+
},
1681+
],
1682+
},
1683+
kind: SymbolKind(
1684+
Field,
1685+
),
1686+
detail: "fn()",
16311687
},
16321688
]
16331689
"#]],
16341690
);
16351691
}
16361692

1693+
#[test]
1694+
fn expected_fn_type_ref() {
1695+
check_kinds(
1696+
r#"
1697+
struct S { field: fn() }
1698+
1699+
fn foo() {
1700+
let foo: fn() = S { fields: || {}}.fi$0;
1701+
}
1702+
"#,
1703+
&[CompletionItemKind::SymbolKind(SymbolKind::Field)],
1704+
expect![[r#"
1705+
[
1706+
CompletionItem {
1707+
label: "field",
1708+
source_range: 76..78,
1709+
delete: 76..78,
1710+
insert: "field",
1711+
kind: SymbolKind(
1712+
Field,
1713+
),
1714+
detail: "fn()",
1715+
relevance: CompletionRelevance {
1716+
exact_name_match: false,
1717+
type_match: Some(
1718+
Exact,
1719+
),
1720+
is_local: false,
1721+
is_item_from_trait: false,
1722+
is_name_already_imported: false,
1723+
requires_import: false,
1724+
is_op_method: false,
1725+
is_private_editable: false,
1726+
postfix_match: None,
1727+
is_definite: false,
1728+
},
1729+
},
1730+
]
1731+
"#]],
1732+
)
1733+
}
1734+
16371735
#[test]
16381736
fn qualified_path_ref() {
16391737
check_kinds(

0 commit comments

Comments
 (0)