1use std::{
30 borrow::Cow, cell::OnceCell, convert::Infallible, fmt, iter::Peekable, ops::ControlFlow,
31};
32
33use ::tt::{TextRange, TextSize};
34use arrayvec::ArrayVec;
35use base_db::Crate;
36use cfg::{CfgExpr, CfgOptions};
37use either::Either;
38use intern::{Interned, Symbol};
39use mbe::{DelimiterKind, Punct};
40use parser::T;
41use smallvec::SmallVec;
42use span::{RealSpanMap, Span, SyntaxContext};
43use syntax::{
44 AstNode, NodeOrToken, SyntaxNode, SyntaxToken,
45 ast::{self, TokenTreeChildren},
46 unescape,
47};
48use syntax_bridge::DocCommentDesugarMode;
49
50use crate::{
51 AstId,
52 db::ExpandDatabase,
53 mod_path::ModPath,
54 span_map::SpanMapRef,
55 tt::{self, TopSubtree},
56};
57
58#[derive(Debug)]
59pub struct AttrPath {
60 pub segments: ArrayVec<SyntaxToken, 2>,
62 pub range: TextRange,
63 pub is_test: bool,
69}
70
71impl AttrPath {
72 #[inline]
73 fn extract(path: &ast::Path) -> Self {
74 let mut is_test = false;
75 let segments = (|| {
76 let mut segments = ArrayVec::new();
77 let segment2 = path.segment()?.name_ref()?.syntax().first_token()?;
78 if segment2.text() == "test" {
79 is_test = true;
81 }
82 let segment1 = path.qualifier();
83 if let Some(segment1) = segment1 {
84 if segment1.qualifier().is_some() {
85 None
86 } else {
87 let segment1 = segment1.segment()?.name_ref()?.syntax().first_token()?;
88 segments.push(segment1);
89 segments.push(segment2);
90 Some(segments)
91 }
92 } else {
93 segments.push(segment2);
94 Some(segments)
95 }
96 })();
97 AttrPath {
98 segments: segments.unwrap_or(ArrayVec::new()),
99 range: path.syntax().text_range(),
100 is_test,
101 }
102 }
103
104 #[inline]
105 pub fn is1(&self, segment: &str) -> bool {
106 self.segments.len() == 1 && self.segments[0].text() == segment
107 }
108}
109
110#[derive(Debug)]
111pub enum Meta {
112 NamedKeyValue {
114 path_range: TextRange,
115 name: Option<SyntaxToken>,
116 value: Option<SyntaxToken>,
117 },
118 TokenTree {
119 path: AttrPath,
120 tt: ast::TokenTree,
121 },
122 Path {
123 path: AttrPath,
124 },
125}
126
127impl Meta {
128 #[inline]
129 pub fn path_range(&self) -> TextRange {
130 match self {
131 Meta::NamedKeyValue { path_range, .. } => *path_range,
132 Meta::TokenTree { path, .. } | Meta::Path { path } => path.range,
133 }
134 }
135
136 fn extract(iter: &mut Peekable<TokenTreeChildren>) -> Option<(Self, TextSize)> {
137 let mut start_offset = None;
138 if let Some(NodeOrToken::Token(colon1)) = iter.peek()
139 && colon1.kind() == T![:]
140 {
141 start_offset = Some(colon1.text_range().start());
142 iter.next();
143 iter.next_if(|it| it.as_token().is_some_and(|it| it.kind() == T![:]));
144 }
145 let first_segment = iter
146 .next_if(|it| it.as_token().is_some_and(|it| it.kind().is_any_identifier()))?
147 .into_token()?;
148 let mut is_test = first_segment.text() == "test";
149 let start_offset = start_offset.unwrap_or_else(|| first_segment.text_range().start());
150
151 let mut segments_len = 1;
152 let mut second_segment = None;
153 let mut path_range = first_segment.text_range();
154 while iter.peek().and_then(NodeOrToken::as_token).is_some_and(|it| it.kind() == T![:])
155 && let _ = iter.next()
156 && iter.peek().and_then(NodeOrToken::as_token).is_some_and(|it| it.kind() == T![:])
157 && let _ = iter.next()
158 && let Some(NodeOrToken::Token(segment)) = iter.peek()
159 && segment.kind().is_any_identifier()
160 {
161 segments_len += 1;
162 is_test = segment.text() == "test";
163 second_segment = Some(segment.clone());
164 path_range = TextRange::new(path_range.start(), segment.text_range().end());
165 iter.next();
166 }
167
168 let segments = |first, second| {
169 let mut segments = ArrayVec::new();
170 if segments_len <= 2 {
171 segments.push(first);
172 if let Some(second) = second {
173 segments.push(second);
174 }
175 }
176 segments
177 };
178 let meta = match iter.peek() {
179 Some(NodeOrToken::Token(eq)) if eq.kind() == T![=] => {
180 iter.next();
181 let value = match iter.peek() {
182 Some(NodeOrToken::Token(token)) if token.kind().is_literal() => {
183 Some(token.clone())
185 }
186 _ => None,
187 };
188 let name = if second_segment.is_none() { Some(first_segment) } else { None };
189 Meta::NamedKeyValue { path_range, name, value }
190 }
191 Some(NodeOrToken::Node(tt)) => Meta::TokenTree {
192 path: AttrPath {
193 segments: segments(first_segment, second_segment),
194 range: path_range,
195 is_test,
196 },
197 tt: tt.clone(),
198 },
199 _ => Meta::Path {
200 path: AttrPath {
201 segments: segments(first_segment, second_segment),
202 range: path_range,
203 is_test,
204 },
205 },
206 };
207 Some((meta, start_offset))
208 }
209
210 fn extract_possibly_unsafe(
211 iter: &mut Peekable<TokenTreeChildren>,
212 container: &ast::TokenTree,
213 ) -> Option<(Self, TextRange)> {
214 if iter.peek().is_some_and(|it| it.as_token().is_some_and(|it| it.kind() == T![unsafe])) {
215 iter.next();
216 let tt = iter.next()?.into_node()?;
217 let result = Self::extract(&mut TokenTreeChildren::new(&tt).peekable()).map(
218 |(meta, start_offset)| (meta, TextRange::new(start_offset, tt_end_offset(&tt))),
219 );
220 while iter.next().is_some_and(|it| it.as_token().is_none_or(|it| it.kind() != T![,])) {}
221 result
222 } else {
223 Self::extract(iter).map(|(meta, start_offset)| {
224 let end_offset = 'find_end_offset: {
225 for it in iter {
226 if let NodeOrToken::Token(it) = it
227 && it.kind() == T![,]
228 {
229 break 'find_end_offset it.text_range().start();
230 }
231 }
232 tt_end_offset(container)
233 };
234 (meta, TextRange::new(start_offset, end_offset))
235 })
236 }
237 }
238}
239
240fn tt_end_offset(tt: &ast::TokenTree) -> TextSize {
241 tt.syntax().last_token().unwrap().text_range().start()
242}
243
244#[inline]
248pub fn expand_cfg_attr<'a, BreakValue>(
249 attrs: impl Iterator<Item = ast::Attr>,
250 cfg_options: impl FnMut() -> &'a CfgOptions,
251 mut callback: impl FnMut(Meta, &SyntaxNode, TextRange, &ast::Attr) -> ControlFlow<BreakValue>,
252) -> Option<BreakValue> {
253 expand_cfg_attr_with_doc_comments::<Infallible, _>(
254 attrs.map(Either::Left),
255 cfg_options,
256 move |Either::Left((meta, container, range, top_attr))| {
257 callback(meta, container, range, top_attr)
258 },
259 )
260}
261
262#[inline]
263pub fn expand_cfg_attr_with_doc_comments<'a, DocComment, BreakValue>(
264 mut attrs: impl Iterator<Item = Either<ast::Attr, DocComment>>,
265 mut cfg_options: impl FnMut() -> &'a CfgOptions,
266 mut callback: impl FnMut(
267 Either<(Meta, &SyntaxNode, TextRange, &ast::Attr), DocComment>,
268 ) -> ControlFlow<BreakValue>,
269) -> Option<BreakValue> {
270 let mut stack = SmallVec::<[_; 1]>::new();
271 let result = attrs.try_for_each(|top_attr| {
272 let top_attr = match top_attr {
273 Either::Left(it) => it,
274 Either::Right(comment) => return callback(Either::Right(comment)),
275 };
276 if let Some((attr_name, tt)) = top_attr.as_simple_call()
277 && attr_name == "cfg_attr"
278 {
279 let mut tt_iter = TokenTreeChildren::new(&tt).peekable();
280 let cfg = cfg::CfgExpr::parse_from_ast(&mut tt_iter);
281 if cfg_options().check(&cfg) != Some(false) {
282 stack.push((tt_iter, tt));
283 while let Some((tt_iter, tt)) = stack.last_mut() {
284 let Some((attr, range)) = Meta::extract_possibly_unsafe(tt_iter, tt) else {
285 stack.pop();
286 continue;
287 };
288 if let Meta::TokenTree { path, tt: nested_tt } = &attr
289 && path.is1("cfg_attr")
290 {
291 let mut nested_tt_iter = TokenTreeChildren::new(nested_tt).peekable();
292 let cfg = cfg::CfgExpr::parse_from_ast(&mut nested_tt_iter);
293 if cfg_options().check(&cfg) != Some(false) {
294 stack.push((nested_tt_iter, nested_tt.clone()));
295 }
296 } else {
297 callback(Either::Left((attr, tt.syntax(), range, &top_attr)))?;
298 }
299 }
300 }
301 } else if let Some(ast_meta) = top_attr.meta()
302 && let Some(path) = ast_meta.path()
303 {
304 let path = AttrPath::extract(&path);
305 let meta = if let Some(tt) = ast_meta.token_tree() {
306 Meta::TokenTree { path, tt }
307 } else if let Some(value) = ast_meta.expr() {
308 let value =
309 if let ast::Expr::Literal(value) = value { Some(value.token()) } else { None };
310 let name =
311 if path.segments.len() == 1 { Some(path.segments[0].clone()) } else { None };
312 Meta::NamedKeyValue { name, value, path_range: path.range }
313 } else {
314 Meta::Path { path }
315 };
316 callback(Either::Left((
317 meta,
318 ast_meta.syntax(),
319 ast_meta.syntax().text_range(),
320 &top_attr,
321 )))?;
322 }
323 ControlFlow::Continue(())
324 });
325 result.break_value()
326}
327
328#[inline]
329pub(crate) fn is_item_tree_filtered_attr(name: &str) -> bool {
330 matches!(
331 name,
332 "doc"
333 | "stable"
334 | "unstable"
335 | "target_feature"
336 | "allow"
337 | "expect"
338 | "warn"
339 | "deny"
340 | "forbid"
341 | "repr"
342 | "inline"
343 | "track_caller"
344 | "must_use"
345 )
346}
347
348pub fn collect_item_tree_attrs<'a, BreakValue>(
351 owner: &dyn ast::HasAttrs,
352 cfg_options: impl Fn() -> &'a CfgOptions,
353 mut on_attr: impl FnMut(Meta, &SyntaxNode, &ast::Attr, TextRange) -> ControlFlow<BreakValue>,
354) -> Option<Either<BreakValue, CfgExpr>> {
355 let attrs = ast::attrs_including_inner(owner);
356 expand_cfg_attr(
357 attrs,
358 || cfg_options(),
359 |attr, container, range, top_attr| {
360 let filter = match &attr {
364 Meta::NamedKeyValue { name: Some(name), .. } => {
365 is_item_tree_filtered_attr(name.text())
366 }
367 Meta::TokenTree { path, tt } if path.segments.len() == 1 => {
368 let name = path.segments[0].text();
369 if name == "cfg" {
370 let cfg =
371 CfgExpr::parse_from_ast(&mut TokenTreeChildren::new(tt).peekable());
372 if cfg_options().check(&cfg) == Some(false) {
373 return ControlFlow::Break(Either::Right(cfg));
374 }
375 true
376 } else {
377 is_item_tree_filtered_attr(name)
378 }
379 }
380 Meta::Path { path } => {
381 path.segments.len() == 1 && is_item_tree_filtered_attr(path.segments[0].text())
382 }
383 _ => false,
384 };
385 if !filter && let ControlFlow::Break(v) = on_attr(attr, container, top_attr, range) {
386 return ControlFlow::Break(Either::Left(v));
387 }
388 ControlFlow::Continue(())
389 },
390 )
391}
392
393#[derive(Debug, Clone, PartialEq, Eq)]
394pub struct Attr {
395 pub path: Interned<ModPath>,
396 pub input: Option<Box<AttrInput>>,
397 pub ctxt: SyntaxContext,
398}
399
400#[derive(Debug, Clone, PartialEq, Eq, Hash)]
401pub enum AttrInput {
402 Literal(tt::Literal),
404 TokenTree(tt::TopSubtree),
406}
407
408impl fmt::Display for AttrInput {
409 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
410 match self {
411 AttrInput::Literal(lit) => write!(f, " = {lit}"),
412 AttrInput::TokenTree(tt) => tt.fmt(f),
413 }
414 }
415}
416
417impl Attr {
418 pub fn string_value(&self) -> Option<&Symbol> {
420 match self.input.as_deref()? {
421 AttrInput::Literal(tt::Literal {
422 symbol: text,
423 kind: tt::LitKind::Str | tt::LitKind::StrRaw(_),
424 ..
425 }) => Some(text),
426 _ => None,
427 }
428 }
429
430 pub fn string_value_with_span(&self) -> Option<(&Symbol, span::Span)> {
432 match self.input.as_deref()? {
433 AttrInput::Literal(tt::Literal {
434 symbol: text,
435 kind: tt::LitKind::Str | tt::LitKind::StrRaw(_),
436 span,
437 suffix: _,
438 }) => Some((text, *span)),
439 _ => None,
440 }
441 }
442
443 pub fn string_value_unescape(&self) -> Option<Cow<'_, str>> {
444 match self.input.as_deref()? {
445 AttrInput::Literal(tt::Literal {
446 symbol: text, kind: tt::LitKind::StrRaw(_), ..
447 }) => Some(Cow::Borrowed(text.as_str())),
448 AttrInput::Literal(tt::Literal { symbol: text, kind: tt::LitKind::Str, .. }) => {
449 unescape(text.as_str())
450 }
451 _ => None,
452 }
453 }
454
455 pub fn single_ident_value(&self) -> Option<&tt::Ident> {
457 match self.input.as_deref()? {
458 AttrInput::TokenTree(tt) => match tt.token_trees().flat_tokens() {
459 [tt::TokenTree::Leaf(tt::Leaf::Ident(ident))] => Some(ident),
460 _ => None,
461 },
462 _ => None,
463 }
464 }
465
466 pub fn token_tree_value(&self) -> Option<&TopSubtree> {
468 match self.input.as_deref()? {
469 AttrInput::TokenTree(tt) => Some(tt),
470 _ => None,
471 }
472 }
473
474 pub fn parse_path_comma_token_tree<'a>(
476 &'a self,
477 db: &'a dyn ExpandDatabase,
478 ) -> Option<impl Iterator<Item = (ModPath, Span, tt::TokenTreesView<'a>)> + 'a> {
479 let args = self.token_tree_value()?;
480
481 if args.top_subtree().delimiter.kind != DelimiterKind::Parenthesis {
482 return None;
483 }
484 Some(parse_path_comma_token_tree(db, args))
485 }
486}
487
488fn parse_path_comma_token_tree<'a>(
489 db: &'a dyn ExpandDatabase,
490 args: &'a tt::TopSubtree,
491) -> impl Iterator<Item = (ModPath, Span, tt::TokenTreesView<'a>)> {
492 args.token_trees()
493 .split(|tt| matches!(tt, tt::TtElement::Leaf(tt::Leaf::Punct(Punct { char: ',', .. }))))
494 .filter_map(move |tts| {
495 let span = tts.flat_tokens().first()?.first_span();
496 Some((ModPath::from_tt(db, tts)?, span, tts))
497 })
498}
499
500fn unescape(s: &str) -> Option<Cow<'_, str>> {
501 let mut buf = String::new();
502 let mut prev_end = 0;
503 let mut has_error = false;
504 unescape::unescape_str(s, |char_range, unescaped_char| {
505 match (unescaped_char, buf.capacity() == 0) {
506 (Ok(c), false) => buf.push(c),
507 (Ok(_), true) if char_range.len() == 1 && char_range.start == prev_end => {
508 prev_end = char_range.end
509 }
510 (Ok(c), true) => {
511 buf.reserve_exact(s.len());
512 buf.push_str(&s[..prev_end]);
513 buf.push(c);
514 }
515 (Err(_), _) => has_error = true,
516 }
517 });
518
519 match (has_error, buf.capacity() == 0) {
520 (true, _) => None,
521 (false, false) => Some(Cow::Owned(buf)),
522 (false, true) => Some(Cow::Borrowed(s)),
523 }
524}
525
526#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
531pub struct AttrId {
532 id: u32,
533}
534
535impl AttrId {
536 #[inline]
537 pub fn from_item_tree_index(id: u32) -> Self {
538 Self { id }
539 }
540
541 #[inline]
542 pub fn item_tree_index(self) -> u32 {
543 self.id
544 }
545
546 pub fn find_attr_range<N: ast::HasAttrs>(
550 self,
551 db: &dyn ExpandDatabase,
552 krate: Crate,
553 owner: AstId<N>,
554 ) -> (ast::Attr, SyntaxNode, TextRange, Meta) {
555 self.find_attr_range_with_source(db, krate, &owner.to_node(db))
556 }
557
558 pub fn find_attr_range_with_source(
562 self,
563 db: &dyn ExpandDatabase,
564 krate: Crate,
565 owner: &dyn ast::HasAttrs,
566 ) -> (ast::Attr, SyntaxNode, TextRange, Meta) {
567 let cfg_options = OnceCell::new();
568 let mut index = 0;
569 let result = collect_item_tree_attrs(
570 owner,
571 || cfg_options.get_or_init(|| krate.cfg_options(db)),
572 |meta, container, top_attr, range| {
573 if index == self.id {
574 return ControlFlow::Break((top_attr.clone(), container.clone(), range, meta));
575 }
576 index += 1;
577 ControlFlow::Continue(())
578 },
579 );
580 match result {
581 Some(Either::Left(it)) => it,
582 _ => {
583 panic!("used an incorrect `AttrId`; crate={krate:?}, attr_id={self:?}");
584 }
585 }
586 }
587
588 pub fn find_derive_range(
589 self,
590 db: &dyn ExpandDatabase,
591 krate: Crate,
592 owner: AstId<ast::Adt>,
593 derive_index: u32,
594 ) -> TextRange {
595 let (_, _, derive_attr_range, derive_attr) = self.find_attr_range(db, krate, owner);
596 let Meta::TokenTree { tt, .. } = derive_attr else {
597 return derive_attr_range;
598 };
599 let span_map = RealSpanMap::absolute(span::EditionedFileId::current_edition(
601 span::FileId::from_raw(0),
602 ));
603 let tt = syntax_bridge::syntax_node_to_token_tree(
604 tt.syntax(),
605 SpanMapRef::RealSpanMap(&span_map),
606 span_map.span_for_range(tt.syntax().text_range()),
607 DocCommentDesugarMode::ProcMacro,
608 );
609 let Some((_, _, derive_tts)) =
610 parse_path_comma_token_tree(db, &tt).nth(derive_index as usize)
611 else {
612 return derive_attr_range;
613 };
614 let (Some(first_tt), Some(last_tt)) =
615 (derive_tts.flat_tokens().first(), derive_tts.flat_tokens().last())
616 else {
617 return derive_attr_range;
618 };
619 let start = first_tt.first_span().range.start();
620 let end = match last_tt {
621 tt::TokenTree::Leaf(it) => it.span().range.end(),
622 tt::TokenTree::Subtree(it) => it.delimiter.close.range.end(),
623 };
624 TextRange::new(start, end)
625 }
626}