//!
//! This module uses a bit of static metadata to provide completions
//! for built-in attributes.
-
-use hir::HasAttrs;
-use ide_db::helpers::generated_lints::{CLIPPY_LINTS, DEFAULT_LINTS, FEATURES};
+//! Non-builtin attribute(excluding derives attributes) completions are done in [`super::unqualified_path`].
+
+use ide_db::{
+ helpers::{
+ generated_lints::{CLIPPY_LINTS, DEFAULT_LINTS, FEATURES, RUSTDOC_LINTS},
+ parse_tt_as_comma_sep_paths,
+ },
+ SymbolKind,
+};
+use itertools::Itertools;
use once_cell::sync::Lazy;
-use rustc_hash::{FxHashMap, FxHashSet};
-use syntax::{algo::non_trivia_sibling, ast, AstNode, Direction, NodeOrToken, SyntaxKind, T};
+use rustc_hash::FxHashMap;
+use syntax::{algo::non_trivia_sibling, ast, AstNode, Direction, SyntaxKind, T};
-use crate::{
- context::CompletionContext,
- item::{CompletionItem, CompletionItemKind, CompletionKind},
- Completions,
-};
+use crate::{context::CompletionContext, item::CompletionItem, Completions};
mod cfg;
mod derive;
pub(crate) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
let attribute = ctx.attribute_under_caret.as_ref()?;
- match (attribute.path().and_then(|p| p.as_single_name_ref()), attribute.token_tree()) {
- (Some(path), Some(token_tree)) => match path.text().as_str() {
- "derive" => derive::complete_derive(acc, ctx, token_tree),
- "repr" => repr::complete_repr(acc, ctx, token_tree),
- "feature" => lint::complete_lint(acc, ctx, token_tree, FEATURES),
+ let name_ref = match attribute.path() {
+ Some(p) => Some(p.as_single_name_ref()?),
+ None => None,
+ };
+ match (name_ref, attribute.token_tree()) {
+ (Some(path), Some(tt)) if tt.l_paren_token().is_some() => match path.text().as_str() {
+ "repr" => repr::complete_repr(acc, ctx, tt),
+ "derive" => derive::complete_derive(acc, ctx, &parse_tt_as_comma_sep_paths(tt)?),
+ "feature" => lint::complete_lint(acc, ctx, &parse_tt_as_comma_sep_paths(tt)?, FEATURES),
"allow" | "warn" | "deny" | "forbid" => {
- lint::complete_lint(acc, ctx, token_tree.clone(), DEFAULT_LINTS);
- lint::complete_lint(acc, ctx, token_tree, CLIPPY_LINTS);
+ let existing_lints = parse_tt_as_comma_sep_paths(tt)?;
+ lint::complete_lint(acc, ctx, &existing_lints, DEFAULT_LINTS);
+ lint::complete_lint(acc, ctx, &existing_lints, CLIPPY_LINTS);
+ lint::complete_lint(acc, ctx, &existing_lints, RUSTDOC_LINTS);
}
"cfg" => {
cfg::complete_cfg(acc, ctx);
}
_ => (),
},
- (None, Some(_)) => (),
- _ => complete_new_attribute(acc, ctx, attribute),
+ (_, Some(_)) => (),
+ (_, None) => complete_new_attribute(acc, ctx, attribute),
}
Some(())
}
});
let add_completion = |attr_completion: &AttrCompletion| {
- let mut item = CompletionItem::new(
- CompletionKind::Attribute,
- ctx.source_range(),
- attr_completion.label,
- );
- item.kind(CompletionItemKind::Attribute);
+ let mut item =
+ CompletionItem::new(SymbolKind::Attribute, ctx.source_range(), attr_completion.label);
if let Some(lookup) = attr_completion.lookup {
item.lookup_by(lookup);
None if is_inner => ATTRIBUTES.iter().for_each(add_completion),
None => ATTRIBUTES.iter().filter(|compl| !compl.prefer_inner).for_each(add_completion),
}
-
- // FIXME: write a test for this when we can
- ctx.scope.process_all_names(&mut |name, scope_def| {
- if let hir::ScopeDef::MacroDef(mac) = scope_def {
- if mac.kind() == hir::MacroKind::Attr {
- let mut item = CompletionItem::new(
- CompletionKind::Attribute,
- ctx.source_range(),
- name.to_string(),
- );
- item.kind(CompletionItemKind::Attribute);
- if let Some(docs) = mac.docs(ctx.sema.db) {
- item.documentation(docs);
- }
- item.add_to(acc);
- }
- }
- });
}
struct AttrCompletion {
#[rustfmt::skip]
static KIND_TO_ATTRIBUTES: Lazy<FxHashMap<SyntaxKind, &[&str]>> = Lazy::new(|| {
use SyntaxKind::*;
- std::array::IntoIter::new([
+ [
(
SOURCE_FILE,
attrs!(
(MATCH_ARM, attrs!()),
(IDENT_PAT, attrs!()),
(RECORD_PAT_FIELD, attrs!()),
- ])
+ ]
+ .into_iter()
.collect()
});
const EXPR_ATTRIBUTES: &[&str] = attrs!();
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(
+ r#"recursion_limit = "…""#,
+ Some("recursion_limit"),
+ Some(r#"recursion_limit = "${0:128}""#),
+ )
+ .prefer_inner(),
attr("repr(…)", Some("repr"), Some("repr(${0:C})")),
attr("should_panic", Some("should_panic"), Some(r#"should_panic"#)),
attr(
.prefer_inner(),
];
-fn parse_comma_sep_input(derive_input: ast::TokenTree) -> Option<FxHashSet<String>> {
- let (l_paren, r_paren) = derive_input.l_paren_token().zip(derive_input.r_paren_token())?;
- let mut input_derives = FxHashSet::default();
- let mut tokens = derive_input
+fn parse_comma_sep_expr(input: ast::TokenTree) -> Option<Vec<ast::Expr>> {
+ let r_paren = input.r_paren_token()?;
+ let tokens = input
.syntax()
.children_with_tokens()
- .filter_map(NodeOrToken::into_token)
- .skip_while(|token| token != &l_paren)
.skip(1)
- .take_while(|token| token != &r_paren)
- .peekable();
- let mut input = String::new();
- while tokens.peek().is_some() {
- for token in tokens.by_ref().take_while(|t| t.kind() != T![,]) {
- input.push_str(token.text());
- }
-
- if !input.is_empty() {
- input_derives.insert(input.trim().to_owned());
- }
-
- input.clear();
- }
-
- Some(input_derives)
+ .take_while(|it| it.as_token() != Some(&r_paren));
+ let input_expressions = tokens.into_iter().group_by(|tok| tok.kind() == T![,]);
+ Some(
+ input_expressions
+ .into_iter()
+ .filter_map(|(is_sep, group)| (!is_sep).then(|| group))
+ .filter_map(|mut tokens| ast::Expr::parse(&tokens.join("")).ok())
+ .collect::<Vec<ast::Expr>>(),
+ )
}
-#[cfg(test)]
-mod tests {
- use super::*;
-
- use expect_test::{expect, Expect};
-
- use crate::tests::completion_list;
-
- #[test]
- fn attributes_are_sorted() {
- let mut attrs = ATTRIBUTES.iter().map(|attr| attr.key());
- let mut prev = attrs.next().unwrap();
-
- attrs.for_each(|next| {
- assert!(
- prev < next,
- r#"ATTRIBUTES array is not sorted, "{}" should come after "{}""#,
- prev,
- next
- );
- prev = next;
- });
- }
-
- fn check(ra_fixture: &str, expect: Expect) {
- let actual = completion_list(ra_fixture);
- expect.assert_eq(&actual);
- }
-
- #[test]
- fn test_attribute_completion_inside_nested_attr() {
- check(r#"#[cfg($0)]"#, expect![[]])
- }
-
- #[test]
- fn test_attribute_completion_with_existing_attr() {
- check(
- r#"#[no_mangle] #[$0] mcall!();"#,
- expect![[r#"
- at allow(…)
- at cfg(…)
- at cfg_attr(…)
- at deny(…)
- at forbid(…)
- at warn(…)
- "#]],
- )
- }
-
- #[test]
- fn complete_attribute_on_source_file() {
- check(
- r#"#![$0]"#,
- expect![[r#"
- at allow(…)
- at cfg(…)
- at cfg_attr(…)
- at deny(…)
- at forbid(…)
- at warn(…)
- at deprecated
- at doc = "…"
- at doc(hidden)
- at doc(alias = "…")
- at must_use
- at no_mangle
- at crate_name = ""
- at feature(…)
- at no_implicit_prelude
- at no_main
- at no_std
- at recursion_limit = …
- at type_length_limit = …
- at windows_subsystem = "…"
- "#]],
- );
- }
-
- #[test]
- fn complete_attribute_on_module() {
- check(
- r#"#[$0] mod foo;"#,
- expect![[r#"
- at allow(…)
- at cfg(…)
- at cfg_attr(…)
- at deny(…)
- at forbid(…)
- at warn(…)
- at deprecated
- at doc = "…"
- at doc(hidden)
- at doc(alias = "…")
- at must_use
- at no_mangle
- at macro_use
- at path = "…"
- "#]],
- );
- check(
- r#"mod foo {#![$0]}"#,
- expect![[r#"
- at allow(…)
- at cfg(…)
- at cfg_attr(…)
- at deny(…)
- at forbid(…)
- at warn(…)
- at deprecated
- at doc = "…"
- at doc(hidden)
- at doc(alias = "…")
- at must_use
- at no_mangle
- at no_implicit_prelude
- "#]],
- );
- }
-
- #[test]
- fn complete_attribute_on_macro_rules() {
- check(
- r#"#[$0] macro_rules! foo {}"#,
- expect![[r#"
- at allow(…)
- at cfg(…)
- at cfg_attr(…)
- at deny(…)
- at forbid(…)
- at warn(…)
- at deprecated
- at doc = "…"
- at doc(hidden)
- at doc(alias = "…")
- at must_use
- at no_mangle
- at macro_export
- at macro_use
- "#]],
- );
- }
-
- #[test]
- fn complete_attribute_on_macro_def() {
- check(
- r#"#[$0] macro foo {}"#,
- expect![[r#"
- at allow(…)
- at cfg(…)
- at cfg_attr(…)
- at deny(…)
- at forbid(…)
- at warn(…)
- at deprecated
- at doc = "…"
- at doc(hidden)
- at doc(alias = "…")
- at must_use
- at no_mangle
- "#]],
- );
- }
-
- #[test]
- fn complete_attribute_on_extern_crate() {
- check(
- r#"#[$0] extern crate foo;"#,
- expect![[r#"
- at allow(…)
- at cfg(…)
- at cfg_attr(…)
- at deny(…)
- at forbid(…)
- at warn(…)
- at deprecated
- at doc = "…"
- at doc(hidden)
- at doc(alias = "…")
- at must_use
- at no_mangle
- at macro_use
- "#]],
+#[test]
+fn attributes_are_sorted() {
+ let mut attrs = ATTRIBUTES.iter().map(|attr| attr.key());
+ let mut prev = attrs.next().unwrap();
+
+ attrs.for_each(|next| {
+ assert!(
+ prev < next,
+ r#"ATTRIBUTES array is not sorted, "{}" should come after "{}""#,
+ prev,
+ next
);
- }
-
- #[test]
- fn complete_attribute_on_use() {
- check(
- r#"#[$0] use foo;"#,
- expect![[r#"
- at allow(…)
- at cfg(…)
- at cfg_attr(…)
- at deny(…)
- at forbid(…)
- at warn(…)
- at deprecated
- at doc = "…"
- at doc(hidden)
- at doc(alias = "…")
- at must_use
- at no_mangle
- "#]],
- );
- }
-
- #[test]
- fn complete_attribute_on_type_alias() {
- check(
- r#"#[$0] type foo = ();"#,
- expect![[r#"
- at allow(…)
- at cfg(…)
- at cfg_attr(…)
- at deny(…)
- at forbid(…)
- at warn(…)
- at deprecated
- at doc = "…"
- at doc(hidden)
- at doc(alias = "…")
- at must_use
- at no_mangle
- "#]],
- );
- }
-
- #[test]
- fn complete_attribute_on_struct() {
- check(
- r#"#[$0] struct Foo;"#,
- expect![[r#"
- at allow(…)
- at cfg(…)
- at cfg_attr(…)
- at deny(…)
- at forbid(…)
- at warn(…)
- at deprecated
- at doc = "…"
- at doc(hidden)
- at doc(alias = "…")
- at must_use
- at no_mangle
- at derive(…)
- at repr(…)
- at non_exhaustive
- "#]],
- );
- }
-
- #[test]
- fn complete_attribute_on_enum() {
- check(
- r#"#[$0] enum Foo {}"#,
- expect![[r#"
- at allow(…)
- at cfg(…)
- at cfg_attr(…)
- at deny(…)
- at forbid(…)
- at warn(…)
- at deprecated
- at doc = "…"
- at doc(hidden)
- at doc(alias = "…")
- at must_use
- at no_mangle
- at derive(…)
- at repr(…)
- at non_exhaustive
- "#]],
- );
- }
-
- #[test]
- fn complete_attribute_on_const() {
- check(
- r#"#[$0] const FOO: () = ();"#,
- expect![[r#"
- at allow(…)
- at cfg(…)
- at cfg_attr(…)
- at deny(…)
- at forbid(…)
- at warn(…)
- at deprecated
- at doc = "…"
- at doc(hidden)
- at doc(alias = "…")
- at must_use
- at no_mangle
- "#]],
- );
- }
-
- #[test]
- fn complete_attribute_on_static() {
- check(
- r#"#[$0] static FOO: () = ()"#,
- expect![[r#"
- at allow(…)
- at cfg(…)
- at cfg_attr(…)
- at deny(…)
- at forbid(…)
- at warn(…)
- at deprecated
- at doc = "…"
- at doc(hidden)
- at doc(alias = "…")
- at must_use
- at no_mangle
- at export_name = "…"
- at link_name = "…"
- at link_section = "…"
- at global_allocator
- at used
- "#]],
- );
- }
-
- #[test]
- fn complete_attribute_on_trait() {
- check(
- r#"#[$0] trait Foo {}"#,
- expect![[r#"
- at allow(…)
- at cfg(…)
- at cfg_attr(…)
- at deny(…)
- at forbid(…)
- at warn(…)
- at deprecated
- at doc = "…"
- at doc(hidden)
- at doc(alias = "…")
- at must_use
- at no_mangle
- at must_use
- "#]],
- );
- }
-
- #[test]
- fn complete_attribute_on_impl() {
- check(
- r#"#[$0] impl () {}"#,
- expect![[r#"
- at allow(…)
- at cfg(…)
- at cfg_attr(…)
- at deny(…)
- at forbid(…)
- at warn(…)
- at deprecated
- at doc = "…"
- at doc(hidden)
- at doc(alias = "…")
- at must_use
- at no_mangle
- at automatically_derived
- "#]],
- );
- check(
- r#"impl () {#![$0]}"#,
- expect![[r#"
- at allow(…)
- at cfg(…)
- at cfg_attr(…)
- at deny(…)
- at forbid(…)
- at warn(…)
- at deprecated
- at doc = "…"
- at doc(hidden)
- at doc(alias = "…")
- at must_use
- at no_mangle
- "#]],
- );
- }
-
- #[test]
- fn complete_attribute_on_extern_block() {
- check(
- r#"#[$0] extern {}"#,
- expect![[r#"
- at allow(…)
- at cfg(…)
- at cfg_attr(…)
- at deny(…)
- at forbid(…)
- at warn(…)
- at deprecated
- at doc = "…"
- at doc(hidden)
- at doc(alias = "…")
- at must_use
- at no_mangle
- at link
- "#]],
- );
- check(
- r#"extern {#![$0]}"#,
- expect![[r#"
- at allow(…)
- at cfg(…)
- at cfg_attr(…)
- at deny(…)
- at forbid(…)
- at warn(…)
- at deprecated
- at doc = "…"
- at doc(hidden)
- at doc(alias = "…")
- at must_use
- at no_mangle
- at link
- "#]],
- );
- }
-
- #[test]
- fn complete_attribute_on_variant() {
- check(
- r#"enum Foo { #[$0] Bar }"#,
- expect![[r#"
- at allow(…)
- at cfg(…)
- at cfg_attr(…)
- at deny(…)
- at forbid(…)
- at warn(…)
- at non_exhaustive
- "#]],
- );
- }
-
- #[test]
- fn complete_attribute_on_fn() {
- check(
- r#"#[$0] fn main() {}"#,
- expect![[r#"
- at allow(…)
- at cfg(…)
- at cfg_attr(…)
- at deny(…)
- at forbid(…)
- at warn(…)
- at deprecated
- at doc = "…"
- at doc(hidden)
- at doc(alias = "…")
- at must_use
- at no_mangle
- at export_name = "…"
- at link_name = "…"
- at link_section = "…"
- at cold
- at ignore = "…"
- at inline
- at must_use
- at panic_handler
- at proc_macro
- at proc_macro_derive(…)
- at proc_macro_attribute
- at should_panic
- at target_feature = "…"
- at test
- at track_caller
- "#]],
- );
- }
-
- #[test]
- fn complete_attribute_on_expr() {
- cov_mark::check!(no_keyword_completion_in_attr_of_expr);
- check(
- r#"fn main() { #[$0] foo() }"#,
- expect![[r#"
- at allow(…)
- at cfg(…)
- at cfg_attr(…)
- at deny(…)
- at forbid(…)
- at warn(…)
- "#]],
- );
- }
-
- #[test]
- fn complete_attribute_in_source_file_end() {
- check(
- r#"#[$0]"#,
- expect![[r#"
- at allow(…)
- at automatically_derived
- at cfg(…)
- at cfg_attr(…)
- at cold
- at deny(…)
- at deprecated
- at derive(…)
- at doc = "…"
- at doc(alias = "…")
- at doc(hidden)
- at export_name = "…"
- at forbid(…)
- at global_allocator
- at ignore = "…"
- at inline
- at link
- at link_name = "…"
- at link_section = "…"
- at macro_export
- at macro_use
- at must_use
- at no_mangle
- at non_exhaustive
- at panic_handler
- at path = "…"
- at proc_macro
- at proc_macro_attribute
- at proc_macro_derive(…)
- at repr(…)
- at should_panic
- at target_feature = "…"
- at test
- at track_caller
- at used
- at warn(…)
- "#]],
- );
- }
-
- #[test]
- fn test_cfg() {
- check(
- r#"#[cfg(target_endian = $0"#,
- expect![[r#"
- at little
- at big
-"#]],
- );
- }
+ prev = next;
+ });
}