]> git.lizzy.rs Git - rust.git/blob - crates/ra_hir_def/src/attr.rs
Make generics and attr queries use ItemTree
[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,
17     item_tree::{ItemTreeId, ItemTreeNode},
18     nameres::ModuleSource,
19     path::ModPath,
20     src::HasChildSource,
21     AdtId, AttrDefId, Lookup,
22 };
23
24 #[derive(Default, Debug, Clone, PartialEq, Eq)]
25 pub struct Attrs {
26     entries: Option<Arc<[Attr]>>,
27 }
28
29 impl ops::Deref for Attrs {
30     type Target = [Attr];
31
32     fn deref(&self) -> &[Attr] {
33         match &self.entries {
34             Some(it) => &*it,
35             None => &[],
36         }
37     }
38 }
39
40 impl Attrs {
41     pub(crate) fn attrs_query(db: &dyn DefDatabase, def: AttrDefId) -> Attrs {
42         match def {
43             AttrDefId::ModuleId(module) => {
44                 let def_map = db.crate_def_map(module.krate);
45                 let mod_data = &def_map[module.local_id];
46                 match mod_data.declaration_source(db) {
47                     Some(it) => {
48                         Attrs::from_attrs_owner(db, it.as_ref().map(|it| it as &dyn AttrsOwner))
49                     }
50                     None => Attrs::from_attrs_owner(
51                         db,
52                         mod_data.definition_source(db).as_ref().map(|src| match src {
53                             ModuleSource::SourceFile(file) => file as &dyn AttrsOwner,
54                             ModuleSource::Module(module) => module as &dyn AttrsOwner,
55                         }),
56                     ),
57                 }
58             }
59             AttrDefId::FieldId(it) => {
60                 let src = it.parent.child_source(db);
61                 match &src.value[it.local_id] {
62                     Either::Left(_tuple) => Attrs::default(),
63                     Either::Right(record) => Attrs::from_attrs_owner(db, src.with_value(record)),
64                 }
65             }
66             AttrDefId::EnumVariantId(var_id) => {
67                 let src = var_id.parent.child_source(db);
68                 let src = src.as_ref().map(|it| &it[var_id.local_id]);
69                 Attrs::from_attrs_owner(db, src.map(|it| it as &dyn AttrsOwner))
70             }
71             AttrDefId::AdtId(it) => match it {
72                 AdtId::StructId(it) => attrs_from_item_tree(it.lookup(db).id, db),
73                 AdtId::EnumId(it) => attrs_from_item_tree(it.lookup(db).id, db),
74                 AdtId::UnionId(it) => attrs_from_item_tree(it.lookup(db).id, db),
75             },
76             AttrDefId::TraitId(it) => attrs_from_item_tree(it.lookup(db).id, db),
77             AttrDefId::MacroDefId(it) => {
78                 it.ast_id.map_or_else(Default::default, |ast_id| attrs_from_ast(ast_id, db))
79             }
80             AttrDefId::ImplId(it) => attrs_from_item_tree(it.lookup(db).id, db),
81             AttrDefId::ConstId(it) => attrs_from_item_tree(it.lookup(db).id, db),
82             AttrDefId::StaticId(it) => attrs_from_item_tree(it.lookup(db).id, db),
83             AttrDefId::FunctionId(it) => attrs_from_item_tree(it.lookup(db).id, db),
84             AttrDefId::TypeAliasId(it) => attrs_from_item_tree(it.lookup(db).id, db),
85         }
86     }
87
88     pub fn from_attrs_owner(db: &dyn DefDatabase, owner: InFile<&dyn AttrsOwner>) -> Attrs {
89         let hygiene = Hygiene::new(db.upcast(), owner.file_id);
90         Attrs::new(owner.value, &hygiene)
91     }
92
93     pub(crate) fn new(owner: &dyn AttrsOwner, hygiene: &Hygiene) -> Attrs {
94         let docs = ast::CommentIter::from_syntax_node(owner.syntax()).doc_comment_text().map(
95             |docs_text| Attr {
96                 input: Some(AttrInput::Literal(SmolStr::new(docs_text))),
97                 path: ModPath::from(hir_expand::name!(doc)),
98             },
99         );
100         let mut attrs = owner.attrs().peekable();
101         let entries = if attrs.peek().is_none() {
102             // Avoid heap allocation
103             None
104         } else {
105             Some(attrs.flat_map(|ast| Attr::from_src(ast, hygiene)).chain(docs).collect())
106         };
107         Attrs { entries }
108     }
109
110     pub fn by_key(&self, key: &'static str) -> AttrQuery<'_> {
111         AttrQuery { attrs: self, key }
112     }
113
114     pub(crate) fn is_cfg_enabled(&self, cfg_options: &CfgOptions) -> bool {
115         // FIXME: handle cfg_attr :-)
116         self.by_key("cfg").tt_values().all(|tt| cfg_options.is_cfg_enabled(tt) != Some(false))
117     }
118 }
119
120 #[derive(Debug, Clone, PartialEq, Eq)]
121 pub struct Attr {
122     pub(crate) path: ModPath,
123     pub(crate) input: Option<AttrInput>,
124 }
125
126 #[derive(Debug, Clone, PartialEq, Eq)]
127 pub enum AttrInput {
128     /// `#[attr = "string"]`
129     Literal(SmolStr),
130     /// `#[attr(subtree)]`
131     TokenTree(Subtree),
132 }
133
134 impl Attr {
135     fn from_src(ast: ast::Attr, hygiene: &Hygiene) -> Option<Attr> {
136         let path = ModPath::from_src(ast.path()?, hygiene)?;
137         let input = match ast.input() {
138             None => None,
139             Some(ast::AttrInput::Literal(lit)) => {
140                 // FIXME: escape? raw string?
141                 let value = lit.syntax().first_token()?.text().trim_matches('"').into();
142                 Some(AttrInput::Literal(value))
143             }
144             Some(ast::AttrInput::TokenTree(tt)) => {
145                 Some(AttrInput::TokenTree(ast_to_token_tree(&tt)?.0))
146             }
147         };
148
149         Some(Attr { path, input })
150     }
151 }
152
153 #[derive(Debug, Clone, Copy)]
154 pub struct AttrQuery<'a> {
155     attrs: &'a Attrs,
156     key: &'static str,
157 }
158
159 impl<'a> AttrQuery<'a> {
160     pub fn tt_values(self) -> impl Iterator<Item = &'a Subtree> {
161         self.attrs().filter_map(|attr| match attr.input.as_ref()? {
162             AttrInput::TokenTree(it) => Some(it),
163             _ => None,
164         })
165     }
166
167     pub fn string_value(self) -> Option<&'a SmolStr> {
168         self.attrs().find_map(|attr| match attr.input.as_ref()? {
169             AttrInput::Literal(it) => Some(it),
170             _ => None,
171         })
172     }
173
174     pub fn exists(self) -> bool {
175         self.attrs().next().is_some()
176     }
177
178     fn attrs(self) -> impl Iterator<Item = &'a Attr> {
179         let key = self.key;
180         self.attrs
181             .iter()
182             .filter(move |attr| attr.path.as_ident().map_or(false, |s| s.to_string() == key))
183     }
184 }
185
186 fn attrs_from_ast<N>(src: AstId<N>, db: &dyn DefDatabase) -> Attrs
187 where
188     N: ast::AttrsOwner,
189 {
190     let src = InFile::new(src.file_id, src.to_node(db.upcast()));
191     Attrs::from_attrs_owner(db, src.as_ref().map(|it| it as &dyn AttrsOwner))
192 }
193
194 fn attrs_from_item_tree<N: ItemTreeNode>(id: ItemTreeId<N>, db: &dyn DefDatabase) -> Attrs {
195     let tree = db.item_tree(id.file_id);
196     let mod_item = N::id_to_mod_item(id.value);
197     tree.attrs(mod_item).clone()
198 }