Skip to content

Commit 63acf72

Browse files
bors[bot]Jonas Schievink
andauthored
Merge #11938
11938: feat: improve assoc. item completion in trait impls r=jonas-schievink a=jonas-schievink Account for macro-generated items, increase the score of these completions since they're very relevant, and allow them to trigger when the cursor is directly in the assoc. item list without requiring further input. ![screenshot-2022-04-08-18:12:06](https://user-images.githubusercontent.com/1786438/162481277-2a0d2f21-dc20-4452-804d-6370766216b6.png) Part of #11860 bors r+ Co-authored-by: Jonas Schievink <jonas.schievink@ferrous-systems.com>
2 parents 847c552 + 22b13c8 commit 63acf72

File tree

5 files changed

+173
-91
lines changed

5 files changed

+173
-91
lines changed

crates/ide_completion/src/completions/trait_impl.rs

Lines changed: 126 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,13 @@ use ide_db::{path_transform::PathTransform, traits::get_missing_assoc_items, Sym
3636
use syntax::{
3737
ast::{self, edit_in_place::AttrsOwnerEdit},
3838
display::function_declaration,
39-
AstNode, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, T,
39+
AstNode, SyntaxElement, SyntaxKind, SyntaxNode, TextRange, T,
4040
};
4141
use text_edit::TextEdit;
4242

43-
use crate::{CompletionContext, CompletionItem, CompletionItemKind, Completions};
43+
use crate::{
44+
CompletionContext, CompletionItem, CompletionItemKind, CompletionRelevance, Completions,
45+
};
4446

4547
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
4648
enum ImplCompletionKind {
@@ -51,84 +53,102 @@ enum ImplCompletionKind {
5153
}
5254

5355
pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext) {
54-
if let Some((kind, trigger, impl_def)) = completion_match(ctx.token.clone()) {
56+
if let Some((kind, replacement_range, impl_def)) = completion_match(ctx) {
5557
if let Some(hir_impl) = ctx.sema.to_def(&impl_def) {
5658
get_missing_assoc_items(&ctx.sema, &impl_def).into_iter().for_each(|item| {
5759
match (item, kind) {
5860
(
5961
hir::AssocItem::Function(fn_item),
6062
ImplCompletionKind::All | ImplCompletionKind::Fn,
61-
) => add_function_impl(acc, ctx, &trigger, fn_item, hir_impl),
63+
) => add_function_impl(acc, ctx, replacement_range, fn_item, hir_impl),
6264
(
6365
hir::AssocItem::TypeAlias(type_item),
6466
ImplCompletionKind::All | ImplCompletionKind::TypeAlias,
65-
) => add_type_alias_impl(acc, ctx, &trigger, type_item),
67+
) => add_type_alias_impl(acc, ctx, replacement_range, type_item),
6668
(
6769
hir::AssocItem::Const(const_item),
6870
ImplCompletionKind::All | ImplCompletionKind::Const,
69-
) => add_const_impl(acc, ctx, &trigger, const_item, hir_impl),
71+
) => add_const_impl(acc, ctx, replacement_range, const_item, hir_impl),
7072
_ => {}
7173
}
7274
});
7375
}
7476
}
7577
}
7678

77-
fn completion_match(mut token: SyntaxToken) -> Option<(ImplCompletionKind, SyntaxNode, ast::Impl)> {
79+
fn completion_match(ctx: &CompletionContext) -> Option<(ImplCompletionKind, TextRange, ast::Impl)> {
80+
let token = ctx.token.clone();
81+
7882
// For keyword without name like `impl .. { fn $0 }`, the current position is inside
7983
// the whitespace token, which is outside `FN` syntax node.
8084
// We need to follow the previous token in this case.
85+
let mut token_before_ws = token.clone();
8186
if token.kind() == SyntaxKind::WHITESPACE {
82-
token = token.prev_token()?;
87+
token_before_ws = token.prev_token()?;
8388
}
8489

85-
let parent_kind = token.parent().map_or(SyntaxKind::EOF, |it| it.kind());
86-
let impl_item_offset = match token.kind() {
87-
// `impl .. { const $0 }`
88-
// ERROR 0
89-
// CONST_KW <- *
90-
T![const] => 0,
91-
// `impl .. { fn/type $0 }`
92-
// FN/TYPE_ALIAS 0
93-
// FN_KW <- *
94-
T![fn] | T![type] => 0,
95-
// `impl .. { fn/type/const foo$0 }`
96-
// FN/TYPE_ALIAS/CONST 1
97-
// NAME 0
98-
// IDENT <- *
99-
SyntaxKind::IDENT if parent_kind == SyntaxKind::NAME => 1,
100-
// `impl .. { foo$0 }`
101-
// MACRO_CALL 3
102-
// PATH 2
103-
// PATH_SEGMENT 1
104-
// NAME_REF 0
105-
// IDENT <- *
106-
SyntaxKind::IDENT if parent_kind == SyntaxKind::NAME_REF => 3,
107-
_ => return None,
108-
};
90+
let parent_kind = token_before_ws.parent().map_or(SyntaxKind::EOF, |it| it.kind());
91+
if token.parent().map(|n| n.kind()) == Some(SyntaxKind::ASSOC_ITEM_LIST)
92+
&& matches!(
93+
token_before_ws.kind(),
94+
SyntaxKind::SEMICOLON | SyntaxKind::R_CURLY | SyntaxKind::L_CURLY
95+
)
96+
{
97+
let impl_def = ast::Impl::cast(token.parent()?.parent()?)?;
98+
let kind = ImplCompletionKind::All;
99+
let replacement_range = TextRange::empty(ctx.position.offset);
100+
Some((kind, replacement_range, impl_def))
101+
} else {
102+
let impl_item_offset = match token_before_ws.kind() {
103+
// `impl .. { const $0 }`
104+
// ERROR 0
105+
// CONST_KW <- *
106+
T![const] => 0,
107+
// `impl .. { fn/type $0 }`
108+
// FN/TYPE_ALIAS 0
109+
// FN_KW <- *
110+
T![fn] | T![type] => 0,
111+
// `impl .. { fn/type/const foo$0 }`
112+
// FN/TYPE_ALIAS/CONST 1
113+
// NAME 0
114+
// IDENT <- *
115+
SyntaxKind::IDENT if parent_kind == SyntaxKind::NAME => 1,
116+
// `impl .. { foo$0 }`
117+
// MACRO_CALL 3
118+
// PATH 2
119+
// PATH_SEGMENT 1
120+
// NAME_REF 0
121+
// IDENT <- *
122+
SyntaxKind::IDENT if parent_kind == SyntaxKind::NAME_REF => 3,
123+
_ => return None,
124+
};
109125

110-
let impl_item = token.ancestors().nth(impl_item_offset)?;
111-
// Must directly belong to an impl block.
112-
// IMPL
113-
// ASSOC_ITEM_LIST
114-
// <item>
115-
let impl_def = ast::Impl::cast(impl_item.parent()?.parent()?)?;
116-
let kind = match impl_item.kind() {
117-
// `impl ... { const $0 fn/type/const }`
118-
_ if token.kind() == T![const] => ImplCompletionKind::Const,
119-
SyntaxKind::CONST | SyntaxKind::ERROR => ImplCompletionKind::Const,
120-
SyntaxKind::TYPE_ALIAS => ImplCompletionKind::TypeAlias,
121-
SyntaxKind::FN => ImplCompletionKind::Fn,
122-
SyntaxKind::MACRO_CALL => ImplCompletionKind::All,
123-
_ => return None,
124-
};
125-
Some((kind, impl_item, impl_def))
126+
let impl_item = token_before_ws.ancestors().nth(impl_item_offset)?;
127+
// Must directly belong to an impl block.
128+
// IMPL
129+
// ASSOC_ITEM_LIST
130+
// <item>
131+
let impl_def = ast::Impl::cast(impl_item.parent()?.parent()?)?;
132+
let kind = match impl_item.kind() {
133+
// `impl ... { const $0 fn/type/const }`
134+
_ if token_before_ws.kind() == T![const] => ImplCompletionKind::Const,
135+
SyntaxKind::CONST | SyntaxKind::ERROR => ImplCompletionKind::Const,
136+
SyntaxKind::TYPE_ALIAS => ImplCompletionKind::TypeAlias,
137+
SyntaxKind::FN => ImplCompletionKind::Fn,
138+
SyntaxKind::MACRO_CALL => ImplCompletionKind::All,
139+
_ => return None,
140+
};
141+
142+
let replacement_range = replacement_range(ctx, &impl_item);
143+
144+
Some((kind, replacement_range, impl_def))
145+
}
126146
}
127147

128148
fn add_function_impl(
129149
acc: &mut Completions,
130150
ctx: &CompletionContext,
131-
fn_def_node: &SyntaxNode,
151+
replacement_range: TextRange,
132152
func: hir::Function,
133153
impl_def: hir::Impl,
134154
) {
@@ -146,9 +166,10 @@ fn add_function_impl(
146166
CompletionItemKind::SymbolKind(SymbolKind::Function)
147167
};
148168

149-
let range = replacement_range(ctx, fn_def_node);
150-
let mut item = CompletionItem::new(completion_kind, range, label);
151-
item.lookup_by(fn_name).set_documentation(func.docs(ctx.db));
169+
let mut item = CompletionItem::new(completion_kind, replacement_range, label);
170+
item.lookup_by(fn_name)
171+
.set_documentation(func.docs(ctx.db))
172+
.set_relevance(CompletionRelevance { is_item_from_trait: true, ..Default::default() });
152173

153174
if let Some(source) = ctx.sema.source(func) {
154175
let assoc_item = ast::AssocItem::Fn(source.value);
@@ -162,11 +183,11 @@ fn add_function_impl(
162183
match ctx.config.snippet_cap {
163184
Some(cap) => {
164185
let snippet = format!("{} {{\n $0\n}}", function_decl);
165-
item.snippet_edit(cap, TextEdit::replace(range, snippet));
186+
item.snippet_edit(cap, TextEdit::replace(replacement_range, snippet));
166187
}
167188
None => {
168189
let header = format!("{} {{", function_decl);
169-
item.text_edit(TextEdit::replace(range, header));
190+
item.text_edit(TextEdit::replace(replacement_range, header));
170191
}
171192
};
172193
item.add_to(acc);
@@ -201,25 +222,26 @@ fn get_transformed_assoc_item(
201222
fn add_type_alias_impl(
202223
acc: &mut Completions,
203224
ctx: &CompletionContext,
204-
type_def_node: &SyntaxNode,
225+
replacement_range: TextRange,
205226
type_alias: hir::TypeAlias,
206227
) {
207228
let alias_name = type_alias.name(ctx.db).to_smol_str();
208229

230+
let label = format!("type {} =", alias_name);
209231
let snippet = format!("type {} = ", alias_name);
210232

211-
let range = replacement_range(ctx, type_def_node);
212-
let mut item = CompletionItem::new(SymbolKind::TypeAlias, range, &snippet);
213-
item.text_edit(TextEdit::replace(range, snippet))
233+
let mut item = CompletionItem::new(SymbolKind::TypeAlias, replacement_range, label);
234+
item.text_edit(TextEdit::replace(replacement_range, snippet))
214235
.lookup_by(alias_name)
215-
.set_documentation(type_alias.docs(ctx.db));
236+
.set_documentation(type_alias.docs(ctx.db))
237+
.set_relevance(CompletionRelevance { is_item_from_trait: true, ..Default::default() });
216238
item.add_to(acc);
217239
}
218240

219241
fn add_const_impl(
220242
acc: &mut Completions,
221243
ctx: &CompletionContext,
222-
const_def_node: &SyntaxNode,
244+
replacement_range: TextRange,
223245
const_: hir::Const,
224246
impl_def: hir::Impl,
225247
) {
@@ -234,13 +256,17 @@ fn add_const_impl(
234256
_ => unreachable!(),
235257
};
236258

237-
let snippet = make_const_compl_syntax(&transformed_const);
259+
let label = make_const_compl_syntax(&transformed_const);
260+
let snippet = format!("{} ", label);
238261

239-
let range = replacement_range(ctx, const_def_node);
240-
let mut item = CompletionItem::new(SymbolKind::Const, range, &snippet);
241-
item.text_edit(TextEdit::replace(range, snippet))
262+
let mut item = CompletionItem::new(SymbolKind::Const, replacement_range, label);
263+
item.text_edit(TextEdit::replace(replacement_range, snippet))
242264
.lookup_by(const_name)
243-
.set_documentation(const_.docs(ctx.db));
265+
.set_documentation(const_.docs(ctx.db))
266+
.set_relevance(CompletionRelevance {
267+
is_item_from_trait: true,
268+
..Default::default()
269+
});
244270
item.add_to(acc);
245271
}
246272
}
@@ -267,7 +293,7 @@ fn make_const_compl_syntax(const_: &ast::Const) -> String {
267293

268294
let syntax = const_.syntax().text().slice(range).to_string();
269295

270-
format!("{} = ", syntax.trim_end())
296+
format!("{} =", syntax.trim_end())
271297
}
272298

273299
fn replacement_range(ctx: &CompletionContext, item: &SyntaxNode) -> TextRange {
@@ -987,4 +1013,38 @@ where Self: SomeTrait<u32> {
9871013
"#,
9881014
)
9891015
}
1016+
1017+
#[test]
1018+
fn works_directly_in_impl() {
1019+
check(
1020+
r#"
1021+
trait Tr {
1022+
fn required();
1023+
}
1024+
1025+
impl Tr for () {
1026+
$0
1027+
}
1028+
"#,
1029+
expect![[r#"
1030+
fn fn required()
1031+
"#]],
1032+
);
1033+
check(
1034+
r#"
1035+
trait Tr {
1036+
fn provided() {}
1037+
fn required();
1038+
}
1039+
1040+
impl Tr for () {
1041+
fn provided() {}
1042+
$0
1043+
}
1044+
"#,
1045+
expect![[r#"
1046+
fn fn required()
1047+
"#]],
1048+
);
1049+
}
9901050
}

crates/ide_completion/src/item.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ pub struct CompletionRelevance {
132132
/// }
133133
/// ```
134134
pub is_local: bool,
135+
/// This is set when trait items are completed in an impl of that trait.
136+
pub is_item_from_trait: bool,
135137
/// Set for method completions of the `core::ops` and `core::cmp` family.
136138
pub is_op_method: bool,
137139
/// Set for item completions that are private but in the workspace.
@@ -197,6 +199,7 @@ impl CompletionRelevance {
197199
exact_name_match,
198200
type_match,
199201
is_local,
202+
is_item_from_trait,
200203
is_op_method,
201204
is_private_editable,
202205
postfix_match,
@@ -228,6 +231,9 @@ impl CompletionRelevance {
228231
if is_local {
229232
score += 1;
230233
}
234+
if is_item_from_trait {
235+
score += 1;
236+
}
231237
if is_definite {
232238
score += 10;
233239
}

crates/ide_completion/src/render.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,7 @@ fn main() { let _: m::Spam = S$0 }
624624
Exact,
625625
),
626626
is_local: false,
627+
is_item_from_trait: false,
627628
is_op_method: false,
628629
is_private_editable: false,
629630
postfix_match: None,
@@ -646,6 +647,7 @@ fn main() { let _: m::Spam = S$0 }
646647
Exact,
647648
),
648649
is_local: false,
650+
is_item_from_trait: false,
649651
is_op_method: false,
650652
is_private_editable: false,
651653
postfix_match: None,
@@ -734,6 +736,7 @@ fn foo() { A { the$0 } }
734736
CouldUnify,
735737
),
736738
is_local: false,
739+
is_item_from_trait: false,
737740
is_op_method: false,
738741
is_private_editable: false,
739742
postfix_match: None,

crates/ide_completion/src/tests/item_list.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,11 +241,14 @@ impl Test for () {
241241
kw fn
242242
kw const
243243
kw type
244+
ta type Type1 =
245+
ct const CONST1: () =
246+
fn fn function1()
244247
kw self
245248
kw super
246249
kw crate
247250
md module
248-
ma makro!(…) macro_rules! makro
251+
ma makro!(…) macro_rules! makro
249252
"#]],
250253
);
251254
}

0 commit comments

Comments
 (0)