1use std::{borrow::Cow, cell::OnceCell, convert::Infallible, fmt, ops::ControlFlow};
18
19use ::tt::TextRange;
20use base_db::{Crate, SourceDatabase};
21use cfg::{CfgExpr, CfgOptions};
22use either::Either;
23use intern::Interned;
24use itertools::Itertools;
25use mbe::{DelimiterKind, Punct};
26use smallvec::SmallVec;
27use span::{RealSpanMap, Span, SyntaxContext};
28use syntax::{AstNode, SmolStr, ast, unescape};
29use syntax_bridge::DocCommentDesugarMode;
30
31use crate::{
32 AstId,
33 mod_path::ModPath,
34 span_map::SpanMap,
35 tt::{self, TopSubtree},
36};
37
38pub trait AstPathExt {
39 fn is1(&self, segment: &str) -> bool;
40
41 fn as_one_segment(&self) -> Option<SmolStr>;
42
43 fn as_up_to_two_segment(&self) -> Option<(SmolStr, Option<SmolStr>)>;
44}
45
46impl AstPathExt for ast::Path {
47 fn is1(&self, segment: &str) -> bool {
48 self.as_one_segment().is_some_and(|it| it == segment)
49 }
50
51 fn as_one_segment(&self) -> Option<SmolStr> {
52 Some(self.as_single_name_ref()?.text().into())
53 }
54
55 fn as_up_to_two_segment(&self) -> Option<(SmolStr, Option<SmolStr>)> {
56 let parent = self.qualifier().as_one_segment();
57 let this = self.segment()?.name_ref()?.text().into();
58 if let Some(parent) = parent { Some((parent, Some(this))) } else { Some((this, None)) }
59 }
60}
61
62impl AstPathExt for Option<ast::Path> {
63 fn is1(&self, segment: &str) -> bool {
64 self.as_ref().is_some_and(|it| it.is1(segment))
65 }
66
67 fn as_one_segment(&self) -> Option<SmolStr> {
68 self.as_ref().and_then(|it| it.as_one_segment())
69 }
70
71 fn as_up_to_two_segment(&self) -> Option<(SmolStr, Option<SmolStr>)> {
72 self.as_ref().and_then(|it| it.as_up_to_two_segment())
73 }
74}
75
76pub trait AstKeyValueMetaExt {
77 fn value_string(&self) -> Option<SmolStr>;
78}
79
80impl AstKeyValueMetaExt for ast::KeyValueMeta {
81 fn value_string(&self) -> Option<SmolStr> {
82 if let Some(ast::Expr::Literal(value)) = self.expr()
83 && let ast::LiteralKind::String(value) = value.kind()
84 && let Ok(value) = value.value()
85 {
86 Some((*value).into())
87 } else {
88 None
89 }
90 }
91}
92
93#[inline]
98pub fn expand_cfg_attr<'a, BreakValue>(
99 attrs: impl Iterator<Item = ast::Attr>,
100 cfg_options: impl FnMut() -> &'a CfgOptions,
101 mut callback: impl FnMut(ast::Meta, ast::Attr) -> ControlFlow<BreakValue>,
102) -> Option<BreakValue> {
103 expand_cfg_attr_with_doc_comments::<Infallible, _>(
104 attrs.map(Either::Left),
105 cfg_options,
106 move |Either::Left((meta, top_attr))| callback(meta, top_attr),
107 )
108}
109
110#[inline]
111pub fn expand_cfg_attr_with_doc_comments<'a, DocComment, BreakValue>(
112 mut attrs: impl Iterator<Item = Either<ast::Attr, DocComment>>,
113 mut cfg_options: impl FnMut() -> &'a CfgOptions,
114 mut callback: impl FnMut(Either<(ast::Meta, ast::Attr), DocComment>) -> ControlFlow<BreakValue>,
115) -> Option<BreakValue> {
116 let mut stack = SmallVec::<[_; 1]>::new();
117 loop {
118 let (mut meta, top_attr) = if let Some(it) = stack.pop() {
119 it
120 } else {
121 let attr = attrs.next()?;
122 match attr {
123 Either::Left(attr) => {
124 let Some(meta) = attr.meta() else { continue };
125 stack.push((meta, attr));
126 }
127 Either::Right(doc_comment) => {
128 if let ControlFlow::Break(break_value) = callback(Either::Right(doc_comment)) {
129 return Some(break_value);
130 }
131 }
132 }
133 continue;
134 };
135
136 while let ast::Meta::UnsafeMeta(unsafe_meta) = &meta {
137 let Some(inner) = unsafe_meta.meta() else { continue };
138 meta = inner;
139 }
140
141 if let ast::Meta::CfgAttrMeta(meta) = meta {
142 let Some(cfg_predicate) = meta.cfg_predicate() else { continue };
143 let cfg_predicate = CfgExpr::parse_from_ast(cfg_predicate);
144 if cfg_options().check(&cfg_predicate) != Some(false) {
145 let prev_stack_len = stack.len();
146 stack.extend(meta.metas().map(|meta| (meta, top_attr.clone())));
147 stack[prev_stack_len..].reverse();
148 }
149 } else {
150 if let ControlFlow::Break(break_value) = callback(Either::Left((meta, top_attr))) {
151 return Some(break_value);
152 }
153 }
154 }
155}
156
157#[inline]
158pub(crate) fn is_item_tree_filtered_attr(name: &str) -> bool {
159 matches!(
160 name,
161 "doc"
162 | "stable"
163 | "unstable"
164 | "target_feature"
165 | "allow"
166 | "expect"
167 | "warn"
168 | "deny"
169 | "forbid"
170 | "repr"
171 | "inline"
172 | "track_caller"
173 | "must_use"
174 )
175}
176
177pub fn collect_item_tree_attrs<'a, BreakValue>(
180 owner: &dyn ast::HasAttrs,
181 cfg_options: impl Fn() -> &'a CfgOptions,
182 mut on_attr: impl FnMut(ast::Meta, ast::Attr) -> ControlFlow<BreakValue>,
183) -> Option<Either<BreakValue, CfgExpr>> {
184 let attrs = ast::attrs_including_inner(owner);
185 expand_cfg_attr(
186 attrs,
187 || cfg_options(),
188 |attr, top_attr| {
189 let filter = match &attr {
193 ast::Meta::CfgMeta(attr) => {
194 let Some(cfg_predicate) = attr.cfg_predicate() else {
195 return ControlFlow::Continue(());
196 };
197 let cfg = CfgExpr::parse_from_ast(cfg_predicate);
198 if cfg_options().check(&cfg) == Some(false) {
199 return ControlFlow::Break(Either::Right(cfg));
200 }
201 true
202 }
203 _ => attr
204 .path()
205 .and_then(|path| path.as_one_segment())
206 .is_some_and(|segment| is_item_tree_filtered_attr(&segment)),
207 };
208 if !filter && let ControlFlow::Break(v) = on_attr(attr, top_attr) {
209 return ControlFlow::Break(Either::Left(v));
210 }
211 ControlFlow::Continue(())
212 },
213 )
214}
215
216#[derive(Debug, Clone, PartialEq, Eq)]
217pub struct Attr {
218 pub path: Interned<ModPath>,
219 pub input: Option<Box<AttrInput>>,
220 pub ctxt: SyntaxContext,
221}
222
223#[derive(Debug, Clone, PartialEq, Eq, Hash)]
224pub enum AttrInput {
225 Literal(tt::Literal),
227 TokenTree(tt::TopSubtree),
229}
230
231impl fmt::Display for AttrInput {
232 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233 match self {
234 AttrInput::Literal(lit) => write!(f, " = {lit}"),
235 AttrInput::TokenTree(tt) => tt.fmt(f),
236 }
237 }
238}
239
240impl Attr {
241 pub fn string_value(&self) -> Option<&str> {
243 match self.input.as_deref()? {
244 AttrInput::Literal(
245 lit @ tt::Literal { kind: tt::LitKind::Str | tt::LitKind::StrRaw(_), .. },
246 ) => Some(lit.text()),
247 _ => None,
248 }
249 }
250
251 pub fn string_value_with_span(&self) -> Option<(&str, span::Span)> {
253 match self.input.as_deref()? {
254 AttrInput::Literal(
255 lit @ tt::Literal { kind: tt::LitKind::Str | tt::LitKind::StrRaw(_), span, .. },
256 ) => Some((lit.text(), *span)),
257 _ => None,
258 }
259 }
260
261 pub fn string_value_unescape(&self) -> Option<Cow<'_, str>> {
262 match self.input.as_deref()? {
263 AttrInput::Literal(lit @ tt::Literal { kind: tt::LitKind::StrRaw(_), .. }) => {
264 Some(Cow::Borrowed(lit.text()))
265 }
266 AttrInput::Literal(lit @ tt::Literal { kind: tt::LitKind::Str, .. }) => {
267 unescape(lit.text())
268 }
269 _ => None,
270 }
271 }
272
273 pub fn single_ident_value(&self) -> Option<tt::Ident> {
275 match self.input.as_deref()? {
276 AttrInput::TokenTree(tt) => match tt.token_trees().iter().collect_array() {
277 Some([tt::TtElement::Leaf(tt::Leaf::Ident(ident))]) => Some(ident),
278 _ => None,
279 },
280 _ => None,
281 }
282 }
283
284 pub fn token_tree_value(&self) -> Option<&TopSubtree> {
286 match self.input.as_deref()? {
287 AttrInput::TokenTree(tt) => Some(tt),
288 _ => None,
289 }
290 }
291
292 pub fn parse_path_comma_token_tree<'a>(
294 &'a self,
295 db: &'a dyn SourceDatabase,
296 ) -> Option<impl Iterator<Item = (ModPath, Span, tt::TokenTreesView<'a>)> + 'a> {
297 let args = self.token_tree_value()?;
298
299 if args.top_subtree().delimiter.kind != DelimiterKind::Parenthesis {
300 return None;
301 }
302 Some(parse_path_comma_token_tree(db, args))
303 }
304}
305
306fn parse_path_comma_token_tree<'a>(
307 db: &'a dyn SourceDatabase,
308 args: &'a tt::TopSubtree,
309) -> impl Iterator<Item = (ModPath, Span, tt::TokenTreesView<'a>)> {
310 args.token_trees()
311 .split(|tt| matches!(tt, tt::TtElement::Leaf(tt::Leaf::Punct(Punct { char: ',', .. }))))
312 .filter_map(move |tts| {
313 let span = tts.first_span()?;
314 Some((ModPath::from_tt(db, tts)?, span, tts))
315 })
316}
317
318fn unescape(s: &str) -> Option<Cow<'_, str>> {
319 let mut buf = String::new();
320 let mut prev_end = 0;
321 let mut has_error = false;
322 unescape::unescape_str(s, |char_range, unescaped_char| {
323 match (unescaped_char, buf.capacity() == 0) {
324 (Ok(c), false) => buf.push(c),
325 (Ok(_), true) if char_range.len() == 1 && char_range.start == prev_end => {
326 prev_end = char_range.end
327 }
328 (Ok(c), true) => {
329 buf.reserve_exact(s.len());
330 buf.push_str(&s[..prev_end]);
331 buf.push(c);
332 }
333 (Err(_), _) => has_error = true,
334 }
335 });
336
337 match (has_error, buf.capacity() == 0) {
338 (true, _) => None,
339 (false, false) => Some(Cow::Owned(buf)),
340 (false, true) => Some(Cow::Borrowed(s)),
341 }
342}
343
344#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
349pub struct AttrId {
350 id: u32,
351}
352
353impl AttrId {
354 #[inline]
355 pub fn from_item_tree_index(id: u32) -> Self {
356 Self { id }
357 }
358
359 #[inline]
360 pub fn item_tree_index(self) -> u32 {
361 self.id
362 }
363
364 pub fn find_attr_range<N: ast::HasAttrs>(
367 self,
368 db: &dyn SourceDatabase,
369 krate: Crate,
370 owner: AstId<N>,
371 ) -> (ast::Attr, ast::Meta) {
372 self.find_attr_range_with_source(db, krate, &owner.to_node(db))
373 }
374
375 pub fn find_attr_range_with_source(
381 self,
382 db: &dyn SourceDatabase,
383 krate: Crate,
384 owner: &dyn ast::HasAttrs,
385 ) -> (ast::Attr, ast::Meta) {
386 self.find_attr_range_with_source_opt(db, krate, owner).unwrap_or_else(|| {
387 panic!("used an incorrect `AttrId`; crate={krate:?}, attr_id={self:?}");
388 })
389 }
390
391 pub(crate) fn find_attr_range_with_source_opt(
394 self,
395 db: &dyn SourceDatabase,
396 krate: Crate,
397 owner: &dyn ast::HasAttrs,
398 ) -> Option<(ast::Attr, ast::Meta)> {
399 let cfg_options = OnceCell::new();
400 let mut index = 0;
401 let result = collect_item_tree_attrs(
402 owner,
403 || cfg_options.get_or_init(|| krate.cfg_options(db)),
404 |meta, top_attr| {
405 if index == self.id {
406 return ControlFlow::Break((top_attr, meta));
407 }
408 index += 1;
409 ControlFlow::Continue(())
410 },
411 );
412 match result {
413 Some(Either::Left(it)) => Some(it),
414 _ => None,
415 }
416 }
417
418 pub fn find_derive_range(
419 self,
420 db: &dyn SourceDatabase,
421 krate: Crate,
422 owner: AstId<ast::Adt>,
423 derive_index: u32,
424 ) -> TextRange {
425 let (_, derive_attr) = self.find_attr_range(db, krate, owner);
426 let ast::Meta::TokenTreeMeta(derive_attr) = derive_attr else {
427 return derive_attr.syntax().text_range();
428 };
429 let Some(tt) = derive_attr.token_tree() else {
430 return derive_attr.syntax().text_range();
431 };
432 let span_map = RealSpanMap::absolute(span::EditionedFileId::current_edition(
434 span::FileId::from_raw(0),
435 ));
436 let tt = syntax_bridge::syntax_node_to_token_tree(
437 tt.syntax(),
438 SpanMap::RealSpanMap(&span_map),
439 span_map.span_for_range(tt.syntax().text_range()),
440 DocCommentDesugarMode::ProcMacro,
441 );
442 let Some((_, _, derive_tts)) =
443 parse_path_comma_token_tree(db, &tt).nth(derive_index as usize)
444 else {
445 return derive_attr.syntax().text_range();
446 };
447 let (Some(first_span), Some(last_span)) = (derive_tts.first_span(), derive_tts.last_span())
448 else {
449 return derive_attr.syntax().text_range();
450 };
451 let start = first_span.range.start();
452 let end = last_span.range.end();
453 TextRange::new(start, end)
454 }
455}