aboutsummaryrefslogtreecommitdiff
path: root/crates/hir_def/src/attr.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/hir_def/src/attr.rs')
-rw-r--r--crates/hir_def/src/attr.rs290
1 files changed, 225 insertions, 65 deletions
diff --git a/crates/hir_def/src/attr.rs b/crates/hir_def/src/attr.rs
index c64b78445..042e119b1 100644
--- a/crates/hir_def/src/attr.rs
+++ b/crates/hir_def/src/attr.rs
@@ -2,22 +2,24 @@
2 2
3use std::{ops, sync::Arc}; 3use std::{ops, sync::Arc};
4 4
5use base_db::CrateId;
5use cfg::{CfgExpr, CfgOptions}; 6use cfg::{CfgExpr, CfgOptions};
6use either::Either; 7use either::Either;
7use hir_expand::{hygiene::Hygiene, AstId, InFile}; 8use hir_expand::{hygiene::Hygiene, name::AsName, AstId, InFile};
8use itertools::Itertools; 9use itertools::Itertools;
9use mbe::ast_to_token_tree; 10use mbe::ast_to_token_tree;
10use syntax::{ 11use syntax::{
11 ast::{self, AstNode, AttrsOwner}, 12 ast::{self, AstNode, AttrsOwner},
12 match_ast, AstToken, SmolStr, SyntaxNode, 13 match_ast, AstToken, SmolStr, SyntaxNode,
13}; 14};
15use test_utils::mark;
14use tt::Subtree; 16use tt::Subtree;
15 17
16use crate::{ 18use crate::{
17 db::DefDatabase, 19 db::DefDatabase,
18 item_tree::{ItemTreeId, ItemTreeNode}, 20 item_tree::{ItemTreeId, ItemTreeNode},
19 nameres::ModuleSource, 21 nameres::ModuleSource,
20 path::ModPath, 22 path::{ModPath, PathKind},
21 src::HasChildSource, 23 src::HasChildSource,
22 AdtId, AttrDefId, Lookup, 24 AdtId, AttrDefId, Lookup,
23}; 25};
@@ -38,12 +40,16 @@ impl From<Documentation> for String {
38 } 40 }
39} 41}
40 42
43/// Syntactical attributes, without filtering of `cfg_attr`s.
41#[derive(Default, Debug, Clone, PartialEq, Eq)] 44#[derive(Default, Debug, Clone, PartialEq, Eq)]
42pub struct Attrs { 45pub(crate) struct RawAttrs {
43 entries: Option<Arc<[Attr]>>, 46 entries: Option<Arc<[Attr]>>,
44} 47}
45 48
46impl ops::Deref for Attrs { 49#[derive(Default, Debug, Clone, PartialEq, Eq)]
50pub struct Attrs(RawAttrs);
51
52impl ops::Deref for RawAttrs {
47 type Target = [Attr]; 53 type Target = [Attr];
48 54
49 fn deref(&self) -> &[Attr] { 55 fn deref(&self) -> &[Attr] {
@@ -54,19 +60,147 @@ impl ops::Deref for Attrs {
54 } 60 }
55} 61}
56 62
63impl ops::Deref for Attrs {
64 type Target = [Attr];
65
66 fn deref(&self) -> &[Attr] {
67 match &self.0.entries {
68 Some(it) => &*it,
69 None => &[],
70 }
71 }
72}
73
74impl RawAttrs {
75 pub(crate) const EMPTY: Self = Self { entries: None };
76
77 pub(crate) fn new(owner: &dyn AttrsOwner, hygiene: &Hygiene) -> Self {
78 let attrs: Vec<_> = collect_attrs(owner).collect();
79 let entries = if attrs.is_empty() {
80 // Avoid heap allocation
81 None
82 } else {
83 Some(
84 attrs
85 .into_iter()
86 .enumerate()
87 .flat_map(|(i, attr)| match attr {
88 Either::Left(attr) => Attr::from_src(attr, hygiene).map(|attr| (i, attr)),
89 Either::Right(comment) => comment.doc_comment().map(|doc| {
90 (
91 i,
92 Attr {
93 index: 0,
94 input: Some(AttrInput::Literal(SmolStr::new(doc))),
95 path: ModPath::from(hir_expand::name!(doc)),
96 },
97 )
98 }),
99 })
100 .map(|(i, attr)| Attr { index: i as u32, ..attr })
101 .collect(),
102 )
103 };
104 Self { entries }
105 }
106
107 fn from_attrs_owner(db: &dyn DefDatabase, owner: InFile<&dyn AttrsOwner>) -> Self {
108 let hygiene = Hygiene::new(db.upcast(), owner.file_id);
109 Self::new(owner.value, &hygiene)
110 }
111
112 pub(crate) fn merge(&self, other: Self) -> Self {
113 match (&self.entries, &other.entries) {
114 (None, None) => Self::EMPTY,
115 (Some(entries), None) | (None, Some(entries)) => {
116 Self { entries: Some(entries.clone()) }
117 }
118 (Some(a), Some(b)) => {
119 Self { entries: Some(a.iter().chain(b.iter()).cloned().collect()) }
120 }
121 }
122 }
123
124 /// Processes `cfg_attr`s, returning the resulting semantic `Attrs`.
125 pub(crate) fn filter(self, db: &dyn DefDatabase, krate: CrateId) -> Attrs {
126 let has_cfg_attrs = self.iter().any(|attr| {
127 attr.path.as_ident().map_or(false, |name| *name == hir_expand::name![cfg_attr])
128 });
129 if !has_cfg_attrs {
130 return Attrs(self);
131 }
132
133 let crate_graph = db.crate_graph();
134 let new_attrs = self
135 .iter()
136 .filter_map(|attr| {
137 let attr = attr.clone();
138 let is_cfg_attr =
139 attr.path.as_ident().map_or(false, |name| *name == hir_expand::name![cfg_attr]);
140 if !is_cfg_attr {
141 return Some(attr);
142 }
143
144 let subtree = match &attr.input {
145 Some(AttrInput::TokenTree(it)) => it,
146 _ => return Some(attr),
147 };
148
149 // Input subtree is: `(cfg, attr)`
150 // Split it up into a `cfg` and an `attr` subtree.
151 // FIXME: There should be a common API for this.
152 let mut saw_comma = false;
153 let (mut cfg, attr): (Vec<_>, Vec<_>) =
154 subtree.clone().token_trees.into_iter().partition(|tree| {
155 if saw_comma {
156 return false;
157 }
158
159 match tree {
160 tt::TokenTree::Leaf(tt::Leaf::Punct(p)) if p.char == ',' => {
161 saw_comma = true;
162 }
163 _ => {}
164 }
165
166 true
167 });
168 cfg.pop(); // `,` ends up in here
169
170 let attr = Subtree { delimiter: None, token_trees: attr };
171 let cfg = Subtree { delimiter: subtree.delimiter, token_trees: cfg };
172 let cfg = CfgExpr::parse(&cfg);
173
174 let cfg_options = &crate_graph[krate].cfg_options;
175 if cfg_options.check(&cfg) == Some(false) {
176 None
177 } else {
178 mark::hit!(cfg_attr_active);
179
180 let attr = ast::Attr::parse(&format!("#[{}]", attr)).ok()?;
181 let hygiene = Hygiene::new_unhygienic(); // FIXME
182 Attr::from_src(attr, &hygiene)
183 }
184 })
185 .collect();
186
187 Attrs(RawAttrs { entries: Some(new_attrs) })
188 }
189}
190
57impl Attrs { 191impl Attrs {
58 pub const EMPTY: Attrs = Attrs { entries: None }; 192 pub const EMPTY: Self = Self(RawAttrs::EMPTY);
59 193
60 pub(crate) fn attrs_query(db: &dyn DefDatabase, def: AttrDefId) -> Attrs { 194 pub(crate) fn attrs_query(db: &dyn DefDatabase, def: AttrDefId) -> Attrs {
61 match def { 195 let raw_attrs = match def {
62 AttrDefId::ModuleId(module) => { 196 AttrDefId::ModuleId(module) => {
63 let def_map = db.crate_def_map(module.krate); 197 let def_map = db.crate_def_map(module.krate);
64 let mod_data = &def_map[module.local_id]; 198 let mod_data = &def_map[module.local_id];
65 match mod_data.declaration_source(db) { 199 match mod_data.declaration_source(db) {
66 Some(it) => { 200 Some(it) => {
67 Attrs::from_attrs_owner(db, it.as_ref().map(|it| it as &dyn AttrsOwner)) 201 RawAttrs::from_attrs_owner(db, it.as_ref().map(|it| it as &dyn AttrsOwner))
68 } 202 }
69 None => Attrs::from_attrs_owner( 203 None => RawAttrs::from_attrs_owner(
70 db, 204 db,
71 mod_data.definition_source(db).as_ref().map(|src| match src { 205 mod_data.definition_source(db).as_ref().map(|src| match src {
72 ModuleSource::SourceFile(file) => file as &dyn AttrsOwner, 206 ModuleSource::SourceFile(file) => file as &dyn AttrsOwner,
@@ -78,14 +212,14 @@ impl Attrs {
78 AttrDefId::FieldId(it) => { 212 AttrDefId::FieldId(it) => {
79 let src = it.parent.child_source(db); 213 let src = it.parent.child_source(db);
80 match &src.value[it.local_id] { 214 match &src.value[it.local_id] {
81 Either::Left(_tuple) => Attrs::default(), 215 Either::Left(_tuple) => RawAttrs::default(),
82 Either::Right(record) => Attrs::from_attrs_owner(db, src.with_value(record)), 216 Either::Right(record) => RawAttrs::from_attrs_owner(db, src.with_value(record)),
83 } 217 }
84 } 218 }
85 AttrDefId::EnumVariantId(var_id) => { 219 AttrDefId::EnumVariantId(var_id) => {
86 let src = var_id.parent.child_source(db); 220 let src = var_id.parent.child_source(db);
87 let src = src.as_ref().map(|it| &it[var_id.local_id]); 221 let src = src.as_ref().map(|it| &it[var_id.local_id]);
88 Attrs::from_attrs_owner(db, src.map(|it| it as &dyn AttrsOwner)) 222 RawAttrs::from_attrs_owner(db, src.map(|it| it as &dyn AttrsOwner))
89 } 223 }
90 AttrDefId::AdtId(it) => match it { 224 AttrDefId::AdtId(it) => match it {
91 AdtId::StructId(it) => attrs_from_item_tree(it.lookup(db).id, db), 225 AdtId::StructId(it) => attrs_from_item_tree(it.lookup(db).id, db),
@@ -101,55 +235,9 @@ impl Attrs {
101 AttrDefId::StaticId(it) => attrs_from_item_tree(it.lookup(db).id, db), 235 AttrDefId::StaticId(it) => attrs_from_item_tree(it.lookup(db).id, db),
102 AttrDefId::FunctionId(it) => attrs_from_item_tree(it.lookup(db).id, db), 236 AttrDefId::FunctionId(it) => attrs_from_item_tree(it.lookup(db).id, db),
103 AttrDefId::TypeAliasId(it) => attrs_from_item_tree(it.lookup(db).id, db), 237 AttrDefId::TypeAliasId(it) => attrs_from_item_tree(it.lookup(db).id, db),
104 }
105 }
106
107 pub fn from_attrs_owner(db: &dyn DefDatabase, owner: InFile<&dyn AttrsOwner>) -> Attrs {
108 let hygiene = Hygiene::new(db.upcast(), owner.file_id);
109 Attrs::new(owner.value, &hygiene)
110 }
111
112 pub(crate) fn new(owner: &dyn AttrsOwner, hygiene: &Hygiene) -> Attrs {
113 let (inner_attrs, inner_docs) = inner_attributes(owner.syntax())
114 .map_or((None, None), |(attrs, docs)| ((Some(attrs), Some(docs))));
115
116 let outer_attrs = owner.attrs().filter(|attr| attr.excl_token().is_none());
117 let attrs = outer_attrs
118 .chain(inner_attrs.into_iter().flatten())
119 .map(|attr| (attr.syntax().text_range().start(), Attr::from_src(attr, hygiene)));
120
121 let outer_docs =
122 ast::CommentIter::from_syntax_node(owner.syntax()).filter(ast::Comment::is_outer);
123 let docs = outer_docs.chain(inner_docs.into_iter().flatten()).map(|docs_text| {
124 (
125 docs_text.syntax().text_range().start(),
126 docs_text.doc_comment().map(|doc| Attr {
127 input: Some(AttrInput::Literal(SmolStr::new(doc))),
128 path: ModPath::from(hir_expand::name!(doc)),
129 }),
130 )
131 });
132 // sort here by syntax node offset because the source can have doc attributes and doc strings be interleaved
133 let attrs: Vec<_> = docs.chain(attrs).sorted_by_key(|&(offset, _)| offset).collect();
134 let entries = if attrs.is_empty() {
135 // Avoid heap allocation
136 None
137 } else {
138 Some(attrs.into_iter().flat_map(|(_, attr)| attr).collect())
139 }; 238 };
140 Attrs { entries }
141 }
142 239
143 pub fn merge(&self, other: Attrs) -> Attrs { 240 raw_attrs.filter(db, def.krate(db))
144 match (&self.entries, &other.entries) {
145 (None, None) => Attrs { entries: None },
146 (Some(entries), None) | (None, Some(entries)) => {
147 Attrs { entries: Some(entries.clone()) }
148 }
149 (Some(a), Some(b)) => {
150 Attrs { entries: Some(a.iter().chain(b.iter()).cloned().collect()) }
151 }
152 }
153 } 241 }
154 242
155 pub fn by_key(&self, key: &'static str) -> AttrQuery<'_> { 243 pub fn by_key(&self, key: &'static str) -> AttrQuery<'_> {
@@ -157,7 +245,6 @@ impl Attrs {
157 } 245 }
158 246
159 pub fn cfg(&self) -> Option<CfgExpr> { 247 pub fn cfg(&self) -> Option<CfgExpr> {
160 // FIXME: handle cfg_attr :-)
161 let mut cfgs = self.by_key("cfg").tt_values().map(CfgExpr::parse).collect::<Vec<_>>(); 248 let mut cfgs = self.by_key("cfg").tt_values().map(CfgExpr::parse).collect::<Vec<_>>();
162 match cfgs.len() { 249 match cfgs.len() {
163 0 => None, 250 0 => None,
@@ -228,6 +315,7 @@ fn inner_attributes(
228 315
229#[derive(Debug, Clone, PartialEq, Eq)] 316#[derive(Debug, Clone, PartialEq, Eq)]
230pub struct Attr { 317pub struct Attr {
318 index: u32,
231 pub(crate) path: ModPath, 319 pub(crate) path: ModPath,
232 pub(crate) input: Option<AttrInput>, 320 pub(crate) input: Option<AttrInput>,
233} 321}
@@ -254,7 +342,59 @@ impl Attr {
254 } else { 342 } else {
255 None 343 None
256 }; 344 };
257 Some(Attr { path, input }) 345 Some(Attr { index: 0, path, input })
346 }
347
348 /// Maps this lowered `Attr` back to its original syntax node.
349 ///
350 /// `owner` must be the original owner of the attribute.
351 ///
352 /// Note that the returned syntax node might be a `#[cfg_attr]`, or a doc comment, instead of
353 /// the attribute represented by `Attr`.
354 pub fn to_src(&self, owner: &dyn AttrsOwner) -> Either<ast::Attr, ast::Comment> {
355 collect_attrs(owner).nth(self.index as usize).unwrap_or_else(|| {
356 panic!("cannot find `Attr` at index {} in {}", self.index, owner.syntax())
357 })
358 }
359
360 /// Parses this attribute as a `#[derive]`, returns an iterator that yields all contained paths
361 /// to derive macros.
362 ///
363 /// Returns `None` when the attribute is not a well-formed `#[derive]` attribute.
364 pub(crate) fn parse_derive(&self) -> Option<impl Iterator<Item = ModPath>> {
365 if self.path.as_ident() != Some(&hir_expand::name![derive]) {
366 return None;
367 }
368
369 match &self.input {
370 Some(AttrInput::TokenTree(args)) => {
371 let mut counter = 0;
372 let paths = args
373 .token_trees
374 .iter()
375 .group_by(move |tt| {
376 match tt {
377 tt::TokenTree::Leaf(tt::Leaf::Punct(p)) if p.char == ',' => {
378 counter += 1;
379 }
380 _ => {}
381 }
382 counter
383 })
384 .into_iter()
385 .map(|(_, tts)| {
386 let segments = tts.filter_map(|tt| match tt {
387 tt::TokenTree::Leaf(tt::Leaf::Ident(id)) => Some(id.as_name()),
388 _ => None,
389 });
390 ModPath::from_segments(PathKind::Plain, segments)
391 })
392 .collect::<Vec<_>>();
393
394 Some(paths.into_iter())
395 }
396 _ => None,
397 }
258 } 398 }
259} 399}
260 400
@@ -283,7 +423,7 @@ impl<'a> AttrQuery<'a> {
283 self.attrs().next().is_some() 423 self.attrs().next().is_some()
284 } 424 }
285 425
286 fn attrs(self) -> impl Iterator<Item = &'a Attr> { 426 pub(crate) fn attrs(self) -> impl Iterator<Item = &'a Attr> {
287 let key = self.key; 427 let key = self.key;
288 self.attrs 428 self.attrs
289 .iter() 429 .iter()
@@ -291,16 +431,36 @@ impl<'a> AttrQuery<'a> {
291 } 431 }
292} 432}
293 433
294fn attrs_from_ast<N>(src: AstId<N>, db: &dyn DefDatabase) -> Attrs 434fn attrs_from_ast<N>(src: AstId<N>, db: &dyn DefDatabase) -> RawAttrs
295where 435where
296 N: ast::AttrsOwner, 436 N: ast::AttrsOwner,
297{ 437{
298 let src = InFile::new(src.file_id, src.to_node(db.upcast())); 438 let src = InFile::new(src.file_id, src.to_node(db.upcast()));
299 Attrs::from_attrs_owner(db, src.as_ref().map(|it| it as &dyn AttrsOwner)) 439 RawAttrs::from_attrs_owner(db, src.as_ref().map(|it| it as &dyn AttrsOwner))
300} 440}
301 441
302fn attrs_from_item_tree<N: ItemTreeNode>(id: ItemTreeId<N>, db: &dyn DefDatabase) -> Attrs { 442fn attrs_from_item_tree<N: ItemTreeNode>(id: ItemTreeId<N>, db: &dyn DefDatabase) -> RawAttrs {
303 let tree = db.item_tree(id.file_id); 443 let tree = db.item_tree(id.file_id);
304 let mod_item = N::id_to_mod_item(id.value); 444 let mod_item = N::id_to_mod_item(id.value);
305 tree.attrs(mod_item.into()).clone() 445 tree.raw_attrs(mod_item.into()).clone()
446}
447
448fn collect_attrs(owner: &dyn AttrsOwner) -> impl Iterator<Item = Either<ast::Attr, ast::Comment>> {
449 let (inner_attrs, inner_docs) = inner_attributes(owner.syntax())
450 .map_or((None, None), |(attrs, docs)| ((Some(attrs), Some(docs))));
451
452 let outer_attrs = owner.attrs().filter(|attr| attr.excl_token().is_none());
453 let attrs = outer_attrs
454 .chain(inner_attrs.into_iter().flatten())
455 .map(|attr| (attr.syntax().text_range().start(), Either::Left(attr)));
456
457 let outer_docs =
458 ast::CommentIter::from_syntax_node(owner.syntax()).filter(ast::Comment::is_outer);
459 let docs = outer_docs
460 .chain(inner_docs.into_iter().flatten())
461 .map(|docs_text| (docs_text.syntax().text_range().start(), Either::Right(docs_text)));
462 // sort here by syntax node offset because the source can have doc attributes and doc strings be interleaved
463 let attrs: Vec<_> = docs.chain(attrs).sorted_by_key(|&(offset, _)| offset).collect();
464
465 attrs.into_iter().map(|(_, attr)| attr)
306} 466}