1 //! A higher level attributes based on TokenTree, with also some shortcuts.
3 use std::{ops, sync::Arc};
6 use hir_expand::{hygiene::Hygiene, AstId, InFile};
7 use mbe::ast_to_token_tree;
8 use ra_cfg::CfgOptions;
10 ast::{self, AstNode, AttrsOwner},
16 db::DefDatabase, nameres::ModuleSource, path::ModPath, src::HasChildSource, src::HasSource,
17 AdtId, AttrDefId, Lookup,
20 #[derive(Default, Debug, Clone, PartialEq, Eq)]
22 entries: Option<Arc<[Attr]>>,
25 impl ops::Deref for Attrs {
28 fn deref(&self) -> &[Attr] {
37 pub(crate) fn attrs_query(db: &dyn DefDatabase, def: AttrDefId) -> Attrs {
39 AttrDefId::ModuleId(module) => {
40 let def_map = db.crate_def_map(module.krate);
41 let mod_data = &def_map[module.local_id];
42 match mod_data.declaration_source(db) {
44 Attrs::from_attrs_owner(db, it.as_ref().map(|it| it as &dyn AttrsOwner))
46 None => Attrs::from_attrs_owner(
48 mod_data.definition_source(db).as_ref().map(|src| match src {
49 ModuleSource::SourceFile(file) => file as &dyn AttrsOwner,
50 ModuleSource::Module(module) => module as &dyn AttrsOwner,
55 AttrDefId::FieldId(it) => {
56 let src = it.parent.child_source(db);
57 match &src.value[it.local_id] {
58 Either::Left(_tuple) => Attrs::default(),
59 Either::Right(record) => Attrs::from_attrs_owner(db, src.with_value(record)),
62 AttrDefId::EnumVariantId(var_id) => {
63 let src = var_id.parent.child_source(db);
64 let src = src.as_ref().map(|it| &it[var_id.local_id]);
65 Attrs::from_attrs_owner(db, src.map(|it| it as &dyn AttrsOwner))
67 AttrDefId::AdtId(it) => match it {
68 AdtId::StructId(it) => attrs_from_loc(it.lookup(db), db),
69 AdtId::EnumId(it) => attrs_from_loc(it.lookup(db), db),
70 AdtId::UnionId(it) => attrs_from_loc(it.lookup(db), db),
72 AttrDefId::TraitId(it) => attrs_from_loc(it.lookup(db), db),
73 AttrDefId::MacroDefId(it) => {
74 it.ast_id.map_or_else(Default::default, |ast_id| attrs_from_ast(ast_id, db))
76 AttrDefId::ImplId(it) => attrs_from_loc(it.lookup(db), db),
77 AttrDefId::ConstId(it) => attrs_from_loc(it.lookup(db), db),
78 AttrDefId::StaticId(it) => attrs_from_loc(it.lookup(db), db),
79 AttrDefId::FunctionId(it) => attrs_from_loc(it.lookup(db), db),
80 AttrDefId::TypeAliasId(it) => attrs_from_loc(it.lookup(db), db),
84 pub fn from_attrs_owner(db: &dyn DefDatabase, owner: InFile<&dyn AttrsOwner>) -> Attrs {
85 let hygiene = Hygiene::new(db.upcast(), owner.file_id);
86 Attrs::new(owner.value, &hygiene)
89 pub(crate) fn new(owner: &dyn AttrsOwner, hygiene: &Hygiene) -> Attrs {
90 let docs = ast::CommentIter::from_syntax_node(owner.syntax()).doc_comment_text().map(
92 input: Some(AttrInput::Literal(SmolStr::new(docs_text))),
93 path: ModPath::from(hir_expand::name!(doc)),
96 let mut attrs = owner.attrs().peekable();
97 let entries = if attrs.peek().is_none() {
98 // Avoid heap allocation
101 Some(attrs.flat_map(|ast| Attr::from_src(ast, hygiene)).chain(docs).collect())
106 pub fn by_key(&self, key: &'static str) -> AttrQuery<'_> {
107 AttrQuery { attrs: self, key }
110 pub(crate) fn is_cfg_enabled(&self, cfg_options: &CfgOptions) -> bool {
111 // FIXME: handle cfg_attr :-)
112 self.by_key("cfg").tt_values().all(|tt| cfg_options.is_cfg_enabled(tt) != Some(false))
116 #[derive(Debug, Clone, PartialEq, Eq)]
118 pub(crate) path: ModPath,
119 pub(crate) input: Option<AttrInput>,
122 #[derive(Debug, Clone, PartialEq, Eq)]
124 /// `#[attr = "string"]`
126 /// `#[attr(subtree)]`
131 fn from_src(ast: ast::Attr, hygiene: &Hygiene) -> Option<Attr> {
132 let path = ModPath::from_src(ast.path()?, hygiene)?;
133 let input = match ast.input() {
135 Some(ast::AttrInput::Literal(lit)) => {
136 // FIXME: escape? raw string?
137 let value = lit.syntax().first_token()?.text().trim_matches('"').into();
138 Some(AttrInput::Literal(value))
140 Some(ast::AttrInput::TokenTree(tt)) => {
141 Some(AttrInput::TokenTree(ast_to_token_tree(&tt)?.0))
145 Some(Attr { path, input })
149 #[derive(Debug, Clone, Copy)]
150 pub struct AttrQuery<'a> {
155 impl<'a> AttrQuery<'a> {
156 pub fn tt_values(self) -> impl Iterator<Item = &'a Subtree> {
157 self.attrs().filter_map(|attr| match attr.input.as_ref()? {
158 AttrInput::TokenTree(it) => Some(it),
163 pub fn string_value(self) -> Option<&'a SmolStr> {
164 self.attrs().find_map(|attr| match attr.input.as_ref()? {
165 AttrInput::Literal(it) => Some(it),
170 pub fn exists(self) -> bool {
171 self.attrs().next().is_some()
174 fn attrs(self) -> impl Iterator<Item = &'a Attr> {
178 .filter(move |attr| attr.path.as_ident().map_or(false, |s| s.to_string() == key))
182 fn attrs_from_ast<N>(src: AstId<N>, db: &dyn DefDatabase) -> Attrs
186 let src = InFile::new(src.file_id, src.to_node(db.upcast()));
187 Attrs::from_attrs_owner(db, src.as_ref().map(|it| it as &dyn AttrsOwner))
190 fn attrs_from_loc<T>(node: T, db: &dyn DefDatabase) -> Attrs
193 T::Value: ast::AttrsOwner,
195 let src = node.source(db);
196 Attrs::from_attrs_owner(db, src.as_ref().map(|it| it as &dyn AttrsOwner))