rustc_attr_parsing/
parser.rs

1//! This is in essence an (improved) duplicate of `rustc_ast/attr/mod.rs`.
2//! That module is intended to be deleted in its entirety.
3//!
4//! FIXME(jdonszelmann): delete `rustc_ast/attr/mod.rs`
5
6use std::fmt::{Debug, Display};
7use std::iter::Peekable;
8
9use rustc_ast::token::{self, Delimiter, Token};
10use rustc_ast::tokenstream::{TokenStreamIter, TokenTree};
11use rustc_ast::{AttrArgs, DelimArgs, Expr, ExprKind, LitKind, MetaItemLit, NormalAttr, Path};
12use rustc_ast_pretty::pprust;
13use rustc_errors::DiagCtxtHandle;
14use rustc_hir::{self as hir, AttrPath};
15use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol, kw, sym};
16
17pub struct SegmentIterator<'a> {
18    offset: usize,
19    path: &'a PathParser<'a>,
20}
21
22impl<'a> Iterator for SegmentIterator<'a> {
23    type Item = &'a Ident;
24
25    fn next(&mut self) -> Option<Self::Item> {
26        if self.offset >= self.path.len() {
27            return None;
28        }
29
30        let res = match self.path {
31            PathParser::Ast(ast_path) => &ast_path.segments[self.offset].ident,
32            PathParser::Attr(attr_path) => &attr_path.segments[self.offset],
33        };
34
35        self.offset += 1;
36        Some(res)
37    }
38}
39
40#[derive(Clone, Debug)]
41pub enum PathParser<'a> {
42    Ast(&'a Path),
43    Attr(AttrPath),
44}
45
46impl<'a> PathParser<'a> {
47    pub fn get_attribute_path(&self) -> hir::AttrPath {
48        AttrPath {
49            segments: self.segments().copied().collect::<Vec<_>>().into_boxed_slice(),
50            span: self.span(),
51        }
52    }
53
54    pub fn segments(&'a self) -> impl Iterator<Item = &'a Ident> {
55        SegmentIterator { offset: 0, path: self }
56    }
57
58    pub fn span(&self) -> Span {
59        match self {
60            PathParser::Ast(path) => path.span,
61            PathParser::Attr(attr_path) => attr_path.span,
62        }
63    }
64
65    pub fn len(&self) -> usize {
66        match self {
67            PathParser::Ast(path) => path.segments.len(),
68            PathParser::Attr(attr_path) => attr_path.segments.len(),
69        }
70    }
71
72    pub fn segments_is(&self, segments: &[Symbol]) -> bool {
73        self.len() == segments.len() && self.segments().zip(segments).all(|(a, b)| a.name == *b)
74    }
75
76    pub fn word(&self) -> Option<Ident> {
77        (self.len() == 1).then(|| **self.segments().next().as_ref().unwrap())
78    }
79
80    pub fn word_sym(&self) -> Option<Symbol> {
81        self.word().map(|ident| ident.name)
82    }
83
84    /// Asserts that this MetaItem is some specific word.
85    ///
86    /// See [`word`](Self::word) for examples of what a word is.
87    pub fn word_is(&self, sym: Symbol) -> bool {
88        self.word().map(|i| i.name == sym).unwrap_or(false)
89    }
90
91    /// Checks whether the first segments match the givens.
92    ///
93    /// Unlike [`segments_is`](Self::segments_is),
94    /// `self` may contain more segments than the number matched  against.
95    pub fn starts_with(&self, segments: &[Symbol]) -> bool {
96        segments.len() < self.len() && self.segments().zip(segments).all(|(a, b)| a.name == *b)
97    }
98}
99
100impl Display for PathParser<'_> {
101    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102        match self {
103            PathParser::Ast(path) => write!(f, "{}", pprust::path_to_string(path)),
104            PathParser::Attr(attr_path) => write!(f, "{attr_path}"),
105        }
106    }
107}
108
109#[derive(Clone, Debug)]
110#[must_use]
111pub enum ArgParser<'a> {
112    NoArgs,
113    List(MetaItemListParser<'a>),
114    NameValue(NameValueParser),
115}
116
117impl<'a> ArgParser<'a> {
118    pub fn span(&self) -> Option<Span> {
119        match self {
120            Self::NoArgs => None,
121            Self::List(l) => Some(l.span),
122            Self::NameValue(n) => Some(n.value_span.with_lo(n.eq_span.lo())),
123        }
124    }
125
126    pub fn from_attr_args<'sess>(value: &'a AttrArgs, dcx: DiagCtxtHandle<'sess>) -> Self {
127        match value {
128            AttrArgs::Empty => Self::NoArgs,
129            AttrArgs::Delimited(args) if args.delim == Delimiter::Parenthesis => {
130                Self::List(MetaItemListParser::new(args, dcx))
131            }
132            AttrArgs::Delimited(args) => {
133                Self::List(MetaItemListParser { sub_parsers: vec![], span: args.dspan.entire() })
134            }
135            AttrArgs::Eq { eq_span, expr } => Self::NameValue(NameValueParser {
136                eq_span: *eq_span,
137                value: expr_to_lit(dcx, &expr, *eq_span),
138                value_span: expr.span,
139            }),
140        }
141    }
142
143    /// Asserts that this MetaItem is a list
144    ///
145    /// Some examples:
146    ///
147    /// - `#[allow(clippy::complexity)]`: `(clippy::complexity)` is a list
148    /// - `#[rustfmt::skip::macros(target_macro_name)]`: `(target_macro_name)` is a list
149    pub fn list(&self) -> Option<&MetaItemListParser<'a>> {
150        match self {
151            Self::List(l) => Some(l),
152            Self::NameValue(_) | Self::NoArgs => None,
153        }
154    }
155
156    /// Asserts that this MetaItem is a name-value pair.
157    ///
158    /// Some examples:
159    ///
160    /// - `#[clippy::cyclomatic_complexity = "100"]`: `clippy::cyclomatic_complexity = "100"` is a name value pair,
161    ///   where the name is a path (`clippy::cyclomatic_complexity`). You already checked the path
162    ///   to get an `ArgParser`, so this method will effectively only assert that the `= "100"` is
163    ///   there
164    /// - `#[doc = "hello"]`: `doc = "hello`  is also a name value pair
165    pub fn name_value(&self) -> Option<&NameValueParser> {
166        match self {
167            Self::NameValue(n) => Some(n),
168            Self::List(_) | Self::NoArgs => None,
169        }
170    }
171
172    /// Assert that there were no args.
173    /// If there were, get a span to the arguments
174    /// (to pass to [`AcceptContext::expected_no_args`](crate::context::AcceptContext::expected_no_args)).
175    pub fn no_args(&self) -> Result<(), Span> {
176        match self {
177            Self::NoArgs => Ok(()),
178            Self::List(args) => Err(args.span),
179            Self::NameValue(args) => Err(args.eq_span.to(args.value_span)),
180        }
181    }
182}
183
184/// Inside lists, values could be either literals, or more deeply nested meta items.
185/// This enum represents that.
186///
187/// Choose which one you want using the provided methods.
188#[derive(Debug, Clone)]
189pub enum MetaItemOrLitParser<'a> {
190    MetaItemParser(MetaItemParser<'a>),
191    Lit(MetaItemLit),
192    Err(Span, ErrorGuaranteed),
193}
194
195impl<'a> MetaItemOrLitParser<'a> {
196    pub fn span(&self) -> Span {
197        match self {
198            MetaItemOrLitParser::MetaItemParser(generic_meta_item_parser) => {
199                generic_meta_item_parser.span()
200            }
201            MetaItemOrLitParser::Lit(meta_item_lit) => meta_item_lit.span,
202            MetaItemOrLitParser::Err(span, _) => *span,
203        }
204    }
205
206    pub fn lit(&self) -> Option<&MetaItemLit> {
207        match self {
208            MetaItemOrLitParser::Lit(meta_item_lit) => Some(meta_item_lit),
209            _ => None,
210        }
211    }
212
213    pub fn meta_item(&self) -> Option<&MetaItemParser<'a>> {
214        match self {
215            MetaItemOrLitParser::MetaItemParser(parser) => Some(parser),
216            _ => None,
217        }
218    }
219}
220
221/// Utility that deconstructs a MetaItem into usable parts.
222///
223/// MetaItems are syntactically extremely flexible, but specific attributes want to parse
224/// them in custom, more restricted ways. This can be done using this struct.
225///
226/// MetaItems consist of some path, and some args. The args could be empty. In other words:
227///
228/// - `name` -> args are empty
229/// - `name(...)` -> args are a [`list`](ArgParser::list), which is the bit between the parentheses
230/// - `name = value`-> arg is [`name_value`](ArgParser::name_value), where the argument is the
231///   `= value` part
232///
233/// The syntax of MetaItems can be found at <https://doc.rust-lang.org/reference/attributes.html>
234#[derive(Clone)]
235pub struct MetaItemParser<'a> {
236    path: PathParser<'a>,
237    args: ArgParser<'a>,
238}
239
240impl<'a> Debug for MetaItemParser<'a> {
241    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
242        f.debug_struct("MetaItemParser")
243            .field("path", &self.path)
244            .field("args", &self.args)
245            .finish()
246    }
247}
248
249impl<'a> MetaItemParser<'a> {
250    /// Create a new parser from a [`NormalAttr`], which is stored inside of any
251    /// [`ast::Attribute`](rustc_ast::Attribute)
252    pub fn from_attr<'sess>(attr: &'a NormalAttr, dcx: DiagCtxtHandle<'sess>) -> Self {
253        Self {
254            path: PathParser::Ast(&attr.item.path),
255            args: ArgParser::from_attr_args(&attr.item.args, dcx),
256        }
257    }
258}
259
260impl<'a> MetaItemParser<'a> {
261    pub fn span(&self) -> Span {
262        if let Some(other) = self.args.span() {
263            self.path.span().with_hi(other.hi())
264        } else {
265            self.path.span()
266        }
267    }
268
269    /// Gets just the path, without the args. Some examples:
270    ///
271    /// - `#[rustfmt::skip]`: `rustfmt::skip` is a path
272    /// - `#[allow(clippy::complexity)]`: `clippy::complexity` is a path
273    /// - `#[inline]`: `inline` is a single segment path
274    pub fn path(&self) -> &PathParser<'a> {
275        &self.path
276    }
277
278    /// Gets just the args parser, without caring about the path.
279    pub fn args(&self) -> &ArgParser<'a> {
280        &self.args
281    }
282
283    /// Asserts that this MetaItem starts with a word, or single segment path.
284    ///
285    /// Some examples:
286    /// - `#[inline]`: `inline` is a word
287    /// - `#[rustfmt::skip]`: `rustfmt::skip` is a path,
288    ///   and not a word and should instead be parsed using [`path`](Self::path)
289    pub fn word_is(&self, sym: Symbol) -> Option<&ArgParser<'a>> {
290        self.path().word_is(sym).then(|| self.args())
291    }
292}
293
294#[derive(Clone)]
295pub struct NameValueParser {
296    pub eq_span: Span,
297    value: MetaItemLit,
298    pub value_span: Span,
299}
300
301impl Debug for NameValueParser {
302    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
303        f.debug_struct("NameValueParser")
304            .field("eq_span", &self.eq_span)
305            .field("value", &self.value)
306            .field("value_span", &self.value_span)
307            .finish()
308    }
309}
310
311impl NameValueParser {
312    pub fn value_as_lit(&self) -> &MetaItemLit {
313        &self.value
314    }
315
316    pub fn value_as_str(&self) -> Option<Symbol> {
317        self.value_as_lit().kind.str()
318    }
319}
320
321fn expr_to_lit(dcx: DiagCtxtHandle<'_>, expr: &Expr, span: Span) -> MetaItemLit {
322    // In valid code the value always ends up as a single literal. Otherwise, a dummy
323    // literal suffices because the error is handled elsewhere.
324    if let ExprKind::Lit(token_lit) = expr.kind
325        && let Ok(lit) = MetaItemLit::from_token_lit(token_lit, expr.span)
326    {
327        lit
328    } else {
329        let guar = dcx.span_delayed_bug(
330            span,
331            "expr in place where literal is expected (builtin attr parsing)",
332        );
333        MetaItemLit { symbol: sym::dummy, suffix: None, kind: LitKind::Err(guar), span }
334    }
335}
336
337struct MetaItemListParserContext<'a, 'sess> {
338    // the tokens inside the delimiters, so `#[some::attr(a b c)]` would have `a b c` inside
339    inside_delimiters: Peekable<TokenStreamIter<'a>>,
340    dcx: DiagCtxtHandle<'sess>,
341}
342
343impl<'a, 'sess> MetaItemListParserContext<'a, 'sess> {
344    fn done(&mut self) -> bool {
345        self.inside_delimiters.peek().is_none()
346    }
347
348    fn next_path(&mut self) -> Option<AttrPath> {
349        // FIXME: Share code with `parse_path`.
350        let tt = self.inside_delimiters.next().map(|tt| TokenTree::uninterpolate(tt));
351
352        match tt.as_deref()? {
353            &TokenTree::Token(
354                Token { kind: ref kind @ (token::Ident(..) | token::PathSep), span },
355                _,
356            ) => {
357                // here we have either an ident or pathsep `::`.
358
359                let mut segments = if let &token::Ident(name, _) = kind {
360                    // when we lookahead another pathsep, more path's coming
361                    if let Some(TokenTree::Token(Token { kind: token::PathSep, .. }, _)) =
362                        self.inside_delimiters.peek()
363                    {
364                        self.inside_delimiters.next();
365                        vec![Ident::new(name, span)]
366                    } else {
367                        // else we have a single identifier path, that's all
368                        return Some(AttrPath {
369                            segments: vec![Ident::new(name, span)].into_boxed_slice(),
370                            span,
371                        });
372                    }
373                } else {
374                    // if `::` is all we get, we just got a path root
375                    vec![Ident::new(kw::PathRoot, span)]
376                };
377
378                // one segment accepted. accept n more
379                loop {
380                    // another ident?
381                    if let Some(&TokenTree::Token(Token { kind: token::Ident(name, _), span }, _)) =
382                        self.inside_delimiters
383                            .next()
384                            .map(|tt| TokenTree::uninterpolate(tt))
385                            .as_deref()
386                    {
387                        segments.push(Ident::new(name, span));
388                    } else {
389                        return None;
390                    }
391                    // stop unless we see another `::`
392                    if let Some(TokenTree::Token(Token { kind: token::PathSep, .. }, _)) =
393                        self.inside_delimiters.peek()
394                    {
395                        self.inside_delimiters.next();
396                    } else {
397                        break;
398                    }
399                }
400                let span = span.with_hi(segments.last().unwrap().span.hi());
401                Some(AttrPath { segments: segments.into_boxed_slice(), span })
402            }
403            TokenTree::Token(Token { kind, .. }, _) if kind.is_delim() => None,
404            _ => {
405                // malformed attributes can get here. We can't crash, but somewhere else should've
406                // already warned for this.
407                None
408            }
409        }
410    }
411
412    fn value(&mut self) -> Option<MetaItemLit> {
413        match self.inside_delimiters.next() {
414            Some(TokenTree::Delimited(.., Delimiter::Invisible(_), inner_tokens)) => {
415                MetaItemListParserContext {
416                    inside_delimiters: inner_tokens.iter().peekable(),
417                    dcx: self.dcx,
418                }
419                .value()
420            }
421            Some(TokenTree::Token(token, _)) => MetaItemLit::from_token(token),
422            _ => None,
423        }
424    }
425
426    /// parses one element on the inside of a list attribute like `#[my_attr( <insides> )]`
427    ///
428    /// parses a path followed be either:
429    /// 1. nothing (a word attr)
430    /// 2. a parenthesized list
431    /// 3. an equals sign and a literal (name-value)
432    ///
433    /// Can also parse *just* a literal. This is for cases like as `#[my_attr("literal")]`
434    /// where no path is given before the literal
435    ///
436    /// Some exceptions too for interpolated attributes which are already pre-processed
437    fn next(&mut self) -> Option<MetaItemOrLitParser<'a>> {
438        // a list element is either a literal
439        if let Some(TokenTree::Token(token, _)) = self.inside_delimiters.peek()
440            && let Some(lit) = MetaItemLit::from_token(token)
441        {
442            self.inside_delimiters.next();
443            return Some(MetaItemOrLitParser::Lit(lit));
444        } else if let Some(TokenTree::Delimited(.., Delimiter::Invisible(_), inner_tokens)) =
445            self.inside_delimiters.peek()
446        {
447            self.inside_delimiters.next();
448            return MetaItemListParserContext {
449                inside_delimiters: inner_tokens.iter().peekable(),
450                dcx: self.dcx,
451            }
452            .next();
453        }
454
455        // or a path.
456        let path = self.next_path()?;
457
458        // Paths can be followed by:
459        // - `(more meta items)` (another list)
460        // - `= lit` (a name-value)
461        // - nothing
462        Some(MetaItemOrLitParser::MetaItemParser(match self.inside_delimiters.peek() {
463            Some(TokenTree::Delimited(dspan, _, Delimiter::Parenthesis, inner_tokens)) => {
464                self.inside_delimiters.next();
465
466                MetaItemParser {
467                    path: PathParser::Attr(path),
468                    args: ArgParser::List(MetaItemListParser::new_tts(
469                        inner_tokens.iter(),
470                        dspan.entire(),
471                        self.dcx,
472                    )),
473                }
474            }
475            Some(TokenTree::Delimited(_, ..)) => {
476                self.inside_delimiters.next();
477                // self.dcx.span_delayed_bug(span.entire(), "wrong delimiters");
478                return None;
479            }
480            Some(TokenTree::Token(Token { kind: token::Eq, span }, _)) => {
481                self.inside_delimiters.next();
482                let value = self.value()?;
483                MetaItemParser {
484                    path: PathParser::Attr(path),
485                    args: ArgParser::NameValue(NameValueParser {
486                        eq_span: *span,
487                        value_span: value.span,
488                        value,
489                    }),
490                }
491            }
492            _ => MetaItemParser { path: PathParser::Attr(path), args: ArgParser::NoArgs },
493        }))
494    }
495
496    fn parse(mut self, span: Span) -> MetaItemListParser<'a> {
497        let mut sub_parsers = Vec::new();
498
499        while !self.done() {
500            let Some(n) = self.next() else {
501                continue;
502            };
503            sub_parsers.push(n);
504
505            match self.inside_delimiters.peek() {
506                None | Some(TokenTree::Token(Token { kind: token::Comma, .. }, _)) => {
507                    self.inside_delimiters.next();
508                }
509                Some(_) => {}
510            }
511        }
512
513        MetaItemListParser { sub_parsers, span }
514    }
515}
516
517#[derive(Debug, Clone)]
518pub struct MetaItemListParser<'a> {
519    sub_parsers: Vec<MetaItemOrLitParser<'a>>,
520    pub span: Span,
521}
522
523impl<'a> MetaItemListParser<'a> {
524    fn new<'sess>(delim: &'a DelimArgs, dcx: DiagCtxtHandle<'sess>) -> Self {
525        MetaItemListParser::new_tts(delim.tokens.iter(), delim.dspan.entire(), dcx)
526    }
527
528    fn new_tts<'sess>(tts: TokenStreamIter<'a>, span: Span, dcx: DiagCtxtHandle<'sess>) -> Self {
529        MetaItemListParserContext { inside_delimiters: tts.peekable(), dcx }.parse(span)
530    }
531
532    /// Lets you pick and choose as what you want to parse each element in the list
533    pub fn mixed(&self) -> impl Iterator<Item = &MetaItemOrLitParser<'a>> {
534        self.sub_parsers.iter()
535    }
536
537    pub fn len(&self) -> usize {
538        self.sub_parsers.len()
539    }
540
541    pub fn is_empty(&self) -> bool {
542        self.len() == 0
543    }
544
545    /// Returns Some if the list contains only a single element.
546    ///
547    /// Inside the Some is the parser to parse this single element.
548    pub fn single(&self) -> Option<&MetaItemOrLitParser<'a>> {
549        let mut iter = self.mixed();
550        iter.next().filter(|_| iter.next().is_none())
551    }
552}