]>
Commit | Line | Data |
---|---|---|
9ffffee4 FG |
1 | //! A higher level attributes based on TokenTree, with also some shortcuts. |
2 | use std::{fmt, ops, sync::Arc}; | |
3 | ||
4 | use base_db::CrateId; | |
5 | use cfg::CfgExpr; | |
6 | use either::Either; | |
7 | use intern::Interned; | |
8 | use mbe::{syntax_node_to_token_tree, DelimiterKind, Punct}; | |
9 | use smallvec::{smallvec, SmallVec}; | |
10 | use syntax::{ast, match_ast, AstNode, SmolStr, SyntaxNode}; | |
11 | ||
12 | use crate::{ | |
353b0b11 | 13 | db::ExpandDatabase, |
9ffffee4 FG |
14 | hygiene::Hygiene, |
15 | mod_path::{ModPath, PathKind}, | |
16 | name::AsName, | |
17 | tt::{self, Subtree}, | |
18 | InFile, | |
19 | }; | |
20 | ||
21 | /// Syntactical attributes, without filtering of `cfg_attr`s. | |
22 | #[derive(Default, Debug, Clone, PartialEq, Eq)] | |
23 | pub struct RawAttrs { | |
24 | entries: Option<Arc<[Attr]>>, | |
25 | } | |
26 | ||
27 | impl ops::Deref for RawAttrs { | |
28 | type Target = [Attr]; | |
29 | ||
30 | fn deref(&self) -> &[Attr] { | |
31 | match &self.entries { | |
32 | Some(it) => &*it, | |
33 | None => &[], | |
34 | } | |
35 | } | |
36 | } | |
37 | ||
38 | impl RawAttrs { | |
39 | pub const EMPTY: Self = Self { entries: None }; | |
40 | ||
353b0b11 | 41 | pub fn new(db: &dyn ExpandDatabase, owner: &dyn ast::HasAttrs, hygiene: &Hygiene) -> Self { |
9ffffee4 FG |
42 | let entries = collect_attrs(owner) |
43 | .filter_map(|(id, attr)| match attr { | |
44 | Either::Left(attr) => { | |
45 | attr.meta().and_then(|meta| Attr::from_src(db, meta, hygiene, id)) | |
46 | } | |
47 | Either::Right(comment) => comment.doc_comment().map(|doc| Attr { | |
48 | id, | |
49 | input: Some(Interned::new(AttrInput::Literal(SmolStr::new(doc)))), | |
50 | path: Interned::new(ModPath::from(crate::name!(doc))), | |
51 | }), | |
52 | }) | |
53 | .collect::<Arc<_>>(); | |
54 | ||
55 | Self { entries: if entries.is_empty() { None } else { Some(entries) } } | |
56 | } | |
57 | ||
353b0b11 | 58 | pub fn from_attrs_owner(db: &dyn ExpandDatabase, owner: InFile<&dyn ast::HasAttrs>) -> Self { |
9ffffee4 FG |
59 | let hygiene = Hygiene::new(db, owner.file_id); |
60 | Self::new(db, owner.value, &hygiene) | |
61 | } | |
62 | ||
63 | pub fn merge(&self, other: Self) -> Self { | |
64 | match (&self.entries, other.entries) { | |
65 | (None, None) => Self::EMPTY, | |
66 | (None, entries @ Some(_)) => Self { entries }, | |
67 | (Some(entries), None) => Self { entries: Some(entries.clone()) }, | |
68 | (Some(a), Some(b)) => { | |
69 | let last_ast_index = a.last().map_or(0, |it| it.id.ast_index() + 1) as u32; | |
70 | Self { | |
71 | entries: Some( | |
72 | a.iter() | |
73 | .cloned() | |
74 | .chain(b.iter().map(|it| { | |
75 | let mut it = it.clone(); | |
76 | it.id.id = it.id.ast_index() as u32 + last_ast_index | |
77 | | (it.id.cfg_attr_index().unwrap_or(0) as u32) | |
78 | << AttrId::AST_INDEX_BITS; | |
79 | it | |
80 | })) | |
81 | .collect(), | |
82 | ), | |
83 | } | |
84 | } | |
85 | } | |
86 | } | |
87 | ||
88 | /// Processes `cfg_attr`s, returning the resulting semantic `Attrs`. | |
89 | // FIXME: This should return a different type | |
353b0b11 | 90 | pub fn filter(self, db: &dyn ExpandDatabase, krate: CrateId) -> RawAttrs { |
9ffffee4 FG |
91 | let has_cfg_attrs = self |
92 | .iter() | |
93 | .any(|attr| attr.path.as_ident().map_or(false, |name| *name == crate::name![cfg_attr])); | |
94 | if !has_cfg_attrs { | |
95 | return self; | |
96 | } | |
97 | ||
98 | let crate_graph = db.crate_graph(); | |
99 | let new_attrs = self | |
100 | .iter() | |
101 | .flat_map(|attr| -> SmallVec<[_; 1]> { | |
102 | let is_cfg_attr = | |
103 | attr.path.as_ident().map_or(false, |name| *name == crate::name![cfg_attr]); | |
104 | if !is_cfg_attr { | |
105 | return smallvec![attr.clone()]; | |
106 | } | |
107 | ||
108 | let subtree = match attr.token_tree_value() { | |
109 | Some(it) => it, | |
110 | _ => return smallvec![attr.clone()], | |
111 | }; | |
112 | ||
113 | let (cfg, parts) = match parse_cfg_attr_input(subtree) { | |
114 | Some(it) => it, | |
115 | None => return smallvec![attr.clone()], | |
116 | }; | |
117 | let index = attr.id; | |
118 | let attrs = | |
119 | parts.enumerate().take(1 << AttrId::CFG_ATTR_BITS).filter_map(|(idx, attr)| { | |
120 | let tree = Subtree { | |
121 | delimiter: tt::Delimiter::unspecified(), | |
122 | token_trees: attr.to_vec(), | |
123 | }; | |
124 | // FIXME hygiene | |
125 | let hygiene = Hygiene::new_unhygienic(); | |
126 | Attr::from_tt(db, &tree, &hygiene, index.with_cfg_attr(idx)) | |
127 | }); | |
128 | ||
129 | let cfg_options = &crate_graph[krate].cfg_options; | |
130 | let cfg = Subtree { delimiter: subtree.delimiter, token_trees: cfg.to_vec() }; | |
131 | let cfg = CfgExpr::parse(&cfg); | |
132 | if cfg_options.check(&cfg) == Some(false) { | |
133 | smallvec![] | |
134 | } else { | |
135 | cov_mark::hit!(cfg_attr_active); | |
136 | ||
137 | attrs.collect() | |
138 | } | |
139 | }) | |
140 | .collect(); | |
141 | ||
142 | RawAttrs { entries: Some(new_attrs) } | |
143 | } | |
144 | } | |
145 | ||
146 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] | |
147 | pub struct AttrId { | |
148 | id: u32, | |
149 | } | |
150 | ||
151 | // FIXME: This only handles a single level of cfg_attr nesting | |
152 | // that is `#[cfg_attr(all(), cfg_attr(all(), cfg(any())))]` breaks again | |
153 | impl AttrId { | |
154 | const CFG_ATTR_BITS: usize = 7; | |
155 | const AST_INDEX_MASK: usize = 0x00FF_FFFF; | |
156 | const AST_INDEX_BITS: usize = Self::AST_INDEX_MASK.count_ones() as usize; | |
157 | const CFG_ATTR_SET_BITS: u32 = 1 << 31; | |
158 | ||
159 | pub fn ast_index(&self) -> usize { | |
160 | self.id as usize & Self::AST_INDEX_MASK | |
161 | } | |
162 | ||
163 | pub fn cfg_attr_index(&self) -> Option<usize> { | |
164 | if self.id & Self::CFG_ATTR_SET_BITS == 0 { | |
165 | None | |
166 | } else { | |
167 | Some(self.id as usize >> Self::AST_INDEX_BITS) | |
168 | } | |
169 | } | |
170 | ||
171 | pub fn with_cfg_attr(self, idx: usize) -> AttrId { | |
172 | AttrId { id: self.id | (idx as u32) << Self::AST_INDEX_BITS | Self::CFG_ATTR_SET_BITS } | |
173 | } | |
174 | } | |
175 | ||
176 | #[derive(Debug, Clone, PartialEq, Eq)] | |
177 | pub struct Attr { | |
178 | pub id: AttrId, | |
179 | pub path: Interned<ModPath>, | |
180 | pub input: Option<Interned<AttrInput>>, | |
181 | } | |
182 | ||
183 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] | |
184 | pub enum AttrInput { | |
185 | /// `#[attr = "string"]` | |
186 | Literal(SmolStr), | |
187 | /// `#[attr(subtree)]` | |
188 | TokenTree(tt::Subtree, mbe::TokenMap), | |
189 | } | |
190 | ||
191 | impl fmt::Display for AttrInput { | |
192 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
193 | match self { | |
194 | AttrInput::Literal(lit) => write!(f, " = \"{}\"", lit.escape_debug()), | |
195 | AttrInput::TokenTree(subtree, _) => subtree.fmt(f), | |
196 | } | |
197 | } | |
198 | } | |
199 | ||
200 | impl Attr { | |
201 | fn from_src( | |
353b0b11 | 202 | db: &dyn ExpandDatabase, |
9ffffee4 FG |
203 | ast: ast::Meta, |
204 | hygiene: &Hygiene, | |
205 | id: AttrId, | |
206 | ) -> Option<Attr> { | |
207 | let path = Interned::new(ModPath::from_src(db, ast.path()?, hygiene)?); | |
208 | let input = if let Some(ast::Expr::Literal(lit)) = ast.expr() { | |
209 | let value = match lit.kind() { | |
210 | ast::LiteralKind::String(string) => string.value()?.into(), | |
211 | _ => lit.syntax().first_token()?.text().trim_matches('"').into(), | |
212 | }; | |
213 | Some(Interned::new(AttrInput::Literal(value))) | |
214 | } else if let Some(tt) = ast.token_tree() { | |
215 | let (tree, map) = syntax_node_to_token_tree(tt.syntax()); | |
216 | Some(Interned::new(AttrInput::TokenTree(tree, map))) | |
217 | } else { | |
218 | None | |
219 | }; | |
220 | Some(Attr { id, path, input }) | |
221 | } | |
222 | ||
223 | fn from_tt( | |
353b0b11 | 224 | db: &dyn ExpandDatabase, |
9ffffee4 FG |
225 | tt: &tt::Subtree, |
226 | hygiene: &Hygiene, | |
227 | id: AttrId, | |
228 | ) -> Option<Attr> { | |
229 | let (parse, _) = mbe::token_tree_to_syntax_node(tt, mbe::TopEntryPoint::MetaItem); | |
230 | let ast = ast::Meta::cast(parse.syntax_node())?; | |
231 | ||
232 | Self::from_src(db, ast, hygiene, id) | |
233 | } | |
234 | ||
235 | pub fn path(&self) -> &ModPath { | |
236 | &self.path | |
237 | } | |
238 | } | |
239 | ||
240 | impl Attr { | |
241 | /// #[path = "string"] | |
242 | pub fn string_value(&self) -> Option<&SmolStr> { | |
243 | match self.input.as_deref()? { | |
244 | AttrInput::Literal(it) => Some(it), | |
245 | _ => None, | |
246 | } | |
247 | } | |
248 | ||
249 | /// #[path(ident)] | |
250 | pub fn single_ident_value(&self) -> Option<&tt::Ident> { | |
251 | match self.input.as_deref()? { | |
252 | AttrInput::TokenTree(subtree, _) => match &*subtree.token_trees { | |
253 | [tt::TokenTree::Leaf(tt::Leaf::Ident(ident))] => Some(ident), | |
254 | _ => None, | |
255 | }, | |
256 | _ => None, | |
257 | } | |
258 | } | |
259 | ||
260 | /// #[path TokenTree] | |
261 | pub fn token_tree_value(&self) -> Option<&Subtree> { | |
262 | match self.input.as_deref()? { | |
263 | AttrInput::TokenTree(subtree, _) => Some(subtree), | |
264 | _ => None, | |
265 | } | |
266 | } | |
267 | ||
268 | /// Parses this attribute as a token tree consisting of comma separated paths. | |
269 | pub fn parse_path_comma_token_tree(&self) -> Option<impl Iterator<Item = ModPath> + '_> { | |
270 | let args = self.token_tree_value()?; | |
271 | ||
272 | if args.delimiter.kind != DelimiterKind::Parenthesis { | |
273 | return None; | |
274 | } | |
275 | let paths = args | |
276 | .token_trees | |
277 | .split(|tt| matches!(tt, tt::TokenTree::Leaf(tt::Leaf::Punct(Punct { char: ',', .. })))) | |
278 | .filter_map(|tts| { | |
279 | if tts.is_empty() { | |
280 | return None; | |
281 | } | |
282 | let segments = tts.iter().filter_map(|tt| match tt { | |
283 | tt::TokenTree::Leaf(tt::Leaf::Ident(id)) => Some(id.as_name()), | |
284 | _ => None, | |
285 | }); | |
286 | Some(ModPath::from_segments(PathKind::Plain, segments)) | |
287 | }); | |
288 | ||
289 | Some(paths) | |
290 | } | |
291 | } | |
292 | ||
293 | pub fn collect_attrs( | |
294 | owner: &dyn ast::HasAttrs, | |
295 | ) -> impl Iterator<Item = (AttrId, Either<ast::Attr, ast::Comment>)> { | |
296 | let inner_attrs = inner_attributes(owner.syntax()).into_iter().flatten(); | |
297 | let outer_attrs = | |
298 | ast::AttrDocCommentIter::from_syntax_node(owner.syntax()).filter(|el| match el { | |
299 | Either::Left(attr) => attr.kind().is_outer(), | |
300 | Either::Right(comment) => comment.is_outer(), | |
301 | }); | |
302 | outer_attrs.chain(inner_attrs).enumerate().map(|(id, attr)| (AttrId { id: id as u32 }, attr)) | |
303 | } | |
304 | ||
305 | fn inner_attributes( | |
306 | syntax: &SyntaxNode, | |
307 | ) -> Option<impl Iterator<Item = Either<ast::Attr, ast::Comment>>> { | |
308 | let node = match_ast! { | |
309 | match syntax { | |
310 | ast::SourceFile(_) => syntax.clone(), | |
311 | ast::ExternBlock(it) => it.extern_item_list()?.syntax().clone(), | |
312 | ast::Fn(it) => it.body()?.stmt_list()?.syntax().clone(), | |
313 | ast::Impl(it) => it.assoc_item_list()?.syntax().clone(), | |
314 | ast::Module(it) => it.item_list()?.syntax().clone(), | |
315 | ast::BlockExpr(it) => { | |
316 | use syntax::SyntaxKind::{BLOCK_EXPR , EXPR_STMT}; | |
317 | // Block expressions accept outer and inner attributes, but only when they are the outer | |
318 | // expression of an expression statement or the final expression of another block expression. | |
319 | let may_carry_attributes = matches!( | |
320 | it.syntax().parent().map(|it| it.kind()), | |
321 | Some(BLOCK_EXPR | EXPR_STMT) | |
322 | ); | |
323 | if !may_carry_attributes { | |
324 | return None | |
325 | } | |
326 | syntax.clone() | |
327 | }, | |
328 | _ => return None, | |
329 | } | |
330 | }; | |
331 | ||
332 | let attrs = ast::AttrDocCommentIter::from_syntax_node(&node).filter(|el| match el { | |
333 | Either::Left(attr) => attr.kind().is_inner(), | |
334 | Either::Right(comment) => comment.is_inner(), | |
335 | }); | |
336 | Some(attrs) | |
337 | } | |
338 | ||
339 | // Input subtree is: `(cfg, $(attr),+)` | |
340 | // Split it up into a `cfg` subtree and the `attr` subtrees. | |
341 | pub fn parse_cfg_attr_input( | |
342 | subtree: &Subtree, | |
343 | ) -> Option<(&[tt::TokenTree], impl Iterator<Item = &[tt::TokenTree]>)> { | |
344 | let mut parts = subtree | |
345 | .token_trees | |
346 | .split(|tt| matches!(tt, tt::TokenTree::Leaf(tt::Leaf::Punct(Punct { char: ',', .. })))); | |
347 | let cfg = parts.next()?; | |
348 | Some((cfg, parts.filter(|it| !it.is_empty()))) | |
349 | } |