blob: 9db317509fef9e0eb651327c894c819aa372ffe5 [file] [log] [blame]
//! Completion for attributes
//!
//! This module uses a bit of static metadata to provide completions
//! for built-in attributes.
use ra_syntax::{ast, AstNode, SyntaxKind};
use rustc_hash::FxHashSet;
use crate::completion::{
completion_context::CompletionContext,
completion_item::{CompletionItem, CompletionItemKind, CompletionKind, Completions},
};
pub(super) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
let attribute = ctx.attribute_under_caret.as_ref()?;
match (attribute.path(), attribute.input()) {
(Some(path), Some(ast::AttrInput::TokenTree(token_tree)))
if path.to_string() == "derive" =>
{
complete_derive(acc, ctx, token_tree)
}
(_, Some(ast::AttrInput::TokenTree(_token_tree))) => {}
_ => complete_attribute_start(acc, ctx, attribute),
}
Some(())
}
fn complete_attribute_start(acc: &mut Completions, ctx: &CompletionContext, attribute: &ast::Attr) {
for attr_completion in ATTRIBUTES {
let mut item = CompletionItem::new(
CompletionKind::Attribute,
ctx.source_range(),
attr_completion.label,
)
.kind(CompletionItemKind::Attribute);
if let Some(lookup) = attr_completion.lookup {
item = item.lookup_by(lookup);
}
match (attr_completion.snippet, ctx.config.snippet_cap) {
(Some(snippet), Some(cap)) => {
item = item.insert_snippet(cap, snippet);
}
_ => {}
}
if attribute.kind() == ast::AttrKind::Inner || !attr_completion.prefer_inner {
acc.add(item);
}
}
}
struct AttrCompletion {
label: &'static str,
lookup: Option<&'static str>,
snippet: Option<&'static str>,
prefer_inner: bool,
}
impl AttrCompletion {
const fn prefer_inner(self) -> AttrCompletion {
AttrCompletion { prefer_inner: true, ..self }
}
}
const fn attr(
label: &'static str,
lookup: Option<&'static str>,
snippet: Option<&'static str>,
) -> AttrCompletion {
AttrCompletion { label, lookup, snippet, prefer_inner: false }
}
const ATTRIBUTES: &[AttrCompletion] = &[
attr("allow(…)", Some("allow"), Some("allow(${0:lint})")),
attr("cfg_attr(…)", Some("cfg_attr"), Some("cfg_attr(${1:predicate}, ${0:attr})")),
attr("cfg(…)", Some("cfg"), Some("cfg(${0:predicate})")),
attr("deny(…)", Some("deny"), Some("deny(${0:lint})")),
attr(r#"deprecated = """#, Some("deprecated"), Some(r#"deprecated = "${0:reason}""#)),
attr("derive(…)", Some("derive"), Some(r#"derive(${0:Debug})"#)),
attr(r#"doc = """#, Some("doc"), Some(r#"doc = "${0:docs}""#)),
attr("feature(…)", Some("feature"), Some("feature(${0:flag})")).prefer_inner(),
attr("forbid(…)", Some("forbid"), Some("forbid(${0:lint})")),
// FIXME: resolve through macro resolution?
attr("global_allocator", None, None).prefer_inner(),
attr("ignore(…)", Some("ignore"), Some("ignore(${0:lint})")),
attr("inline(…)", Some("inline"), Some("inline(${0:lint})")),
attr(r#"link_name = """#, Some("link_name"), Some(r#"link_name = "${0:symbol_name}""#)),
attr("link", None, None),
attr("macro_export", None, None),
attr("macro_use", None, None),
attr(r#"must_use = """#, Some("must_use"), Some(r#"must_use = "${0:reason}""#)),
attr("no_mangle", None, None),
attr("no_std", None, None).prefer_inner(),
attr("non_exhaustive", None, None),
attr("panic_handler", None, None).prefer_inner(),
attr("path = \"…\"", Some("path"), Some("path =\"${0:path}\"")),
attr("proc_macro", None, None),
attr("proc_macro_attribute", None, None),
attr("proc_macro_derive(…)", Some("proc_macro_derive"), Some("proc_macro_derive(${0:Trait})")),
attr("recursion_limit = …", Some("recursion_limit"), Some("recursion_limit = ${0:128}"))
.prefer_inner(),
attr("repr(…)", Some("repr"), Some("repr(${0:C})")),
attr(
"should_panic(…)",
Some("should_panic"),
Some(r#"should_panic(expected = "${0:reason}")"#),
),
attr(
r#"target_feature = """#,
Some("target_feature"),
Some("target_feature = \"${0:feature}\""),
),
attr("test", None, None),
attr("used", None, None),
attr("warn(…)", Some("warn"), Some("warn(${0:lint})")),
attr(
r#"windows_subsystem = """#,
Some("windows_subsystem"),
Some(r#"windows_subsystem = "${0:subsystem}""#),
)
.prefer_inner(),
];
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)]
mod tests {
use expect::{expect, Expect};
use crate::completion::{test_utils::completion_list, CompletionKind};
fn check(ra_fixture: &str, expect: Expect) {
let actual = completion_list(ra_fixture, CompletionKind::Attribute);
expect.assert_eq(&actual);
}
#[test]
fn empty_derive_completion() {
check(
r#"
#[derive(<|>)]
struct Test {}
"#,
expect![[r#"
at Clone
at Copy, Clone
at Debug
at Default
at Eq, PartialEq
at Hash
at Ord, PartialOrd, Eq, PartialEq
at PartialEq
at PartialOrd, PartialEq
"#]],
);
}
#[test]
fn no_completion_for_incorrect_derive() {
check(
r#"
#[derive{<|>)]
struct Test {}
"#,
expect![[r#""#]],
)
}
#[test]
fn derive_with_input_completion() {
check(
r#"
#[derive(serde::Serialize, PartialEq, <|>)]
struct Test {}
"#,
expect![[r#"
at Clone
at Copy, Clone
at Debug
at Default
at Eq
at Hash
at Ord, PartialOrd, Eq
at PartialOrd
"#]],
)
}
#[test]
fn test_attribute_completion() {
check(
r#"#[<|>]"#,
expect![[r#"
at allow(…)
at cfg(…)
at cfg_attr(…)
at deny(…)
at deprecated = "…"
at derive(…)
at doc = "…"
at forbid(…)
at ignore(…)
at inline(…)
at link
at link_name = "…"
at macro_export
at macro_use
at must_use = "…"
at no_mangle
at non_exhaustive
at path = "…"
at proc_macro
at proc_macro_attribute
at proc_macro_derive(…)
at repr(…)
at should_panic(…)
at target_feature = "…"
at test
at used
at warn(…)
"#]],
)
}
#[test]
fn test_attribute_completion_inside_nested_attr() {
check(r#"#[allow(<|>)]"#, expect![[]])
}
#[test]
fn test_inner_attribute_completion() {
check(
r"#![<|>]",
expect![[r#"
at allow(…)
at cfg(…)
at cfg_attr(…)
at deny(…)
at deprecated = "…"
at derive(…)
at doc = "…"
at feature(…)
at forbid(…)
at global_allocator
at ignore(…)
at inline(…)
at link
at link_name = "…"
at macro_export
at macro_use
at must_use = "…"
at no_mangle
at no_std
at non_exhaustive
at panic_handler
at path = "…"
at proc_macro
at proc_macro_attribute
at proc_macro_derive(…)
at recursion_limit =
at repr(…)
at should_panic(…)
at target_feature = "…"
at test
at used
at warn(…)
at windows_subsystem = "…"
"#]],
);
}
}