mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-26 13:03:31 +00:00
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>
This commit is contained in:
commit
2474f42ae9
5 changed files with 308 additions and 40 deletions
|
@ -19,7 +19,7 @@ use hir_def::{
|
||||||
use hir_expand::{
|
use hir_expand::{
|
||||||
diagnostics::DiagnosticSink,
|
diagnostics::DiagnosticSink,
|
||||||
name::{name, AsName},
|
name::{name, AsName},
|
||||||
MacroDefId,
|
MacroDefId, MacroDefKind,
|
||||||
};
|
};
|
||||||
use hir_ty::{
|
use hir_ty::{
|
||||||
autoderef, display::HirFormatter, expr::ExprValidator, method_resolution, ApplicationTy,
|
autoderef, display::HirFormatter, expr::ExprValidator, method_resolution, ApplicationTy,
|
||||||
|
@ -762,13 +762,12 @@ impl MacroDef {
|
||||||
|
|
||||||
/// Indicate it is a proc-macro
|
/// Indicate it is a proc-macro
|
||||||
pub fn is_proc_macro(&self) -> bool {
|
pub fn is_proc_macro(&self) -> bool {
|
||||||
match self.id.kind {
|
matches!(self.id.kind, MacroDefKind::CustomDerive(_))
|
||||||
hir_expand::MacroDefKind::Declarative => false,
|
}
|
||||||
hir_expand::MacroDefKind::BuiltIn(_) => false,
|
|
||||||
hir_expand::MacroDefKind::BuiltInDerive(_) => false,
|
/// Indicate it is a derive macro
|
||||||
hir_expand::MacroDefKind::BuiltInEager(_) => false,
|
pub fn is_derive_macro(&self) -> bool {
|
||||||
hir_expand::MacroDefKind::CustomDerive(_) => true,
|
matches!(self.id.kind, MacroDefKind::CustomDerive(_) | MacroDefKind::BuiltInDerive(_))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,21 +65,23 @@ pub(crate) fn completions(
|
||||||
let ctx = CompletionContext::new(db, position, config)?;
|
let ctx = CompletionContext::new(db, position, config)?;
|
||||||
|
|
||||||
let mut acc = Completions::default();
|
let mut acc = Completions::default();
|
||||||
|
if ctx.attribute_under_caret.is_some() {
|
||||||
complete_fn_param::complete_fn_param(&mut acc, &ctx);
|
complete_attribute::complete_attribute(&mut acc, &ctx);
|
||||||
complete_keyword::complete_expr_keyword(&mut acc, &ctx);
|
} else {
|
||||||
complete_keyword::complete_use_tree_keyword(&mut acc, &ctx);
|
complete_fn_param::complete_fn_param(&mut acc, &ctx);
|
||||||
complete_snippet::complete_expr_snippet(&mut acc, &ctx);
|
complete_keyword::complete_expr_keyword(&mut acc, &ctx);
|
||||||
complete_snippet::complete_item_snippet(&mut acc, &ctx);
|
complete_keyword::complete_use_tree_keyword(&mut acc, &ctx);
|
||||||
complete_qualified_path::complete_qualified_path(&mut acc, &ctx);
|
complete_snippet::complete_expr_snippet(&mut acc, &ctx);
|
||||||
complete_unqualified_path::complete_unqualified_path(&mut acc, &ctx);
|
complete_snippet::complete_item_snippet(&mut acc, &ctx);
|
||||||
complete_dot::complete_dot(&mut acc, &ctx);
|
complete_qualified_path::complete_qualified_path(&mut acc, &ctx);
|
||||||
complete_record::complete_record(&mut acc, &ctx);
|
complete_unqualified_path::complete_unqualified_path(&mut acc, &ctx);
|
||||||
complete_pattern::complete_pattern(&mut acc, &ctx);
|
complete_dot::complete_dot(&mut acc, &ctx);
|
||||||
complete_postfix::complete_postfix(&mut acc, &ctx);
|
complete_record::complete_record(&mut acc, &ctx);
|
||||||
complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx);
|
complete_pattern::complete_pattern(&mut acc, &ctx);
|
||||||
complete_trait_impl::complete_trait_impl(&mut acc, &ctx);
|
complete_postfix::complete_postfix(&mut acc, &ctx);
|
||||||
complete_attribute::complete_attribute(&mut acc, &ctx);
|
complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx);
|
||||||
|
complete_trait_impl::complete_trait_impl(&mut acc, &ctx);
|
||||||
|
}
|
||||||
|
|
||||||
Some(acc)
|
Some(acc)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,23 +5,26 @@
|
||||||
|
|
||||||
use super::completion_context::CompletionContext;
|
use super::completion_context::CompletionContext;
|
||||||
use super::completion_item::{CompletionItem, CompletionItemKind, CompletionKind, Completions};
|
use super::completion_item::{CompletionItem, CompletionItemKind, CompletionKind, Completions};
|
||||||
|
use ast::AttrInput;
|
||||||
use ra_syntax::{
|
use ra_syntax::{
|
||||||
ast::{Attr, AttrKind},
|
ast::{self, AttrKind},
|
||||||
AstNode,
|
AstNode, SyntaxKind,
|
||||||
};
|
};
|
||||||
|
use rustc_hash::FxHashSet;
|
||||||
|
|
||||||
pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) {
|
pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
|
||||||
if !ctx.is_attribute {
|
let attribute = ctx.attribute_under_caret.as_ref()?;
|
||||||
return;
|
|
||||||
|
match (attribute.path(), attribute.input()) {
|
||||||
|
(Some(path), Some(AttrInput::TokenTree(token_tree))) if path.to_string() == "derive" => {
|
||||||
|
complete_derive(acc, ctx, token_tree)
|
||||||
|
}
|
||||||
|
_ => complete_attribute_start(acc, ctx, attribute),
|
||||||
}
|
}
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
|
||||||
let is_inner = ctx
|
fn complete_attribute_start(acc: &mut Completions, ctx: &CompletionContext, attribute: &ast::Attr) {
|
||||||
.original_token
|
|
||||||
.ancestors()
|
|
||||||
.find_map(Attr::cast)
|
|
||||||
.map(|attr| attr.kind() == AttrKind::Inner)
|
|
||||||
.unwrap_or(false);
|
|
||||||
|
|
||||||
for attr_completion in ATTRIBUTES {
|
for attr_completion in ATTRIBUTES {
|
||||||
let mut item = CompletionItem::new(
|
let mut item = CompletionItem::new(
|
||||||
CompletionKind::Attribute,
|
CompletionKind::Attribute,
|
||||||
|
@ -37,7 +40,7 @@ pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext)
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_inner || !attr_completion.should_be_inner {
|
if attribute.kind() == AttrKind::Inner || !attr_completion.should_be_inner {
|
||||||
acc.add(item);
|
acc.add(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -126,6 +129,106 @@ const ATTRIBUTES: &[AttrCompletion] = &[
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
fn complete_derive(acc: &mut Completions, ctx: &CompletionContext, derive_input: ast::TokenTree) {
|
||||||
|
if let Ok(existing_derives) = parse_derive_input(derive_input) {
|
||||||
|
for derive_completion in DEFAULT_DERIVE_COMPLETIONS
|
||||||
|
.into_iter()
|
||||||
|
.filter(|completion| !existing_derives.contains(completion.label))
|
||||||
|
{
|
||||||
|
let mut label = derive_completion.label.to_owned();
|
||||||
|
for dependency in derive_completion
|
||||||
|
.dependencies
|
||||||
|
.into_iter()
|
||||||
|
.filter(|&&dependency| !existing_derives.contains(dependency))
|
||||||
|
{
|
||||||
|
label.push_str(", ");
|
||||||
|
label.push_str(dependency);
|
||||||
|
}
|
||||||
|
acc.add(
|
||||||
|
CompletionItem::new(CompletionKind::Attribute, ctx.source_range(), label)
|
||||||
|
.kind(CompletionItemKind::Attribute),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for custom_derive_name in get_derive_names_in_scope(ctx).difference(&existing_derives) {
|
||||||
|
acc.add(
|
||||||
|
CompletionItem::new(
|
||||||
|
CompletionKind::Attribute,
|
||||||
|
ctx.source_range(),
|
||||||
|
custom_derive_name,
|
||||||
|
)
|
||||||
|
.kind(CompletionItemKind::Attribute),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_derive_input(derive_input: ast::TokenTree) -> Result<FxHashSet<String>, ()> {
|
||||||
|
match (derive_input.left_delimiter_token(), derive_input.right_delimiter_token()) {
|
||||||
|
(Some(left_paren), Some(right_paren))
|
||||||
|
if left_paren.kind() == SyntaxKind::L_PAREN
|
||||||
|
&& right_paren.kind() == SyntaxKind::R_PAREN =>
|
||||||
|
{
|
||||||
|
let mut input_derives = FxHashSet::default();
|
||||||
|
let mut current_derive = String::new();
|
||||||
|
for token in derive_input
|
||||||
|
.syntax()
|
||||||
|
.children_with_tokens()
|
||||||
|
.filter_map(|token| token.into_token())
|
||||||
|
.skip_while(|token| token != &left_paren)
|
||||||
|
.skip(1)
|
||||||
|
.take_while(|token| token != &right_paren)
|
||||||
|
{
|
||||||
|
if SyntaxKind::COMMA == token.kind() {
|
||||||
|
if !current_derive.is_empty() {
|
||||||
|
input_derives.insert(current_derive);
|
||||||
|
current_derive = String::new();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
current_derive.push_str(token.to_string().trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !current_derive.is_empty() {
|
||||||
|
input_derives.insert(current_derive);
|
||||||
|
}
|
||||||
|
Ok(input_derives)
|
||||||
|
}
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_derive_names_in_scope(ctx: &CompletionContext) -> FxHashSet<String> {
|
||||||
|
let mut result = FxHashSet::default();
|
||||||
|
ctx.scope().process_all_names(&mut |name, scope_def| {
|
||||||
|
if let hir::ScopeDef::MacroDef(mac) = scope_def {
|
||||||
|
if mac.is_derive_macro() {
|
||||||
|
result.insert(name.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DeriveCompletion {
|
||||||
|
label: &'static str,
|
||||||
|
dependencies: &'static [&'static str],
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Standard Rust derives and the information about their dependencies
|
||||||
|
/// (the dependencies are needed so that the main derive don't break the compilation when added)
|
||||||
|
const DEFAULT_DERIVE_COMPLETIONS: &[DeriveCompletion] = &[
|
||||||
|
DeriveCompletion { label: "Clone", dependencies: &[] },
|
||||||
|
DeriveCompletion { label: "Copy", dependencies: &["Clone"] },
|
||||||
|
DeriveCompletion { label: "Debug", dependencies: &[] },
|
||||||
|
DeriveCompletion { label: "Default", dependencies: &[] },
|
||||||
|
DeriveCompletion { label: "Hash", dependencies: &[] },
|
||||||
|
DeriveCompletion { label: "PartialEq", dependencies: &[] },
|
||||||
|
DeriveCompletion { label: "Eq", dependencies: &["PartialEq"] },
|
||||||
|
DeriveCompletion { label: "PartialOrd", dependencies: &["PartialEq"] },
|
||||||
|
DeriveCompletion { label: "Ord", dependencies: &["PartialOrd", "Eq", "PartialEq"] },
|
||||||
|
];
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind};
|
use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind};
|
||||||
|
@ -135,6 +238,170 @@ mod tests {
|
||||||
do_completion(code, CompletionKind::Attribute)
|
do_completion(code, CompletionKind::Attribute)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_derive_completion() {
|
||||||
|
assert_debug_snapshot!(
|
||||||
|
do_attr_completion(
|
||||||
|
r"
|
||||||
|
#[derive(<|>)]
|
||||||
|
struct Test {}
|
||||||
|
",
|
||||||
|
),
|
||||||
|
@r###"
|
||||||
|
[
|
||||||
|
CompletionItem {
|
||||||
|
label: "Clone",
|
||||||
|
source_range: 30..30,
|
||||||
|
delete: 30..30,
|
||||||
|
insert: "Clone",
|
||||||
|
kind: Attribute,
|
||||||
|
},
|
||||||
|
CompletionItem {
|
||||||
|
label: "Copy, Clone",
|
||||||
|
source_range: 30..30,
|
||||||
|
delete: 30..30,
|
||||||
|
insert: "Copy, Clone",
|
||||||
|
kind: Attribute,
|
||||||
|
},
|
||||||
|
CompletionItem {
|
||||||
|
label: "Debug",
|
||||||
|
source_range: 30..30,
|
||||||
|
delete: 30..30,
|
||||||
|
insert: "Debug",
|
||||||
|
kind: Attribute,
|
||||||
|
},
|
||||||
|
CompletionItem {
|
||||||
|
label: "Default",
|
||||||
|
source_range: 30..30,
|
||||||
|
delete: 30..30,
|
||||||
|
insert: "Default",
|
||||||
|
kind: Attribute,
|
||||||
|
},
|
||||||
|
CompletionItem {
|
||||||
|
label: "Eq, PartialEq",
|
||||||
|
source_range: 30..30,
|
||||||
|
delete: 30..30,
|
||||||
|
insert: "Eq, PartialEq",
|
||||||
|
kind: Attribute,
|
||||||
|
},
|
||||||
|
CompletionItem {
|
||||||
|
label: "Hash",
|
||||||
|
source_range: 30..30,
|
||||||
|
delete: 30..30,
|
||||||
|
insert: "Hash",
|
||||||
|
kind: Attribute,
|
||||||
|
},
|
||||||
|
CompletionItem {
|
||||||
|
label: "Ord, PartialOrd, Eq, PartialEq",
|
||||||
|
source_range: 30..30,
|
||||||
|
delete: 30..30,
|
||||||
|
insert: "Ord, PartialOrd, Eq, PartialEq",
|
||||||
|
kind: Attribute,
|
||||||
|
},
|
||||||
|
CompletionItem {
|
||||||
|
label: "PartialEq",
|
||||||
|
source_range: 30..30,
|
||||||
|
delete: 30..30,
|
||||||
|
insert: "PartialEq",
|
||||||
|
kind: Attribute,
|
||||||
|
},
|
||||||
|
CompletionItem {
|
||||||
|
label: "PartialOrd, PartialEq",
|
||||||
|
source_range: 30..30,
|
||||||
|
delete: 30..30,
|
||||||
|
insert: "PartialOrd, PartialEq",
|
||||||
|
kind: Attribute,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_completion_for_incorrect_derive() {
|
||||||
|
assert_debug_snapshot!(
|
||||||
|
do_attr_completion(
|
||||||
|
r"
|
||||||
|
#[derive{<|>)]
|
||||||
|
struct Test {}
|
||||||
|
",
|
||||||
|
),
|
||||||
|
@"[]"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn derive_with_input_completion() {
|
||||||
|
assert_debug_snapshot!(
|
||||||
|
do_attr_completion(
|
||||||
|
r"
|
||||||
|
#[derive(serde::Serialize, PartialEq, <|>)]
|
||||||
|
struct Test {}
|
||||||
|
",
|
||||||
|
),
|
||||||
|
@r###"
|
||||||
|
[
|
||||||
|
CompletionItem {
|
||||||
|
label: "Clone",
|
||||||
|
source_range: 59..59,
|
||||||
|
delete: 59..59,
|
||||||
|
insert: "Clone",
|
||||||
|
kind: Attribute,
|
||||||
|
},
|
||||||
|
CompletionItem {
|
||||||
|
label: "Copy, Clone",
|
||||||
|
source_range: 59..59,
|
||||||
|
delete: 59..59,
|
||||||
|
insert: "Copy, Clone",
|
||||||
|
kind: Attribute,
|
||||||
|
},
|
||||||
|
CompletionItem {
|
||||||
|
label: "Debug",
|
||||||
|
source_range: 59..59,
|
||||||
|
delete: 59..59,
|
||||||
|
insert: "Debug",
|
||||||
|
kind: Attribute,
|
||||||
|
},
|
||||||
|
CompletionItem {
|
||||||
|
label: "Default",
|
||||||
|
source_range: 59..59,
|
||||||
|
delete: 59..59,
|
||||||
|
insert: "Default",
|
||||||
|
kind: Attribute,
|
||||||
|
},
|
||||||
|
CompletionItem {
|
||||||
|
label: "Eq",
|
||||||
|
source_range: 59..59,
|
||||||
|
delete: 59..59,
|
||||||
|
insert: "Eq",
|
||||||
|
kind: Attribute,
|
||||||
|
},
|
||||||
|
CompletionItem {
|
||||||
|
label: "Hash",
|
||||||
|
source_range: 59..59,
|
||||||
|
delete: 59..59,
|
||||||
|
insert: "Hash",
|
||||||
|
kind: Attribute,
|
||||||
|
},
|
||||||
|
CompletionItem {
|
||||||
|
label: "Ord, PartialOrd, Eq",
|
||||||
|
source_range: 59..59,
|
||||||
|
delete: 59..59,
|
||||||
|
insert: "Ord, PartialOrd, Eq",
|
||||||
|
kind: Attribute,
|
||||||
|
},
|
||||||
|
CompletionItem {
|
||||||
|
label: "PartialOrd",
|
||||||
|
source_range: 59..59,
|
||||||
|
delete: 59..59,
|
||||||
|
insert: "PartialOrd",
|
||||||
|
kind: Attribute,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_attribute_completion() {
|
fn test_attribute_completion() {
|
||||||
assert_debug_snapshot!(
|
assert_debug_snapshot!(
|
||||||
|
|
|
@ -58,7 +58,7 @@ pub(crate) struct CompletionContext<'a> {
|
||||||
pub(super) is_macro_call: bool,
|
pub(super) is_macro_call: bool,
|
||||||
pub(super) is_path_type: bool,
|
pub(super) is_path_type: bool,
|
||||||
pub(super) has_type_args: bool,
|
pub(super) has_type_args: bool,
|
||||||
pub(super) is_attribute: bool,
|
pub(super) attribute_under_caret: Option<ast::Attr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> CompletionContext<'a> {
|
impl<'a> CompletionContext<'a> {
|
||||||
|
@ -116,7 +116,7 @@ impl<'a> CompletionContext<'a> {
|
||||||
is_path_type: false,
|
is_path_type: false,
|
||||||
has_type_args: false,
|
has_type_args: false,
|
||||||
dot_receiver_is_ambiguous_float_literal: false,
|
dot_receiver_is_ambiguous_float_literal: false,
|
||||||
is_attribute: false,
|
attribute_under_caret: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut original_file = original_file.syntax().clone();
|
let mut original_file = original_file.syntax().clone();
|
||||||
|
@ -200,6 +200,7 @@ impl<'a> CompletionContext<'a> {
|
||||||
Some(ty)
|
Some(ty)
|
||||||
})
|
})
|
||||||
.flatten();
|
.flatten();
|
||||||
|
self.attribute_under_caret = find_node_at_offset(&file_with_fake_ident, offset);
|
||||||
|
|
||||||
// First, let's try to complete a reference to some declaration.
|
// First, let's try to complete a reference to some declaration.
|
||||||
if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&file_with_fake_ident, offset) {
|
if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&file_with_fake_ident, offset) {
|
||||||
|
@ -318,7 +319,6 @@ impl<'a> CompletionContext<'a> {
|
||||||
.and_then(|it| it.syntax().parent().and_then(ast::CallExpr::cast))
|
.and_then(|it| it.syntax().parent().and_then(ast::CallExpr::cast))
|
||||||
.is_some();
|
.is_some();
|
||||||
self.is_macro_call = path.syntax().parent().and_then(ast::MacroCall::cast).is_some();
|
self.is_macro_call = path.syntax().parent().and_then(ast::MacroCall::cast).is_some();
|
||||||
self.is_attribute = path.syntax().parent().and_then(ast::Attr::cast).is_some();
|
|
||||||
|
|
||||||
self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some();
|
self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some();
|
||||||
self.has_type_args = segment.type_arg_list().is_some();
|
self.has_type_args = segment.type_arg_list().is_some();
|
||||||
|
|
|
@ -467,7 +467,7 @@ impl ast::TokenTree {
|
||||||
|
|
||||||
pub fn right_delimiter_token(&self) -> Option<SyntaxToken> {
|
pub fn right_delimiter_token(&self) -> Option<SyntaxToken> {
|
||||||
self.syntax().last_child_or_token()?.into_token().filter(|it| match it.kind() {
|
self.syntax().last_child_or_token()?.into_token().filter(|it| match it.kind() {
|
||||||
T!['{'] | T!['('] | T!['['] => true,
|
T!['}'] | T![')'] | T![']'] => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue