]> git.proxmox.com Git - rustc.git/blame - src/tools/rust-analyzer/crates/hir-expand/src/attrs.rs
New upstream version 1.70.0+dfsg1
[rustc.git] / src / tools / rust-analyzer / crates / hir-expand / src / attrs.rs
CommitLineData
9ffffee4
FG
1//! A higher level attributes based on TokenTree, with also some shortcuts.
2use std::{fmt, ops, sync::Arc};
3
4use base_db::CrateId;
5use cfg::CfgExpr;
6use either::Either;
7use intern::Interned;
8use mbe::{syntax_node_to_token_tree, DelimiterKind, Punct};
9use smallvec::{smallvec, SmallVec};
10use syntax::{ast, match_ast, AstNode, SmolStr, SyntaxNode};
11
12use 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)]
23pub struct RawAttrs {
24 entries: Option<Arc<[Attr]>>,
25}
26
27impl 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
38impl 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)]
147pub 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
153impl 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)]
177pub 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)]
184pub enum AttrInput {
185 /// `#[attr = "string"]`
186 Literal(SmolStr),
187 /// `#[attr(subtree)]`
188 TokenTree(tt::Subtree, mbe::TokenMap),
189}
190
191impl 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
200impl 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
240impl 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
293pub 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
305fn 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.
341pub 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}