Skip to content

Commit bd037c5

Browse files
bors[bot]Jonas Schievink
and
Jonas Schievink
authored
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 <[email protected]>
2 parents 847c552 + 72eae75 commit bd037c5

File tree

4 files changed

+106
-48
lines changed

4 files changed

+106
-48
lines changed

crates/ide_completion/src/completions/trait_impl.rs

Lines changed: 63 additions & 24 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,30 +53,40 @@ 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 mut token = ctx.token.clone();
81+
82+
let parent_kind = token.parent().map_or(SyntaxKind::EOF, |it| it.kind());
83+
if parent_kind == SyntaxKind::ASSOC_ITEM_LIST {
84+
let impl_def = ast::Impl::cast(token.parent()?.parent()?)?;
85+
let kind = ImplCompletionKind::All;
86+
let replacement_range = TextRange::empty(ctx.position.offset);
87+
return Some((kind, replacement_range, impl_def));
88+
}
89+
7890
// For keyword without name like `impl .. { fn $0 }`, the current position is inside
7991
// the whitespace token, which is outside `FN` syntax node.
8092
// We need to follow the previous token in this case.
@@ -122,13 +134,16 @@ fn completion_match(mut token: SyntaxToken) -> Option<(ImplCompletionKind, Synta
122134
SyntaxKind::MACRO_CALL => ImplCompletionKind::All,
123135
_ => return None,
124136
};
125-
Some((kind, impl_item, impl_def))
137+
138+
let replacement_range = replacement_range(ctx, &impl_item);
139+
140+
Some((kind, replacement_range, impl_def))
126141
}
127142

128143
fn add_function_impl(
129144
acc: &mut Completions,
130145
ctx: &CompletionContext,
131-
fn_def_node: &SyntaxNode,
146+
replacement_range: TextRange,
132147
func: hir::Function,
133148
impl_def: hir::Impl,
134149
) {
@@ -146,9 +161,10 @@ fn add_function_impl(
146161
CompletionItemKind::SymbolKind(SymbolKind::Function)
147162
};
148163

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));
164+
let mut item = CompletionItem::new(completion_kind, replacement_range, label);
165+
item.lookup_by(fn_name)
166+
.set_documentation(func.docs(ctx.db))
167+
.set_relevance(CompletionRelevance { is_item_from_trait: true, ..Default::default() });
152168

153169
if let Some(source) = ctx.sema.source(func) {
154170
let assoc_item = ast::AssocItem::Fn(source.value);
@@ -162,11 +178,11 @@ fn add_function_impl(
162178
match ctx.config.snippet_cap {
163179
Some(cap) => {
164180
let snippet = format!("{} {{\n $0\n}}", function_decl);
165-
item.snippet_edit(cap, TextEdit::replace(range, snippet));
181+
item.snippet_edit(cap, TextEdit::replace(replacement_range, snippet));
166182
}
167183
None => {
168184
let header = format!("{} {{", function_decl);
169-
item.text_edit(TextEdit::replace(range, header));
185+
item.text_edit(TextEdit::replace(replacement_range, header));
170186
}
171187
};
172188
item.add_to(acc);
@@ -201,25 +217,25 @@ fn get_transformed_assoc_item(
201217
fn add_type_alias_impl(
202218
acc: &mut Completions,
203219
ctx: &CompletionContext,
204-
type_def_node: &SyntaxNode,
220+
replacement_range: TextRange,
205221
type_alias: hir::TypeAlias,
206222
) {
207223
let alias_name = type_alias.name(ctx.db).to_smol_str();
208224

209225
let snippet = format!("type {} = ", alias_name);
210226

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))
227+
let mut item = CompletionItem::new(SymbolKind::TypeAlias, replacement_range, &snippet);
228+
item.text_edit(TextEdit::replace(replacement_range, snippet))
214229
.lookup_by(alias_name)
215-
.set_documentation(type_alias.docs(ctx.db));
230+
.set_documentation(type_alias.docs(ctx.db))
231+
.set_relevance(CompletionRelevance { is_item_from_trait: true, ..Default::default() });
216232
item.add_to(acc);
217233
}
218234

219235
fn add_const_impl(
220236
acc: &mut Completions,
221237
ctx: &CompletionContext,
222-
const_def_node: &SyntaxNode,
238+
replacement_range: TextRange,
223239
const_: hir::Const,
224240
impl_def: hir::Impl,
225241
) {
@@ -236,11 +252,14 @@ fn add_const_impl(
236252

237253
let snippet = make_const_compl_syntax(&transformed_const);
238254

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))
255+
let mut item = CompletionItem::new(SymbolKind::Const, replacement_range, &snippet);
256+
item.text_edit(TextEdit::replace(replacement_range, snippet))
242257
.lookup_by(const_name)
243-
.set_documentation(const_.docs(ctx.db));
258+
.set_documentation(const_.docs(ctx.db))
259+
.set_relevance(CompletionRelevance {
260+
is_item_from_trait: true,
261+
..Default::default()
262+
});
244263
item.add_to(acc);
245264
}
246265
}
@@ -987,4 +1006,24 @@ where Self: SomeTrait<u32> {
9871006
"#,
9881007
)
9891008
}
1009+
1010+
#[test]
1011+
fn works_directly_in_impl() {
1012+
check(
1013+
r#"
1014+
trait Tr {
1015+
fn provided() {}
1016+
fn required();
1017+
}
1018+
1019+
impl Tr for () {
1020+
fn provided() {}
1021+
$0
1022+
}
1023+
"#,
1024+
expect![[r#"
1025+
fn fn required()
1026+
"#]],
1027+
);
1028+
}
9901029
}

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_db/src/traits.rs

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@
33
use crate::RootDatabase;
44
use hir::Semantics;
55
use rustc_hash::FxHashSet;
6-
use syntax::{
7-
ast::{self, HasName},
8-
AstNode,
9-
};
6+
use syntax::{ast, AstNode};
107

118
/// Given the `impl` block, attempts to find the trait this `impl` corresponds to.
129
pub fn resolve_target_trait(
@@ -28,32 +25,28 @@ pub fn get_missing_assoc_items(
2825
sema: &Semantics<RootDatabase>,
2926
impl_def: &ast::Impl,
3027
) -> Vec<hir::AssocItem> {
28+
let imp = match sema.to_def(impl_def) {
29+
Some(it) => it,
30+
None => return vec![],
31+
};
32+
3133
// Names must be unique between constants and functions. However, type aliases
3234
// may share the same name as a function or constant.
3335
let mut impl_fns_consts = FxHashSet::default();
3436
let mut impl_type = FxHashSet::default();
3537

36-
if let Some(item_list) = impl_def.assoc_item_list() {
37-
for item in item_list.assoc_items() {
38-
match item {
39-
ast::AssocItem::Fn(f) => {
40-
if let Some(n) = f.name() {
41-
impl_fns_consts.insert(n.syntax().to_string());
42-
}
43-
}
44-
45-
ast::AssocItem::TypeAlias(t) => {
46-
if let Some(n) = t.name() {
47-
impl_type.insert(n.syntax().to_string());
48-
}
49-
}
50-
51-
ast::AssocItem::Const(c) => {
52-
if let Some(n) = c.name() {
53-
impl_fns_consts.insert(n.syntax().to_string());
54-
}
38+
for item in imp.items(sema.db) {
39+
match item {
40+
hir::AssocItem::Function(it) => {
41+
impl_fns_consts.insert(it.name(sema.db).to_string());
42+
}
43+
hir::AssocItem::Const(it) => {
44+
if let Some(name) = it.name(sema.db) {
45+
impl_fns_consts.insert(name.to_string());
5546
}
56-
ast::AssocItem::MacroCall(_) => (),
47+
}
48+
hir::AssocItem::TypeAlias(it) => {
49+
impl_type.insert(it.name(sema.db).to_string());
5750
}
5851
}
5952
}
@@ -219,5 +212,22 @@ impl Foo {
219212
}"#,
220213
expect![[r#""#]],
221214
);
215+
216+
check_missing_assoc(
217+
r#"
218+
trait Tr {
219+
fn required();
220+
}
221+
macro_rules! m {
222+
() => { fn required() {} };
223+
}
224+
impl Tr for () {
225+
m!();
226+
$0
227+
}
228+
229+
"#,
230+
expect![[r#""#]],
231+
);
222232
}
223233
}

0 commit comments

Comments
 (0)