//! 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(r#"ignore = "…""#, Some("ignore"), Some(r#"ignore = "${0:reason}""#)), 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, ()> { 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 { 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 = "…" "#]], ); } }