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