Skip to content

Commit 2474f42

Browse files
Merge #4270
4270: Improve derive macro completion r=edwin0cheng a=SomeoneToIgnore * Adds completions for standard derive macros (considering their dependencies on each other, so we don't get compile errors) * Adds completions for custom derive macros that are in scope, if the proc macro feature is enabled in the settings * Separates macro completion from other completions to avoid incorrect completion propositions Co-authored-by: Kirill Bulatov <mail4score@gmail.com>
2 parents 17bd79f + 2fd054f commit 2474f42

File tree

5 files changed

+308
-40
lines changed

5 files changed

+308
-40
lines changed

crates/ra_hir/src/code_model.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use hir_def::{
1919
use hir_expand::{
2020
diagnostics::DiagnosticSink,
2121
name::{name, AsName},
22-
MacroDefId,
22+
MacroDefId, MacroDefKind,
2323
};
2424
use hir_ty::{
2525
autoderef, display::HirFormatter, expr::ExprValidator, method_resolution, ApplicationTy,
@@ -762,13 +762,12 @@ impl MacroDef {
762762

763763
/// Indicate it is a proc-macro
764764
pub fn is_proc_macro(&self) -> bool {
765-
match self.id.kind {
766-
hir_expand::MacroDefKind::Declarative => false,
767-
hir_expand::MacroDefKind::BuiltIn(_) => false,
768-
hir_expand::MacroDefKind::BuiltInDerive(_) => false,
769-
hir_expand::MacroDefKind::BuiltInEager(_) => false,
770-
hir_expand::MacroDefKind::CustomDerive(_) => true,
771-
}
765+
matches!(self.id.kind, MacroDefKind::CustomDerive(_))
766+
}
767+
768+
/// Indicate it is a derive macro
769+
pub fn is_derive_macro(&self) -> bool {
770+
matches!(self.id.kind, MacroDefKind::CustomDerive(_) | MacroDefKind::BuiltInDerive(_))
772771
}
773772
}
774773

crates/ra_ide/src/completion.rs

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -65,21 +65,23 @@ pub(crate) fn completions(
6565
let ctx = CompletionContext::new(db, position, config)?;
6666

6767
let mut acc = Completions::default();
68-
69-
complete_fn_param::complete_fn_param(&mut acc, &ctx);
70-
complete_keyword::complete_expr_keyword(&mut acc, &ctx);
71-
complete_keyword::complete_use_tree_keyword(&mut acc, &ctx);
72-
complete_snippet::complete_expr_snippet(&mut acc, &ctx);
73-
complete_snippet::complete_item_snippet(&mut acc, &ctx);
74-
complete_qualified_path::complete_qualified_path(&mut acc, &ctx);
75-
complete_unqualified_path::complete_unqualified_path(&mut acc, &ctx);
76-
complete_dot::complete_dot(&mut acc, &ctx);
77-
complete_record::complete_record(&mut acc, &ctx);
78-
complete_pattern::complete_pattern(&mut acc, &ctx);
79-
complete_postfix::complete_postfix(&mut acc, &ctx);
80-
complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx);
81-
complete_trait_impl::complete_trait_impl(&mut acc, &ctx);
82-
complete_attribute::complete_attribute(&mut acc, &ctx);
68+
if ctx.attribute_under_caret.is_some() {
69+
complete_attribute::complete_attribute(&mut acc, &ctx);
70+
} else {
71+
complete_fn_param::complete_fn_param(&mut acc, &ctx);
72+
complete_keyword::complete_expr_keyword(&mut acc, &ctx);
73+
complete_keyword::complete_use_tree_keyword(&mut acc, &ctx);
74+
complete_snippet::complete_expr_snippet(&mut acc, &ctx);
75+
complete_snippet::complete_item_snippet(&mut acc, &ctx);
76+
complete_qualified_path::complete_qualified_path(&mut acc, &ctx);
77+
complete_unqualified_path::complete_unqualified_path(&mut acc, &ctx);
78+
complete_dot::complete_dot(&mut acc, &ctx);
79+
complete_record::complete_record(&mut acc, &ctx);
80+
complete_pattern::complete_pattern(&mut acc, &ctx);
81+
complete_postfix::complete_postfix(&mut acc, &ctx);
82+
complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx);
83+
complete_trait_impl::complete_trait_impl(&mut acc, &ctx);
84+
}
8385

8486
Some(acc)
8587
}

crates/ra_ide/src/completion/complete_attribute.rs

Lines changed: 280 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,26 @@
55
66
use super::completion_context::CompletionContext;
77
use super::completion_item::{CompletionItem, CompletionItemKind, CompletionKind, Completions};
8+
use ast::AttrInput;
89
use ra_syntax::{
9-
ast::{Attr, AttrKind},
10-
AstNode,
10+
ast::{self, AttrKind},
11+
AstNode, SyntaxKind,
1112
};
13+
use rustc_hash::FxHashSet;
1214

13-
pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) {
14-
if !ctx.is_attribute {
15-
return;
16-
}
15+
pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
16+
let attribute = ctx.attribute_under_caret.as_ref()?;
1717

18-
let is_inner = ctx
19-
.original_token
20-
.ancestors()
21-
.find_map(Attr::cast)
22-
.map(|attr| attr.kind() == AttrKind::Inner)
23-
.unwrap_or(false);
18+
match (attribute.path(), attribute.input()) {
19+
(Some(path), Some(AttrInput::TokenTree(token_tree))) if path.to_string() == "derive" => {
20+
complete_derive(acc, ctx, token_tree)
21+
}
22+
_ => complete_attribute_start(acc, ctx, attribute),
23+
}
24+
Some(())
25+
}
2426

27+
fn complete_attribute_start(acc: &mut Completions, ctx: &CompletionContext, attribute: &ast::Attr) {
2528
for attr_completion in ATTRIBUTES {
2629
let mut item = CompletionItem::new(
2730
CompletionKind::Attribute,
@@ -37,7 +40,7 @@ pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext)
3740
_ => {}
3841
}
3942

40-
if is_inner || !attr_completion.should_be_inner {
43+
if attribute.kind() == AttrKind::Inner || !attr_completion.should_be_inner {
4144
acc.add(item);
4245
}
4346
}
@@ -126,6 +129,106 @@ const ATTRIBUTES: &[AttrCompletion] = &[
126129
},
127130
];
128131

132+
fn complete_derive(acc: &mut Completions, ctx: &CompletionContext, derive_input: ast::TokenTree) {
133+
if let Ok(existing_derives) = parse_derive_input(derive_input) {
134+
for derive_completion in DEFAULT_DERIVE_COMPLETIONS
135+
.into_iter()
136+
.filter(|completion| !existing_derives.contains(completion.label))
137+
{
138+
let mut label = derive_completion.label.to_owned();
139+
for dependency in derive_completion
140+
.dependencies
141+
.into_iter()
142+
.filter(|&&dependency| !existing_derives.contains(dependency))
143+
{
144+
label.push_str(", ");
145+
label.push_str(dependency);
146+
}
147+
acc.add(
148+
CompletionItem::new(CompletionKind::Attribute, ctx.source_range(), label)
149+
.kind(CompletionItemKind::Attribute),
150+
);
151+
}
152+
153+
for custom_derive_name in get_derive_names_in_scope(ctx).difference(&existing_derives) {
154+
acc.add(
155+
CompletionItem::new(
156+
CompletionKind::Attribute,
157+
ctx.source_range(),
158+
custom_derive_name,
159+
)
160+
.kind(CompletionItemKind::Attribute),
161+
);
162+
}
163+
}
164+
}
165+
166+
fn parse_derive_input(derive_input: ast::TokenTree) -> Result<FxHashSet<String>, ()> {
167+
match (derive_input.left_delimiter_token(), derive_input.right_delimiter_token()) {
168+
(Some(left_paren), Some(right_paren))
169+
if left_paren.kind() == SyntaxKind::L_PAREN
170+
&& right_paren.kind() == SyntaxKind::R_PAREN =>
171+
{
172+
let mut input_derives = FxHashSet::default();
173+
let mut current_derive = String::new();
174+
for token in derive_input
175+
.syntax()
176+
.children_with_tokens()
177+
.filter_map(|token| token.into_token())
178+
.skip_while(|token| token != &left_paren)
179+
.skip(1)
180+
.take_while(|token| token != &right_paren)
181+
{
182+
if SyntaxKind::COMMA == token.kind() {
183+
if !current_derive.is_empty() {
184+
input_derives.insert(current_derive);
185+
current_derive = String::new();
186+
}
187+
} else {
188+
current_derive.push_str(token.to_string().trim());
189+
}
190+
}
191+
192+
if !current_derive.is_empty() {
193+
input_derives.insert(current_derive);
194+
}
195+
Ok(input_derives)
196+
}
197+
_ => Err(()),
198+
}
199+
}
200+
201+
fn get_derive_names_in_scope(ctx: &CompletionContext) -> FxHashSet<String> {
202+
let mut result = FxHashSet::default();
203+
ctx.scope().process_all_names(&mut |name, scope_def| {
204+
if let hir::ScopeDef::MacroDef(mac) = scope_def {
205+
if mac.is_derive_macro() {
206+
result.insert(name.to_string());
207+
}
208+
}
209+
});
210+
result
211+
}
212+
213+
struct DeriveCompletion {
214+
label: &'static str,
215+
dependencies: &'static [&'static str],
216+
}
217+
218+
/// Standard Rust derives and the information about their dependencies
219+
/// (the dependencies are needed so that the main derive don't break the compilation when added)
220+
const DEFAULT_DERIVE_COMPLETIONS: &[DeriveCompletion] = &[
221+
DeriveCompletion { label: "Clone", dependencies: &[] },
222+
DeriveCompletion { label: "Copy", dependencies: &["Clone"] },
223+
DeriveCompletion { label: "Debug", dependencies: &[] },
224+
DeriveCompletion { label: "Default", dependencies: &[] },
225+
DeriveCompletion { label: "Hash", dependencies: &[] },
226+
DeriveCompletion { label: "PartialEq", dependencies: &[] },
227+
DeriveCompletion { label: "Eq", dependencies: &["PartialEq"] },
228+
DeriveCompletion { label: "PartialOrd", dependencies: &["PartialEq"] },
229+
DeriveCompletion { label: "Ord", dependencies: &["PartialOrd", "Eq", "PartialEq"] },
230+
];
231+
129232
#[cfg(test)]
130233
mod tests {
131234
use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind};
@@ -135,6 +238,170 @@ mod tests {
135238
do_completion(code, CompletionKind::Attribute)
136239
}
137240

241+
#[test]
242+
fn empty_derive_completion() {
243+
assert_debug_snapshot!(
244+
do_attr_completion(
245+
r"
246+
#[derive(<|>)]
247+
struct Test {}
248+
",
249+
),
250+
@r###"
251+
[
252+
CompletionItem {
253+
label: "Clone",
254+
source_range: 30..30,
255+
delete: 30..30,
256+
insert: "Clone",
257+
kind: Attribute,
258+
},
259+
CompletionItem {
260+
label: "Copy, Clone",
261+
source_range: 30..30,
262+
delete: 30..30,
263+
insert: "Copy, Clone",
264+
kind: Attribute,
265+
},
266+
CompletionItem {
267+
label: "Debug",
268+
source_range: 30..30,
269+
delete: 30..30,
270+
insert: "Debug",
271+
kind: Attribute,
272+
},
273+
CompletionItem {
274+
label: "Default",
275+
source_range: 30..30,
276+
delete: 30..30,
277+
insert: "Default",
278+
kind: Attribute,
279+
},
280+
CompletionItem {
281+
label: "Eq, PartialEq",
282+
source_range: 30..30,
283+
delete: 30..30,
284+
insert: "Eq, PartialEq",
285+
kind: Attribute,
286+
},
287+
CompletionItem {
288+
label: "Hash",
289+
source_range: 30..30,
290+
delete: 30..30,
291+
insert: "Hash",
292+
kind: Attribute,
293+
},
294+
CompletionItem {
295+
label: "Ord, PartialOrd, Eq, PartialEq",
296+
source_range: 30..30,
297+
delete: 30..30,
298+
insert: "Ord, PartialOrd, Eq, PartialEq",
299+
kind: Attribute,
300+
},
301+
CompletionItem {
302+
label: "PartialEq",
303+
source_range: 30..30,
304+
delete: 30..30,
305+
insert: "PartialEq",
306+
kind: Attribute,
307+
},
308+
CompletionItem {
309+
label: "PartialOrd, PartialEq",
310+
source_range: 30..30,
311+
delete: 30..30,
312+
insert: "PartialOrd, PartialEq",
313+
kind: Attribute,
314+
},
315+
]
316+
"###
317+
);
318+
}
319+
320+
#[test]
321+
fn no_completion_for_incorrect_derive() {
322+
assert_debug_snapshot!(
323+
do_attr_completion(
324+
r"
325+
#[derive{<|>)]
326+
struct Test {}
327+
",
328+
),
329+
@"[]"
330+
);
331+
}
332+
333+
#[test]
334+
fn derive_with_input_completion() {
335+
assert_debug_snapshot!(
336+
do_attr_completion(
337+
r"
338+
#[derive(serde::Serialize, PartialEq, <|>)]
339+
struct Test {}
340+
",
341+
),
342+
@r###"
343+
[
344+
CompletionItem {
345+
label: "Clone",
346+
source_range: 59..59,
347+
delete: 59..59,
348+
insert: "Clone",
349+
kind: Attribute,
350+
},
351+
CompletionItem {
352+
label: "Copy, Clone",
353+
source_range: 59..59,
354+
delete: 59..59,
355+
insert: "Copy, Clone",
356+
kind: Attribute,
357+
},
358+
CompletionItem {
359+
label: "Debug",
360+
source_range: 59..59,
361+
delete: 59..59,
362+
insert: "Debug",
363+
kind: Attribute,
364+
},
365+
CompletionItem {
366+
label: "Default",
367+
source_range: 59..59,
368+
delete: 59..59,
369+
insert: "Default",
370+
kind: Attribute,
371+
},
372+
CompletionItem {
373+
label: "Eq",
374+
source_range: 59..59,
375+
delete: 59..59,
376+
insert: "Eq",
377+
kind: Attribute,
378+
},
379+
CompletionItem {
380+
label: "Hash",
381+
source_range: 59..59,
382+
delete: 59..59,
383+
insert: "Hash",
384+
kind: Attribute,
385+
},
386+
CompletionItem {
387+
label: "Ord, PartialOrd, Eq",
388+
source_range: 59..59,
389+
delete: 59..59,
390+
insert: "Ord, PartialOrd, Eq",
391+
kind: Attribute,
392+
},
393+
CompletionItem {
394+
label: "PartialOrd",
395+
source_range: 59..59,
396+
delete: 59..59,
397+
insert: "PartialOrd",
398+
kind: Attribute,
399+
},
400+
]
401+
"###
402+
);
403+
}
404+
138405
#[test]
139406
fn test_attribute_completion() {
140407
assert_debug_snapshot!(

0 commit comments

Comments
 (0)