1use std::iter;
3use std::{borrow::Cow, fmt, ops};
4
5use base_db::Crate;
6use cfg::{CfgExpr, CfgOptions};
7use either::Either;
8use intern::{Interned, Symbol, sym};
9
10use mbe::{DelimiterKind, Punct};
11use smallvec::{SmallVec, smallvec};
12use span::{Span, SyntaxContext};
13use syntax::unescape;
14use syntax::{AstNode, AstToken, SyntaxNode, ast, match_ast};
15use syntax_bridge::{DocCommentDesugarMode, desugar_doc_comment_text, syntax_node_to_token_tree};
16use triomphe::ThinArc;
17
18use crate::{
19 db::ExpandDatabase,
20 mod_path::ModPath,
21 name::Name,
22 span_map::SpanMapRef,
23 tt::{self, TopSubtree, token_to_literal},
24};
25
26#[derive(Default, Debug, Clone, PartialEq, Eq)]
28pub struct RawAttrs {
29 entries: Option<ThinArc<(), Attr>>,
31}
32
33impl ops::Deref for RawAttrs {
34 type Target = [Attr];
35
36 fn deref(&self) -> &[Attr] {
37 match &self.entries {
38 Some(it) => &it.slice,
39 None => &[],
40 }
41 }
42}
43
44impl RawAttrs {
45 pub const EMPTY: Self = Self { entries: None };
46
47 pub fn new(
48 db: &dyn ExpandDatabase,
49 owner: &dyn ast::HasAttrs,
50 span_map: SpanMapRef<'_>,
51 ) -> Self {
52 let entries: Vec<_> = Self::attrs_iter::<true>(db, owner, span_map).collect();
53
54 let entries = if entries.is_empty() {
55 None
56 } else {
57 Some(ThinArc::from_header_and_iter((), entries.into_iter()))
58 };
59
60 RawAttrs { entries }
61 }
62
63 pub fn new_expanded(
65 db: &dyn ExpandDatabase,
66 owner: &dyn ast::HasAttrs,
67 span_map: SpanMapRef<'_>,
68 cfg_options: &CfgOptions,
69 ) -> Self {
70 let entries: Vec<_> =
71 Self::attrs_iter_expanded::<true>(db, owner, span_map, cfg_options).collect();
72
73 let entries = if entries.is_empty() {
74 None
75 } else {
76 Some(ThinArc::from_header_and_iter((), entries.into_iter()))
77 };
78
79 RawAttrs { entries }
80 }
81
82 pub fn attrs_iter<const DESUGAR_COMMENTS: bool>(
83 db: &dyn ExpandDatabase,
84 owner: &dyn ast::HasAttrs,
85 span_map: SpanMapRef<'_>,
86 ) -> impl Iterator<Item = Attr> {
87 collect_attrs(owner).filter_map(move |(id, attr)| match attr {
88 Either::Left(attr) => {
89 attr.meta().and_then(|meta| Attr::from_src(db, meta, span_map, id))
90 }
91 Either::Right(comment) if DESUGAR_COMMENTS => comment.doc_comment().map(|doc| {
92 let span = span_map.span_for_range(comment.syntax().text_range());
93 let (text, kind) = desugar_doc_comment_text(doc, DocCommentDesugarMode::ProcMacro);
94 Attr {
95 id,
96 input: Some(Box::new(AttrInput::Literal(tt::Literal {
97 symbol: text,
98 span,
99 kind,
100 suffix: None,
101 }))),
102 path: Interned::new(ModPath::from(Name::new_symbol(sym::doc, span.ctx))),
103 ctxt: span.ctx,
104 }
105 }),
106 Either::Right(_) => None,
107 })
108 }
109
110 pub fn attrs_iter_expanded<const DESUGAR_COMMENTS: bool>(
111 db: &dyn ExpandDatabase,
112 owner: &dyn ast::HasAttrs,
113 span_map: SpanMapRef<'_>,
114 cfg_options: &CfgOptions,
115 ) -> impl Iterator<Item = Attr> {
116 Self::attrs_iter::<DESUGAR_COMMENTS>(db, owner, span_map)
117 .flat_map(|attr| attr.expand_cfg_attr(db, cfg_options))
118 }
119
120 pub fn merge(&self, other: Self) -> Self {
121 match (&self.entries, other.entries) {
122 (None, None) => Self::EMPTY,
123 (None, entries @ Some(_)) => Self { entries },
124 (Some(entries), None) => Self { entries: Some(entries.clone()) },
125 (Some(a), Some(b)) => {
126 let last_ast_index = a.slice.last().map_or(0, |it| it.id.ast_index() + 1);
127 let items = a
128 .slice
129 .iter()
130 .cloned()
131 .chain(b.slice.iter().map(|it| {
132 let mut it = it.clone();
133 let id = it.id.ast_index() + last_ast_index;
134 it.id = AttrId::new(id, it.id.is_inner_attr());
135 it
136 }))
137 .collect::<Vec<_>>();
138 Self { entries: Some(ThinArc::from_header_and_iter((), items.into_iter())) }
139 }
140 }
141 }
142
143 pub fn expand_cfg_attr(self, db: &dyn ExpandDatabase, krate: Crate) -> RawAttrs {
145 let has_cfg_attrs =
146 self.iter().any(|attr| attr.path.as_ident().is_some_and(|name| *name == sym::cfg_attr));
147 if !has_cfg_attrs {
148 return self;
149 }
150
151 let cfg_options = krate.cfg_options(db);
152 let new_attrs = self
153 .iter()
154 .cloned()
155 .flat_map(|attr| attr.expand_cfg_attr(db, cfg_options))
156 .collect::<Vec<_>>();
157 let entries = if new_attrs.is_empty() {
158 None
159 } else {
160 Some(ThinArc::from_header_and_iter((), new_attrs.into_iter()))
161 };
162 RawAttrs { entries }
163 }
164
165 pub fn is_empty(&self) -> bool {
166 self.entries.is_none()
167 }
168}
169
170#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
171pub struct AttrId {
172 id: u32,
173}
174
175impl AttrId {
178 const INNER_ATTR_SET_BIT: u32 = 1 << 31;
179
180 pub fn new(id: usize, is_inner: bool) -> Self {
181 assert!(id <= !Self::INNER_ATTR_SET_BIT as usize);
182 let id = id as u32;
183 Self { id: if is_inner { id | Self::INNER_ATTR_SET_BIT } else { id } }
184 }
185
186 pub fn ast_index(&self) -> usize {
187 (self.id & !Self::INNER_ATTR_SET_BIT) as usize
188 }
189
190 pub fn is_inner_attr(&self) -> bool {
191 self.id & Self::INNER_ATTR_SET_BIT != 0
192 }
193}
194
195#[derive(Debug, Clone, PartialEq, Eq)]
196pub struct Attr {
197 pub id: AttrId,
198 pub path: Interned<ModPath>,
199 pub input: Option<Box<AttrInput>>,
200 pub ctxt: SyntaxContext,
201}
202
203#[derive(Debug, Clone, PartialEq, Eq, Hash)]
204pub enum AttrInput {
205 Literal(tt::Literal),
207 TokenTree(tt::TopSubtree),
209}
210
211impl fmt::Display for AttrInput {
212 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
213 match self {
214 AttrInput::Literal(lit) => write!(f, " = {lit}"),
215 AttrInput::TokenTree(tt) => tt.fmt(f),
216 }
217 }
218}
219
220impl Attr {
221 fn from_src(
222 db: &dyn ExpandDatabase,
223 ast: ast::Meta,
224 span_map: SpanMapRef<'_>,
225 id: AttrId,
226 ) -> Option<Attr> {
227 let path = ast.path()?;
228 let range = path.syntax().text_range();
229 let path = Interned::new(ModPath::from_src(db, path, &mut |range| {
230 span_map.span_for_range(range).ctx
231 })?);
232 let span = span_map.span_for_range(range);
233 let input = if let Some(ast::Expr::Literal(lit)) = ast.expr() {
234 let token = lit.token();
235 Some(Box::new(AttrInput::Literal(token_to_literal(token.text(), span))))
236 } else if let Some(tt) = ast.token_tree() {
237 let tree = syntax_node_to_token_tree(
238 tt.syntax(),
239 span_map,
240 span,
241 DocCommentDesugarMode::ProcMacro,
242 );
243 Some(Box::new(AttrInput::TokenTree(tree)))
244 } else {
245 None
246 };
247 Some(Attr { id, path, input, ctxt: span.ctx })
248 }
249
250 fn from_tt(
251 db: &dyn ExpandDatabase,
252 mut tt: tt::TokenTreesView<'_>,
253 id: AttrId,
254 ) -> Option<Attr> {
255 if matches!(tt.flat_tokens(),
256 [tt::TokenTree::Leaf(tt::Leaf::Ident(tt::Ident { sym, .. })), ..]
257 if *sym == sym::unsafe_
258 ) {
259 match tt.iter().nth(1) {
260 Some(tt::TtElement::Subtree(_, iter)) => tt = iter.remaining(),
261 _ => return None,
262 }
263 }
264 let first = tt.flat_tokens().first()?;
265 let ctxt = first.first_span().ctx;
266 let (path, input) = {
267 let mut iter = tt.iter();
268 let start = iter.savepoint();
269 let mut input = tt::TokenTreesView::new(&[]);
270 let mut path = iter.from_savepoint(start);
271 let mut path_split_savepoint = iter.savepoint();
272 while let Some(tt) = iter.next() {
273 path = iter.from_savepoint(start);
274 if !matches!(
275 tt,
276 tt::TtElement::Leaf(
277 tt::Leaf::Punct(tt::Punct { char: ':' | '$', .. }) | tt::Leaf::Ident(_),
278 )
279 ) {
280 input = path_split_savepoint.remaining();
281 break;
282 }
283 path_split_savepoint = iter.savepoint();
284 }
285 (path, input)
286 };
287
288 let path = Interned::new(ModPath::from_tt(db, path)?);
289
290 let input = match (input.flat_tokens().first(), input.try_into_subtree()) {
291 (_, Some(tree)) => {
292 Some(Box::new(AttrInput::TokenTree(tt::TopSubtree::from_subtree(tree))))
293 }
294 (Some(tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { char: '=', .. }))), _) => {
295 match input.flat_tokens().get(1) {
296 Some(tt::TokenTree::Leaf(tt::Leaf::Literal(lit))) => {
297 Some(Box::new(AttrInput::Literal(lit.clone())))
298 }
299 _ => None,
300 }
301 }
302 _ => None,
303 };
304 Some(Attr { id, path, input, ctxt })
305 }
306
307 pub fn path(&self) -> &ModPath {
308 &self.path
309 }
310
311 pub fn expand_cfg_attr(
312 self,
313 db: &dyn ExpandDatabase,
314 cfg_options: &CfgOptions,
315 ) -> impl IntoIterator<Item = Self> {
316 let is_cfg_attr = self.path.as_ident().is_some_and(|name| *name == sym::cfg_attr);
317 if !is_cfg_attr {
318 return smallvec![self];
319 }
320
321 let subtree = match self.token_tree_value() {
322 Some(it) => it,
323 _ => return smallvec![self.clone()],
324 };
325
326 let (cfg, parts) = match parse_cfg_attr_input(subtree) {
327 Some(it) => it,
328 None => return smallvec![self.clone()],
329 };
330 let index = self.id;
331 let attrs = parts.filter_map(|attr| Attr::from_tt(db, attr, index));
332
333 let cfg = TopSubtree::from_token_trees(subtree.top_subtree().delimiter, cfg);
334 let cfg = CfgExpr::parse(&cfg);
335 if cfg_options.check(&cfg) == Some(false) {
336 smallvec![]
337 } else {
338 cov_mark::hit!(cfg_attr_active);
339
340 attrs.collect::<SmallVec<[_; 1]>>()
341 }
342 }
343}
344
345impl Attr {
346 pub fn string_value(&self) -> Option<&Symbol> {
348 match self.input.as_deref()? {
349 AttrInput::Literal(tt::Literal {
350 symbol: text,
351 kind: tt::LitKind::Str | tt::LitKind::StrRaw(_),
352 ..
353 }) => Some(text),
354 _ => None,
355 }
356 }
357
358 pub fn string_value_with_span(&self) -> Option<(&Symbol, span::Span)> {
360 match self.input.as_deref()? {
361 AttrInput::Literal(tt::Literal {
362 symbol: text,
363 kind: tt::LitKind::Str | tt::LitKind::StrRaw(_),
364 span,
365 suffix: _,
366 }) => Some((text, *span)),
367 _ => None,
368 }
369 }
370
371 pub fn string_value_unescape(&self) -> Option<Cow<'_, str>> {
372 match self.input.as_deref()? {
373 AttrInput::Literal(tt::Literal {
374 symbol: text, kind: tt::LitKind::StrRaw(_), ..
375 }) => Some(Cow::Borrowed(text.as_str())),
376 AttrInput::Literal(tt::Literal { symbol: text, kind: tt::LitKind::Str, .. }) => {
377 unescape(text.as_str())
378 }
379 _ => None,
380 }
381 }
382
383 pub fn single_ident_value(&self) -> Option<&tt::Ident> {
385 match self.input.as_deref()? {
386 AttrInput::TokenTree(tt) => match tt.token_trees().flat_tokens() {
387 [tt::TokenTree::Leaf(tt::Leaf::Ident(ident))] => Some(ident),
388 _ => None,
389 },
390 _ => None,
391 }
392 }
393
394 pub fn token_tree_value(&self) -> Option<&TopSubtree> {
396 match self.input.as_deref()? {
397 AttrInput::TokenTree(tt) => Some(tt),
398 _ => None,
399 }
400 }
401
402 pub fn parse_path_comma_token_tree<'a>(
404 &'a self,
405 db: &'a dyn ExpandDatabase,
406 ) -> Option<impl Iterator<Item = (ModPath, Span)> + 'a> {
407 let args = self.token_tree_value()?;
408
409 if args.top_subtree().delimiter.kind != DelimiterKind::Parenthesis {
410 return None;
411 }
412 let paths = args
413 .token_trees()
414 .split(|tt| matches!(tt, tt::TtElement::Leaf(tt::Leaf::Punct(Punct { char: ',', .. }))))
415 .filter_map(move |tts| {
416 let span = tts.flat_tokens().first()?.first_span();
417 Some((ModPath::from_tt(db, tts)?, span))
418 });
419
420 Some(paths)
421 }
422
423 pub fn cfg(&self) -> Option<CfgExpr> {
424 if *self.path.as_ident()? == sym::cfg {
425 self.token_tree_value().map(CfgExpr::parse)
426 } else {
427 None
428 }
429 }
430}
431
432fn unescape(s: &str) -> Option<Cow<'_, str>> {
433 let mut buf = String::new();
434 let mut prev_end = 0;
435 let mut has_error = false;
436 unescape::unescape_str(s, |char_range, unescaped_char| {
437 match (unescaped_char, buf.capacity() == 0) {
438 (Ok(c), false) => buf.push(c),
439 (Ok(_), true) if char_range.len() == 1 && char_range.start == prev_end => {
440 prev_end = char_range.end
441 }
442 (Ok(c), true) => {
443 buf.reserve_exact(s.len());
444 buf.push_str(&s[..prev_end]);
445 buf.push(c);
446 }
447 (Err(_), _) => has_error = true,
448 }
449 });
450
451 match (has_error, buf.capacity() == 0) {
452 (true, _) => None,
453 (false, false) => Some(Cow::Owned(buf)),
454 (false, true) => Some(Cow::Borrowed(s)),
455 }
456}
457
458pub fn collect_attrs(
459 owner: &dyn ast::HasAttrs,
460) -> impl Iterator<Item = (AttrId, Either<ast::Attr, ast::Comment>)> {
461 let inner_attrs =
462 inner_attributes(owner.syntax()).into_iter().flatten().zip(iter::repeat(true));
463 let outer_attrs = ast::AttrDocCommentIter::from_syntax_node(owner.syntax())
464 .filter(|el| match el {
465 Either::Left(attr) => attr.kind().is_outer(),
466 Either::Right(comment) => comment.is_outer(),
467 })
468 .zip(iter::repeat(false));
469 outer_attrs
470 .chain(inner_attrs)
471 .enumerate()
472 .map(|(id, (attr, is_inner))| (AttrId::new(id, is_inner), attr))
473}
474
475fn inner_attributes(
476 syntax: &SyntaxNode,
477) -> Option<impl Iterator<Item = Either<ast::Attr, ast::Comment>>> {
478 let node = match_ast! {
479 match syntax {
480 ast::SourceFile(_) => syntax.clone(),
481 ast::ExternBlock(it) => it.extern_item_list()?.syntax().clone(),
482 ast::Fn(it) => it.body()?.stmt_list()?.syntax().clone(),
483 ast::Impl(it) => it.assoc_item_list()?.syntax().clone(),
484 ast::Module(it) => it.item_list()?.syntax().clone(),
485 ast::BlockExpr(it) => {
486 if !it.may_carry_attributes() {
487 return None
488 }
489 syntax.clone()
490 },
491 _ => return None,
492 }
493 };
494
495 let attrs = ast::AttrDocCommentIter::from_syntax_node(&node).filter(|el| match el {
496 Either::Left(attr) => attr.kind().is_inner(),
497 Either::Right(comment) => comment.is_inner(),
498 });
499 Some(attrs)
500}
501
502fn parse_cfg_attr_input(
505 subtree: &TopSubtree,
506) -> Option<(tt::TokenTreesView<'_>, impl Iterator<Item = tt::TokenTreesView<'_>>)> {
507 let mut parts = subtree
508 .token_trees()
509 .split(|tt| matches!(tt, tt::TtElement::Leaf(tt::Leaf::Punct(Punct { char: ',', .. }))));
510 let cfg = parts.next()?;
511 Some((cfg, parts.filter(|it| !it.is_empty())))
512}