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