]> git.lizzy.rs Git - rust.git/blob - crates/ra_hir_def/src/attr.rs
Enable hover and autocomplete docs on macro generated items
[rust.git] / crates / ra_hir_def / src / attr.rs
1 //! A higher level attributes based on TokenTree, with also some shortcuts.
2
3 use std::{ops, sync::Arc};
4
5 use either::Either;
6 use hir_expand::{hygiene::Hygiene, AstId, InFile};
7 use mbe::ast_to_token_tree;
8 use ra_cfg::CfgOptions;
9 use ra_syntax::{
10     ast::{self, AstNode, AttrsOwner},
11     SmolStr,
12 };
13 use tt::Subtree;
14
15 use crate::{
16     db::DefDatabase, nameres::ModuleSource, path::ModPath, src::HasChildSource, src::HasSource,
17     AdtId, AttrDefId, Lookup,
18 };
19
20 #[derive(Default, Debug, Clone, PartialEq, Eq)]
21 pub struct Attrs {
22     entries: Option<Arc<[Attr]>>,
23 }
24
25 impl ops::Deref for Attrs {
26     type Target = [Attr];
27
28     fn deref(&self) -> &[Attr] {
29         match &self.entries {
30             Some(it) => &*it,
31             None => &[],
32         }
33     }
34 }
35
36 impl Attrs {
37     pub(crate) fn attrs_query(db: &dyn DefDatabase, def: AttrDefId) -> Attrs {
38         match def {
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) {
43                     Some(it) => {
44                         Attrs::from_attrs_owner(db, it.as_ref().map(|it| it as &dyn AttrsOwner))
45                     }
46                     None => Attrs::from_attrs_owner(
47                         db,
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,
51                         }),
52                     ),
53                 }
54             }
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)),
60                 }
61             }
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))
66             }
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),
71             },
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))
75             }
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),
81         }
82     }
83
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)
87     }
88
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(
91             |docs_text| Attr {
92                 input: Some(AttrInput::Literal(SmolStr::new(docs_text))),
93                 path: ModPath::from(hir_expand::name!(doc)),
94             },
95         );
96         let mut attrs = owner.attrs().peekable();
97         let entries = if attrs.peek().is_none() {
98             // Avoid heap allocation
99             None
100         } else {
101             Some(attrs.flat_map(|ast| Attr::from_src(ast, hygiene)).chain(docs).collect())
102         };
103         Attrs { entries }
104     }
105
106     pub fn by_key(&self, key: &'static str) -> AttrQuery<'_> {
107         AttrQuery { attrs: self, key }
108     }
109
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))
113     }
114 }
115
116 #[derive(Debug, Clone, PartialEq, Eq)]
117 pub struct Attr {
118     pub(crate) path: ModPath,
119     pub(crate) input: Option<AttrInput>,
120 }
121
122 #[derive(Debug, Clone, PartialEq, Eq)]
123 pub enum AttrInput {
124     /// `#[attr = "string"]`
125     Literal(SmolStr),
126     /// `#[attr(subtree)]`
127     TokenTree(Subtree),
128 }
129
130 impl Attr {
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() {
134             None => None,
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))
139             }
140             Some(ast::AttrInput::TokenTree(tt)) => {
141                 Some(AttrInput::TokenTree(ast_to_token_tree(&tt)?.0))
142             }
143         };
144
145         Some(Attr { path, input })
146     }
147 }
148
149 #[derive(Debug, Clone, Copy)]
150 pub struct AttrQuery<'a> {
151     attrs: &'a Attrs,
152     key: &'static str,
153 }
154
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),
159             _ => None,
160         })
161     }
162
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),
166             _ => None,
167         })
168     }
169
170     pub fn exists(self) -> bool {
171         self.attrs().next().is_some()
172     }
173
174     fn attrs(self) -> impl Iterator<Item = &'a Attr> {
175         let key = self.key;
176         self.attrs
177             .iter()
178             .filter(move |attr| attr.path.as_ident().map_or(false, |s| s.to_string() == key))
179     }
180 }
181
182 fn attrs_from_ast<N>(src: AstId<N>, db: &dyn DefDatabase) -> Attrs
183 where
184     N: ast::AttrsOwner,
185 {
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))
188 }
189
190 fn attrs_from_loc<T>(node: T, db: &dyn DefDatabase) -> Attrs
191 where
192     T: HasSource,
193     T::Value: ast::AttrsOwner,
194 {
195     let src = node.source(db);
196     Attrs::from_attrs_owner(db, src.as_ref().map(|it| it as &dyn AttrsOwner))
197 }