diff options
Diffstat (limited to 'crates/hir_def/src/attr.rs')
-rw-r--r-- | crates/hir_def/src/attr.rs | 290 |
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 | ||
3 | use std::{ops, sync::Arc}; | 3 | use std::{ops, sync::Arc}; |
4 | 4 | ||
5 | use base_db::CrateId; | ||
5 | use cfg::{CfgExpr, CfgOptions}; | 6 | use cfg::{CfgExpr, CfgOptions}; |
6 | use either::Either; | 7 | use either::Either; |
7 | use hir_expand::{hygiene::Hygiene, AstId, InFile}; | 8 | use hir_expand::{hygiene::Hygiene, name::AsName, AstId, InFile}; |
8 | use itertools::Itertools; | 9 | use itertools::Itertools; |
9 | use mbe::ast_to_token_tree; | 10 | use mbe::ast_to_token_tree; |
10 | use syntax::{ | 11 | use 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 | }; |
15 | use test_utils::mark; | ||
14 | use tt::Subtree; | 16 | use tt::Subtree; |
15 | 17 | ||
16 | use crate::{ | 18 | use 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)] |
42 | pub struct Attrs { | 45 | pub(crate) struct RawAttrs { |
43 | entries: Option<Arc<[Attr]>>, | 46 | entries: Option<Arc<[Attr]>>, |
44 | } | 47 | } |
45 | 48 | ||
46 | impl ops::Deref for Attrs { | 49 | #[derive(Default, Debug, Clone, PartialEq, Eq)] |
50 | pub struct Attrs(RawAttrs); | ||
51 | |||
52 | impl 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 | ||
63 | impl 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 | |||
74 | impl 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 | |||
57 | impl Attrs { | 191 | impl 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)] |
230 | pub struct Attr { | 317 | pub 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 | ||
294 | fn attrs_from_ast<N>(src: AstId<N>, db: &dyn DefDatabase) -> Attrs | 434 | fn attrs_from_ast<N>(src: AstId<N>, db: &dyn DefDatabase) -> RawAttrs |
295 | where | 435 | where |
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 | ||
302 | fn attrs_from_item_tree<N: ItemTreeNode>(id: ItemTreeId<N>, db: &dyn DefDatabase) -> Attrs { | 442 | fn 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 | |||
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) | ||
306 | } | 466 | } |