diff options
Diffstat (limited to 'crates/hir_def/src/attr.rs')
-rw-r--r-- | crates/hir_def/src/attr.rs | 208 |
1 files changed, 164 insertions, 44 deletions
diff --git a/crates/hir_def/src/attr.rs b/crates/hir_def/src/attr.rs index 9cd0b72aa..042e119b1 100644 --- a/crates/hir_def/src/attr.rs +++ b/crates/hir_def/src/attr.rs | |||
@@ -5,20 +5,21 @@ use std::{ops, sync::Arc}; | |||
5 | use base_db::CrateId; | 5 | use base_db::CrateId; |
6 | use cfg::{CfgExpr, CfgOptions}; | 6 | use cfg::{CfgExpr, CfgOptions}; |
7 | use either::Either; | 7 | use either::Either; |
8 | use hir_expand::{hygiene::Hygiene, AstId, InFile}; | 8 | use hir_expand::{hygiene::Hygiene, name::AsName, AstId, InFile}; |
9 | use itertools::Itertools; | 9 | use itertools::Itertools; |
10 | use mbe::ast_to_token_tree; | 10 | use mbe::ast_to_token_tree; |
11 | use syntax::{ | 11 | use syntax::{ |
12 | ast::{self, AstNode, AttrsOwner}, | 12 | ast::{self, AstNode, AttrsOwner}, |
13 | match_ast, AstToken, SmolStr, SyntaxNode, | 13 | match_ast, AstToken, SmolStr, SyntaxNode, |
14 | }; | 14 | }; |
15 | use test_utils::mark; | ||
15 | use tt::Subtree; | 16 | use tt::Subtree; |
16 | 17 | ||
17 | use crate::{ | 18 | use crate::{ |
18 | db::DefDatabase, | 19 | db::DefDatabase, |
19 | item_tree::{ItemTreeId, ItemTreeNode}, | 20 | item_tree::{ItemTreeId, ItemTreeNode}, |
20 | nameres::ModuleSource, | 21 | nameres::ModuleSource, |
21 | path::ModPath, | 22 | path::{ModPath, PathKind}, |
22 | src::HasChildSource, | 23 | src::HasChildSource, |
23 | AdtId, AttrDefId, Lookup, | 24 | AdtId, AttrDefId, Lookup, |
24 | }; | 25 | }; |
@@ -41,7 +42,7 @@ impl From<Documentation> for String { | |||
41 | 42 | ||
42 | /// Syntactical attributes, without filtering of `cfg_attr`s. | 43 | /// Syntactical attributes, without filtering of `cfg_attr`s. |
43 | #[derive(Default, Debug, Clone, PartialEq, Eq)] | 44 | #[derive(Default, Debug, Clone, PartialEq, Eq)] |
44 | pub struct RawAttrs { | 45 | pub(crate) struct RawAttrs { |
45 | entries: Option<Arc<[Attr]>>, | 46 | entries: Option<Arc<[Attr]>>, |
46 | } | 47 | } |
47 | 48 | ||
@@ -71,35 +72,34 @@ impl ops::Deref for Attrs { | |||
71 | } | 72 | } |
72 | 73 | ||
73 | impl RawAttrs { | 74 | impl RawAttrs { |
74 | pub const EMPTY: Self = Self { entries: None }; | 75 | pub(crate) const EMPTY: Self = Self { entries: None }; |
75 | 76 | ||
76 | pub(crate) fn new(owner: &dyn AttrsOwner, hygiene: &Hygiene) -> Self { | 77 | pub(crate) fn new(owner: &dyn AttrsOwner, hygiene: &Hygiene) -> Self { |
77 | let (inner_attrs, inner_docs) = inner_attributes(owner.syntax()) | 78 | let attrs: Vec<_> = collect_attrs(owner).collect(); |
78 | .map_or((None, None), |(attrs, docs)| ((Some(attrs), Some(docs)))); | ||
79 | |||
80 | let outer_attrs = owner.attrs().filter(|attr| attr.excl_token().is_none()); | ||
81 | let attrs = outer_attrs | ||
82 | .chain(inner_attrs.into_iter().flatten()) | ||
83 | .map(|attr| (attr.syntax().text_range().start(), Attr::from_src(attr, hygiene))); | ||
84 | |||
85 | let outer_docs = | ||
86 | ast::CommentIter::from_syntax_node(owner.syntax()).filter(ast::Comment::is_outer); | ||
87 | let docs = outer_docs.chain(inner_docs.into_iter().flatten()).map(|docs_text| { | ||
88 | ( | ||
89 | docs_text.syntax().text_range().start(), | ||
90 | docs_text.doc_comment().map(|doc| Attr { | ||
91 | input: Some(AttrInput::Literal(SmolStr::new(doc))), | ||
92 | path: ModPath::from(hir_expand::name!(doc)), | ||
93 | }), | ||
94 | ) | ||
95 | }); | ||
96 | // sort here by syntax node offset because the source can have doc attributes and doc strings be interleaved | ||
97 | let attrs: Vec<_> = docs.chain(attrs).sorted_by_key(|&(offset, _)| offset).collect(); | ||
98 | let entries = if attrs.is_empty() { | 79 | let entries = if attrs.is_empty() { |
99 | // Avoid heap allocation | 80 | // Avoid heap allocation |
100 | None | 81 | None |
101 | } else { | 82 | } else { |
102 | Some(attrs.into_iter().flat_map(|(_, attr)| attr).collect()) | 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 | }; | 103 | }; |
104 | Self { entries } | 104 | Self { entries } |
105 | } | 105 | } |
@@ -122,9 +122,69 @@ impl RawAttrs { | |||
122 | } | 122 | } |
123 | 123 | ||
124 | /// Processes `cfg_attr`s, returning the resulting semantic `Attrs`. | 124 | /// Processes `cfg_attr`s, returning the resulting semantic `Attrs`. |
125 | pub(crate) fn filter(self, _db: &dyn DefDatabase, _krate: CrateId) -> Attrs { | 125 | pub(crate) fn filter(self, db: &dyn DefDatabase, krate: CrateId) -> Attrs { |
126 | // FIXME actually implement this | 126 | let has_cfg_attrs = self.iter().any(|attr| { |
127 | Attrs(self) | 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) }) | ||
128 | } | 188 | } |
129 | } | 189 | } |
130 | 190 | ||
@@ -180,24 +240,11 @@ impl Attrs { | |||
180 | raw_attrs.filter(db, def.krate(db)) | 240 | raw_attrs.filter(db, def.krate(db)) |
181 | } | 241 | } |
182 | 242 | ||
183 | pub fn merge(&self, other: Attrs) -> Attrs { | ||
184 | match (&self.0.entries, &other.0.entries) { | ||
185 | (None, None) => Attrs::EMPTY, | ||
186 | (Some(entries), None) | (None, Some(entries)) => { | ||
187 | Attrs(RawAttrs { entries: Some(entries.clone()) }) | ||
188 | } | ||
189 | (Some(a), Some(b)) => { | ||
190 | Attrs(RawAttrs { entries: Some(a.iter().chain(b.iter()).cloned().collect()) }) | ||
191 | } | ||
192 | } | ||
193 | } | ||
194 | |||
195 | pub fn by_key(&self, key: &'static str) -> AttrQuery<'_> { | 243 | pub fn by_key(&self, key: &'static str) -> AttrQuery<'_> { |
196 | AttrQuery { attrs: self, key } | 244 | AttrQuery { attrs: self, key } |
197 | } | 245 | } |
198 | 246 | ||
199 | pub fn cfg(&self) -> Option<CfgExpr> { | 247 | pub fn cfg(&self) -> Option<CfgExpr> { |
200 | // FIXME: handle cfg_attr :-) | ||
201 | 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<_>>(); |
202 | match cfgs.len() { | 249 | match cfgs.len() { |
203 | 0 => None, | 250 | 0 => None, |
@@ -268,6 +315,7 @@ fn inner_attributes( | |||
268 | 315 | ||
269 | #[derive(Debug, Clone, PartialEq, Eq)] | 316 | #[derive(Debug, Clone, PartialEq, Eq)] |
270 | pub struct Attr { | 317 | pub struct Attr { |
318 | index: u32, | ||
271 | pub(crate) path: ModPath, | 319 | pub(crate) path: ModPath, |
272 | pub(crate) input: Option<AttrInput>, | 320 | pub(crate) input: Option<AttrInput>, |
273 | } | 321 | } |
@@ -294,7 +342,59 @@ impl Attr { | |||
294 | } else { | 342 | } else { |
295 | None | 343 | None |
296 | }; | 344 | }; |
297 | 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 | } | ||
298 | } | 398 | } |
299 | } | 399 | } |
300 | 400 | ||
@@ -323,7 +423,7 @@ impl<'a> AttrQuery<'a> { | |||
323 | self.attrs().next().is_some() | 423 | self.attrs().next().is_some() |
324 | } | 424 | } |
325 | 425 | ||
326 | fn attrs(self) -> impl Iterator<Item = &'a Attr> { | 426 | pub(crate) fn attrs(self) -> impl Iterator<Item = &'a Attr> { |
327 | let key = self.key; | 427 | let key = self.key; |
328 | self.attrs | 428 | self.attrs |
329 | .iter() | 429 | .iter() |
@@ -344,3 +444,23 @@ fn attrs_from_item_tree<N: ItemTreeNode>(id: ItemTreeId<N>, db: &dyn DefDatabase | |||
344 | let mod_item = N::id_to_mod_item(id.value); | 444 | let mod_item = N::id_to_mod_item(id.value); |
345 | tree.raw_attrs(mod_item.into()).clone() | 445 | tree.raw_attrs(mod_item.into()).clone() |
346 | } | 446 | } |
447 | |||
448 | fn 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) | ||
466 | } | ||