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. Some examples:
256    ///
257    /// - `#[rustfmt::skip]`: `rustfmt::skip` is a path
258    /// - `#[allow(clippy::complexity)]`: `clippy::complexity` is a path
259    /// - `#[inline]`: `inline` is a single segment path
260    pub fn path(&self) -> &PathParser<'a> {
261        &self.path
262    }
263
264    /// Gets just the args parser, without caring about the path.
265    pub fn args(&self) -> &ArgParser<'a> {
266        &self.args
267    }
268
269    /// Asserts that this MetaItem starts with a word, or single segment path.
270    ///
271    /// Some examples:
272    /// - `#[inline]`: `inline` is a word
273    /// - `#[rustfmt::skip]`: `rustfmt::skip` is a path,
274    ///   and not a word and should instead be parsed using [`path`](Self::path)
275    pub fn word_is(&self, sym: Symbol) -> Option<&ArgParser<'a>> {
276        self.path().word_is(sym).then(|| self.args())
277    }
278}
279
280#[derive(Clone)]
281pub struct NameValueParser {
282    pub eq_span: Span,
283    value: MetaItemLit,
284    pub value_span: Span,
285}
286
287impl Debug for NameValueParser {
288    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
289        f.debug_struct("NameValueParser")
290            .field("eq_span", &self.eq_span)
291            .field("value", &self.value)
292            .field("value_span", &self.value_span)
293            .finish()
294    }
295}
296
297impl NameValueParser {
298    pub fn value_as_lit(&self) -> &MetaItemLit {
299        &self.value
300    }
301
302    pub fn value_as_str(&self) -> Option<Symbol> {
303        self.value_as_lit().kind.str()
304    }
305}
306
307fn expr_to_lit(dcx: DiagCtxtHandle<'_>, expr: &Expr, span: Span) -> MetaItemLit {
308    // In valid code the value always ends up as a single literal. Otherwise, a dummy
309    // literal suffices because the error is handled elsewhere.
310    if let ExprKind::Lit(token_lit) = expr.kind
311        && let Ok(lit) = MetaItemLit::from_token_lit(token_lit, expr.span)
312    {
313        lit
314    } else {
315        let guar = dcx.span_delayed_bug(
316            span,
317            "expr in place where literal is expected (builtin attr parsing)",
318        );
319        MetaItemLit { symbol: sym::dummy, suffix: None, kind: LitKind::Err(guar), span }
320    }
321}
322
323struct MetaItemListParserContext<'a> {
324    // the tokens inside the delimiters, so `#[some::attr(a b c)]` would have `a b c` inside
325    inside_delimiters: Peekable<TokenStreamIter<'a>>,
326    dcx: DiagCtxtHandle<'a>,
327}
328
329impl<'a> MetaItemListParserContext<'a> {
330    fn done(&mut self) -> bool {
331        self.inside_delimiters.peek().is_none()
332    }
333
334    fn next_path(&mut self) -> Option<AttrPath> {
335        // FIXME: Share code with `parse_path`.
336        let tt = self.inside_delimiters.next().map(|tt| TokenTree::uninterpolate(tt));
337
338        match tt.as_deref()? {
339            &TokenTree::Token(
340                Token { kind: ref kind @ (token::Ident(..) | token::PathSep), span },
341                _,
342            ) => {
343                // here we have either an ident or pathsep `::`.
344
345                let mut segments = if let &token::Ident(name, _) = kind {
346                    // when we lookahead another pathsep, more path's coming
347                    if let Some(TokenTree::Token(Token { kind: token::PathSep, .. }, _)) =
348                        self.inside_delimiters.peek()
349                    {
350                        self.inside_delimiters.next();
351                        vec![Ident::new(name, span)]
352                    } else {
353                        // else we have a single identifier path, that's all
354                        return Some(AttrPath {
355                            segments: vec![Ident::new(name, span)].into_boxed_slice(),
356                            span,
357                        });
358                    }
359                } else {
360                    // if `::` is all we get, we just got a path root
361                    vec![Ident::new(kw::PathRoot, span)]
362                };
363
364                // one segment accepted. accept n more
365                loop {
366                    // another ident?
367                    if let Some(&TokenTree::Token(Token { kind: token::Ident(name, _), span }, _)) =
368                        self.inside_delimiters
369                            .next()
370                            .map(|tt| TokenTree::uninterpolate(tt))
371                            .as_deref()
372                    {
373                        segments.push(Ident::new(name, span));
374                    } else {
375                        return None;
376                    }
377                    // stop unless we see another `::`
378                    if let Some(TokenTree::Token(Token { kind: token::PathSep, .. }, _)) =
379                        self.inside_delimiters.peek()
380                    {
381                        self.inside_delimiters.next();
382                    } else {
383                        break;
384                    }
385                }
386                let span = span.with_hi(segments.last().unwrap().span.hi());
387                Some(AttrPath { segments: segments.into_boxed_slice(), span })
388            }
389            TokenTree::Token(Token { kind, .. }, _) if kind.is_delim() => None,
390            _ => {
391                // malformed attributes can get here. We can't crash, but somewhere else should've
392                // already warned for this.
393                None
394            }
395        }
396    }
397
398    fn value(&mut self) -> Option<MetaItemLit> {
399        match self.inside_delimiters.next() {
400            Some(TokenTree::Delimited(.., Delimiter::Invisible(_), inner_tokens)) => {
401                MetaItemListParserContext {
402                    inside_delimiters: inner_tokens.iter().peekable(),
403                    dcx: self.dcx,
404                }
405                .value()
406            }
407            Some(TokenTree::Token(token, _)) => MetaItemLit::from_token(token),
408            _ => None,
409        }
410    }
411
412    /// parses one element on the inside of a list attribute like `#[my_attr( <insides> )]`
413    ///
414    /// parses a path followed be either:
415    /// 1. nothing (a word attr)
416    /// 2. a parenthesized list
417    /// 3. an equals sign and a literal (name-value)
418    ///
419    /// Can also parse *just* a literal. This is for cases like as `#[my_attr("literal")]`
420    /// where no path is given before the literal
421    ///
422    /// Some exceptions too for interpolated attributes which are already pre-processed
423    fn next(&mut self) -> Option<MetaItemOrLitParser<'a>> {
424        // a list element is either a literal
425        if let Some(TokenTree::Token(token, _)) = self.inside_delimiters.peek()
426            && let Some(lit) = MetaItemLit::from_token(token)
427        {
428            self.inside_delimiters.next();
429            return Some(MetaItemOrLitParser::Lit(lit));
430        } else if let Some(TokenTree::Delimited(.., Delimiter::Invisible(_), inner_tokens)) =
431            self.inside_delimiters.peek()
432        {
433            self.inside_delimiters.next();
434            return MetaItemListParserContext {
435                inside_delimiters: inner_tokens.iter().peekable(),
436                dcx: self.dcx,
437            }
438            .next();
439        }
440
441        // or a path.
442        let path = self.next_path()?;
443
444        // Paths can be followed by:
445        // - `(more meta items)` (another list)
446        // - `= lit` (a name-value)
447        // - nothing
448        Some(MetaItemOrLitParser::MetaItemParser(match self.inside_delimiters.peek() {
449            Some(TokenTree::Delimited(dspan, _, Delimiter::Parenthesis, inner_tokens)) => {
450                self.inside_delimiters.next();
451
452                MetaItemParser {
453                    path: PathParser::Attr(path),
454                    args: ArgParser::List(MetaItemListParser::new_tts(
455                        inner_tokens.iter(),
456                        dspan.entire(),
457                        self.dcx,
458                    )),
459                }
460            }
461            Some(TokenTree::Delimited(_, ..)) => {
462                self.inside_delimiters.next();
463                // self.dcx.span_delayed_bug(span.entire(), "wrong delimiters");
464                return None;
465            }
466            Some(TokenTree::Token(Token { kind: token::Eq, span }, _)) => {
467                self.inside_delimiters.next();
468                let value = self.value()?;
469                MetaItemParser {
470                    path: PathParser::Attr(path),
471                    args: ArgParser::NameValue(NameValueParser {
472                        eq_span: *span,
473                        value_span: value.span,
474                        value,
475                    }),
476                }
477            }
478            _ => MetaItemParser { path: PathParser::Attr(path), args: ArgParser::NoArgs },
479        }))
480    }
481
482    fn parse(mut self, span: Span) -> MetaItemListParser<'a> {
483        let mut sub_parsers = Vec::new();
484
485        while !self.done() {
486            let Some(n) = self.next() else {
487                continue;
488            };
489            sub_parsers.push(n);
490
491            match self.inside_delimiters.peek() {
492                None | Some(TokenTree::Token(Token { kind: token::Comma, .. }, _)) => {
493                    self.inside_delimiters.next();
494                }
495                Some(_) => {}
496            }
497        }
498
499        MetaItemListParser { sub_parsers, span }
500    }
501}
502
503#[derive(Debug, Clone)]
504pub struct MetaItemListParser<'a> {
505    sub_parsers: Vec<MetaItemOrLitParser<'a>>,
506    pub span: Span,
507}
508
509impl<'a> MetaItemListParser<'a> {
510    fn new(delim: &'a DelimArgs, dcx: DiagCtxtHandle<'a>) -> MetaItemListParser<'a> {
511        MetaItemListParser::new_tts(delim.tokens.iter(), delim.dspan.entire(), dcx)
512    }
513
514    fn new_tts(tts: TokenStreamIter<'a>, span: Span, dcx: DiagCtxtHandle<'a>) -> Self {
515        MetaItemListParserContext { inside_delimiters: tts.peekable(), dcx }.parse(span)
516    }
517
518    /// Lets you pick and choose as what you want to parse each element in the list
519    pub fn mixed(&self) -> impl Iterator<Item = &MetaItemOrLitParser<'a>> {
520        self.sub_parsers.iter()
521    }
522
523    pub fn len(&self) -> usize {
524        self.sub_parsers.len()
525    }
526
527    pub fn is_empty(&self) -> bool {
528        self.len() == 0
529    }
530
531    /// Returns Some if the list contains only a single element.
532    ///
533    /// Inside the Some is the parser to parse this single element.
534    pub fn single(&self) -> Option<&MetaItemOrLitParser<'a>> {
535        let mut iter = self.mixed();
536        iter.next().filter(|_| iter.next().is_none())
537    }
538}