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::borrow::Cow;
7use std::fmt::{Debug, Display};
8
9use rustc_ast::token::{self, Delimiter, MetaVarKind};
10use rustc_ast::tokenstream::TokenStream;
11use rustc_ast::{AttrArgs, Expr, ExprKind, LitKind, MetaItemLit, NormalAttr, Path};
12use rustc_ast_pretty::pprust;
13use rustc_errors::{Diag, PResult};
14use rustc_hir::{self as hir, AttrPath};
15use rustc_parse::exp;
16use rustc_parse::parser::{Parser, PathStyle, token_descr};
17use rustc_session::errors::{create_lit_error, report_lit_error};
18use rustc_session::parse::ParseSess;
19use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol, sym};
20use thin_vec::ThinVec;
21
22use crate::ShouldEmit;
23use crate::session_diagnostics::{
24    InvalidMetaItem, InvalidMetaItemQuoteIdentSugg, InvalidMetaItemRemoveNegSugg, MetaBadDelim,
25    MetaBadDelimSugg, SuffixedLiteralInAttribute,
26};
27
28#[derive(Clone, Debug)]
29pub struct PathParser<'a>(pub Cow<'a, Path>);
30
31impl<'a> PathParser<'a> {
32    pub fn get_attribute_path(&self) -> hir::AttrPath {
33        AttrPath {
34            segments: self.segments().copied().collect::<Vec<_>>().into_boxed_slice(),
35            span: self.span(),
36        }
37    }
38
39    pub fn segments(&'a self) -> impl Iterator<Item = &'a Ident> {
40        self.0.segments.iter().map(|seg| &seg.ident)
41    }
42
43    pub fn span(&self) -> Span {
44        self.0.span
45    }
46
47    pub fn len(&self) -> usize {
48        self.0.segments.len()
49    }
50
51    pub fn segments_is(&self, segments: &[Symbol]) -> bool {
52        self.segments().map(|segment| &segment.name).eq(segments)
53    }
54
55    pub fn word(&self) -> Option<Ident> {
56        (self.len() == 1).then(|| **self.segments().next().as_ref().unwrap())
57    }
58
59    pub fn word_sym(&self) -> Option<Symbol> {
60        self.word().map(|ident| ident.name)
61    }
62
63    /// Asserts that this MetaItem is some specific word.
64    ///
65    /// See [`word`](Self::word) for examples of what a word is.
66    pub fn word_is(&self, sym: Symbol) -> bool {
67        self.word().map(|i| i.name == sym).unwrap_or(false)
68    }
69
70    /// Checks whether the first segments match the givens.
71    ///
72    /// Unlike [`segments_is`](Self::segments_is),
73    /// `self` may contain more segments than the number matched  against.
74    pub fn starts_with(&self, segments: &[Symbol]) -> bool {
75        segments.len() < self.len() && self.segments().zip(segments).all(|(a, b)| a.name == *b)
76    }
77}
78
79impl Display for PathParser<'_> {
80    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81        write!(f, "{}", pprust::path_to_string(&self.0))
82    }
83}
84
85#[derive(Clone, Debug)]
86#[must_use]
87pub enum ArgParser<'a> {
88    NoArgs,
89    List(MetaItemListParser<'a>),
90    NameValue(NameValueParser),
91}
92
93impl<'a> ArgParser<'a> {
94    pub fn span(&self) -> Option<Span> {
95        match self {
96            Self::NoArgs => None,
97            Self::List(l) => Some(l.span),
98            Self::NameValue(n) => Some(n.value_span.with_lo(n.eq_span.lo())),
99        }
100    }
101
102    pub fn from_attr_args<'sess>(
103        value: &'a AttrArgs,
104        parts: &[Symbol],
105        psess: &'sess ParseSess,
106        should_emit: ShouldEmit,
107    ) -> Option<Self> {
108        Some(match value {
109            AttrArgs::Empty => Self::NoArgs,
110            AttrArgs::Delimited(args) => {
111                // The arguments of rustc_dummy are not validated if the arguments are delimited
112                if parts == &[sym::rustc_dummy] {
113                    return Some(ArgParser::List(MetaItemListParser {
114                        sub_parsers: ThinVec::new(),
115                        span: args.dspan.entire(),
116                    }));
117                }
118
119                if args.delim != Delimiter::Parenthesis {
120                    psess.dcx().emit_err(MetaBadDelim {
121                        span: args.dspan.entire(),
122                        sugg: MetaBadDelimSugg { open: args.dspan.open, close: args.dspan.close },
123                    });
124                    return None;
125                }
126
127                Self::List(
128                    MetaItemListParser::new(&args.tokens, args.dspan.entire(), psess, should_emit)
129                        .map_err(|e| should_emit.emit_err(e))
130                        .ok()?,
131                )
132            }
133            AttrArgs::Eq { eq_span, expr } => Self::NameValue(NameValueParser {
134                eq_span: *eq_span,
135                value: expr_to_lit(psess, &expr, expr.span, should_emit)?,
136                value_span: expr.span,
137            }),
138        })
139    }
140
141    /// Asserts that this MetaItem is a list
142    ///
143    /// Some examples:
144    ///
145    /// - `#[allow(clippy::complexity)]`: `(clippy::complexity)` is a list
146    /// - `#[rustfmt::skip::macros(target_macro_name)]`: `(target_macro_name)` is a list
147    pub fn list(&self) -> Option<&MetaItemListParser<'a>> {
148        match self {
149            Self::List(l) => Some(l),
150            Self::NameValue(_) | Self::NoArgs => None,
151        }
152    }
153
154    /// Asserts that this MetaItem is a name-value pair.
155    ///
156    /// Some examples:
157    ///
158    /// - `#[clippy::cyclomatic_complexity = "100"]`: `clippy::cyclomatic_complexity = "100"` is a name value pair,
159    ///   where the name is a path (`clippy::cyclomatic_complexity`). You already checked the path
160    ///   to get an `ArgParser`, so this method will effectively only assert that the `= "100"` is
161    ///   there
162    /// - `#[doc = "hello"]`: `doc = "hello`  is also a name value pair
163    pub fn name_value(&self) -> Option<&NameValueParser> {
164        match self {
165            Self::NameValue(n) => Some(n),
166            Self::List(_) | Self::NoArgs => None,
167        }
168    }
169
170    /// Assert that there were no args.
171    /// If there were, get a span to the arguments
172    /// (to pass to [`AcceptContext::expected_no_args`](crate::context::AcceptContext::expected_no_args)).
173    pub fn no_args(&self) -> Result<(), Span> {
174        match self {
175            Self::NoArgs => Ok(()),
176            Self::List(args) => Err(args.span),
177            Self::NameValue(args) => Err(args.eq_span.to(args.value_span)),
178        }
179    }
180}
181
182/// Inside lists, values could be either literals, or more deeply nested meta items.
183/// This enum represents that.
184///
185/// Choose which one you want using the provided methods.
186#[derive(Debug, Clone)]
187pub enum MetaItemOrLitParser<'a> {
188    MetaItemParser(MetaItemParser<'a>),
189    Lit(MetaItemLit),
190    Err(Span, ErrorGuaranteed),
191}
192
193impl<'sess> MetaItemOrLitParser<'sess> {
194    pub fn parse_single(
195        parser: &mut Parser<'sess>,
196        should_emit: ShouldEmit,
197    ) -> PResult<'sess, MetaItemOrLitParser<'static>> {
198        let mut this = MetaItemListParserContext { parser, should_emit };
199        this.parse_meta_item_inner()
200    }
201
202    pub fn span(&self) -> Span {
203        match self {
204            MetaItemOrLitParser::MetaItemParser(generic_meta_item_parser) => {
205                generic_meta_item_parser.span()
206            }
207            MetaItemOrLitParser::Lit(meta_item_lit) => meta_item_lit.span,
208            MetaItemOrLitParser::Err(span, _) => *span,
209        }
210    }
211
212    pub fn lit(&self) -> Option<&MetaItemLit> {
213        match self {
214            MetaItemOrLitParser::Lit(meta_item_lit) => Some(meta_item_lit),
215            _ => None,
216        }
217    }
218
219    pub fn meta_item(&self) -> Option<&MetaItemParser<'sess>> {
220        match self {
221            MetaItemOrLitParser::MetaItemParser(parser) => Some(parser),
222            _ => None,
223        }
224    }
225}
226
227/// Utility that deconstructs a MetaItem into usable parts.
228///
229/// MetaItems are syntactically extremely flexible, but specific attributes want to parse
230/// them in custom, more restricted ways. This can be done using this struct.
231///
232/// MetaItems consist of some path, and some args. The args could be empty. In other words:
233///
234/// - `name` -> args are empty
235/// - `name(...)` -> args are a [`list`](ArgParser::list), which is the bit between the parentheses
236/// - `name = value`-> arg is [`name_value`](ArgParser::name_value), where the argument is the
237///   `= value` part
238///
239/// The syntax of MetaItems can be found at <https://doc.rust-lang.org/reference/attributes.html>
240#[derive(Clone)]
241pub struct MetaItemParser<'a> {
242    path: PathParser<'a>,
243    args: ArgParser<'a>,
244}
245
246impl<'a> Debug for MetaItemParser<'a> {
247    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
248        f.debug_struct("MetaItemParser")
249            .field("path", &self.path)
250            .field("args", &self.args)
251            .finish()
252    }
253}
254
255impl<'a> MetaItemParser<'a> {
256    /// Create a new parser from a [`NormalAttr`], which is stored inside of any
257    /// [`ast::Attribute`](rustc_ast::Attribute)
258    pub fn from_attr<'sess>(
259        attr: &'a NormalAttr,
260        parts: &[Symbol],
261        psess: &'sess ParseSess,
262        should_emit: ShouldEmit,
263    ) -> Option<Self> {
264        Some(Self {
265            path: PathParser(Cow::Borrowed(&attr.item.path)),
266            args: ArgParser::from_attr_args(&attr.item.args, parts, psess, should_emit)?,
267        })
268    }
269}
270
271impl<'a> MetaItemParser<'a> {
272    pub fn span(&self) -> Span {
273        if let Some(other) = self.args.span() {
274            self.path.span().with_hi(other.hi())
275        } else {
276            self.path.span()
277        }
278    }
279
280    /// Gets just the path, without the args. Some examples:
281    ///
282    /// - `#[rustfmt::skip]`: `rustfmt::skip` is a path
283    /// - `#[allow(clippy::complexity)]`: `clippy::complexity` is a path
284    /// - `#[inline]`: `inline` is a single segment path
285    pub fn path(&self) -> &PathParser<'a> {
286        &self.path
287    }
288
289    /// Gets just the args parser, without caring about the path.
290    pub fn args(&self) -> &ArgParser<'a> {
291        &self.args
292    }
293
294    /// Asserts that this MetaItem starts with a word, or single segment path.
295    ///
296    /// Some examples:
297    /// - `#[inline]`: `inline` is a word
298    /// - `#[rustfmt::skip]`: `rustfmt::skip` is a path,
299    ///   and not a word and should instead be parsed using [`path`](Self::path)
300    pub fn word_is(&self, sym: Symbol) -> Option<&ArgParser<'a>> {
301        self.path().word_is(sym).then(|| self.args())
302    }
303}
304
305#[derive(Clone)]
306pub struct NameValueParser {
307    pub eq_span: Span,
308    value: MetaItemLit,
309    pub value_span: Span,
310}
311
312impl Debug for NameValueParser {
313    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
314        f.debug_struct("NameValueParser")
315            .field("eq_span", &self.eq_span)
316            .field("value", &self.value)
317            .field("value_span", &self.value_span)
318            .finish()
319    }
320}
321
322impl NameValueParser {
323    pub fn value_as_lit(&self) -> &MetaItemLit {
324        &self.value
325    }
326
327    pub fn value_as_str(&self) -> Option<Symbol> {
328        self.value_as_lit().kind.str()
329    }
330}
331
332fn expr_to_lit(
333    psess: &ParseSess,
334    expr: &Expr,
335    span: Span,
336    should_emit: ShouldEmit,
337) -> Option<MetaItemLit> {
338    if let ExprKind::Lit(token_lit) = expr.kind {
339        let res = MetaItemLit::from_token_lit(token_lit, expr.span);
340        match res {
341            Ok(lit) => {
342                if token_lit.suffix.is_some() {
343                    should_emit.emit_err(
344                        psess.dcx().create_err(SuffixedLiteralInAttribute { span: lit.span }),
345                    );
346                    None
347                } else {
348                    if !lit.kind.is_unsuffixed() {
349                        // Emit error and continue, we can still parse the attribute as if the suffix isn't there
350                        should_emit.emit_err(
351                            psess.dcx().create_err(SuffixedLiteralInAttribute { span: lit.span }),
352                        );
353                    }
354
355                    Some(lit)
356                }
357            }
358            Err(err) => {
359                let guar = report_lit_error(psess, err, token_lit, expr.span);
360                let lit = MetaItemLit {
361                    symbol: token_lit.symbol,
362                    suffix: token_lit.suffix,
363                    kind: LitKind::Err(guar),
364                    span: expr.span,
365                };
366                Some(lit)
367            }
368        }
369    } else {
370        if matches!(should_emit, ShouldEmit::Nothing) {
371            return None;
372        }
373
374        // Example cases:
375        // - `#[foo = 1+1]`: results in `ast::ExprKind::BinOp`.
376        // - `#[foo = include_str!("nonexistent-file.rs")]`:
377        //   results in `ast::ExprKind::Err`. In that case we delay
378        //   the error because an earlier error will have already
379        //   been reported.
380        let msg = "attribute value must be a literal";
381        let err = psess.dcx().struct_span_err(span, msg);
382        should_emit.emit_err(err);
383        None
384    }
385}
386
387struct MetaItemListParserContext<'a, 'sess> {
388    parser: &'a mut Parser<'sess>,
389    should_emit: ShouldEmit,
390}
391
392impl<'a, 'sess> MetaItemListParserContext<'a, 'sess> {
393    fn parse_unsuffixed_meta_item_lit(&mut self) -> PResult<'sess, MetaItemLit> {
394        let Some(token_lit) = self.parser.eat_token_lit() else { return Err(self.expected_lit()) };
395        self.unsuffixed_meta_item_from_lit(token_lit)
396    }
397
398    fn unsuffixed_meta_item_from_lit(
399        &mut self,
400        token_lit: token::Lit,
401    ) -> PResult<'sess, MetaItemLit> {
402        let lit = match MetaItemLit::from_token_lit(token_lit, self.parser.prev_token.span) {
403            Ok(lit) => lit,
404            Err(err) => {
405                return Err(create_lit_error(
406                    &self.parser.psess,
407                    err,
408                    token_lit,
409                    self.parser.prev_token_uninterpolated_span(),
410                ));
411            }
412        };
413
414        if !lit.kind.is_unsuffixed() {
415            // Emit error and continue, we can still parse the attribute as if the suffix isn't there
416            self.should_emit.emit_err(
417                self.parser.dcx().create_err(SuffixedLiteralInAttribute { span: lit.span }),
418            );
419        }
420
421        Ok(lit)
422    }
423
424    fn parse_attr_item(&mut self) -> PResult<'sess, MetaItemParser<'static>> {
425        if let Some(MetaVarKind::Meta { has_meta_form }) = self.parser.token.is_metavar_seq() {
426            return if has_meta_form {
427                let attr_item = self
428                    .parser
429                    .eat_metavar_seq(MetaVarKind::Meta { has_meta_form: true }, |this| {
430                        MetaItemListParserContext { parser: this, should_emit: self.should_emit }
431                            .parse_attr_item()
432                    })
433                    .unwrap();
434                Ok(attr_item)
435            } else {
436                self.parser.unexpected_any()
437            };
438        }
439
440        let path = self.parser.parse_path(PathStyle::Mod)?;
441
442        // Check style of arguments that this meta item has
443        let args = if self.parser.check(exp!(OpenParen)) {
444            let start = self.parser.token.span;
445            let (sub_parsers, _) = self.parser.parse_paren_comma_seq(|parser| {
446                MetaItemListParserContext { parser, should_emit: self.should_emit }
447                    .parse_meta_item_inner()
448            })?;
449            let end = self.parser.prev_token.span;
450            ArgParser::List(MetaItemListParser { sub_parsers, span: start.with_hi(end.hi()) })
451        } else if self.parser.eat(exp!(Eq)) {
452            let eq_span = self.parser.prev_token.span;
453            let value = self.parse_unsuffixed_meta_item_lit()?;
454
455            ArgParser::NameValue(NameValueParser { eq_span, value, value_span: value.span })
456        } else {
457            ArgParser::NoArgs
458        };
459
460        Ok(MetaItemParser { path: PathParser(Cow::Owned(path)), args })
461    }
462
463    fn parse_meta_item_inner(&mut self) -> PResult<'sess, MetaItemOrLitParser<'static>> {
464        if let Some(token_lit) = self.parser.eat_token_lit() {
465            // If a literal token is parsed, we commit to parsing a MetaItemLit for better errors
466            Ok(MetaItemOrLitParser::Lit(self.unsuffixed_meta_item_from_lit(token_lit)?))
467        } else {
468            let prev_pros = self.parser.approx_token_stream_pos();
469            match self.parse_attr_item() {
470                Ok(item) => Ok(MetaItemOrLitParser::MetaItemParser(item)),
471                Err(err) => {
472                    // If `parse_attr_item` made any progress, it likely has a more precise error we should prefer
473                    // If it didn't make progress we use the `expected_lit` from below
474                    if self.parser.approx_token_stream_pos() != prev_pros {
475                        Err(err)
476                    } else {
477                        err.cancel();
478                        Err(self.expected_lit())
479                    }
480                }
481            }
482        }
483    }
484
485    fn expected_lit(&mut self) -> Diag<'sess> {
486        let mut err = InvalidMetaItem {
487            span: self.parser.token.span,
488            descr: token_descr(&self.parser.token),
489            quote_ident_sugg: None,
490            remove_neg_sugg: None,
491        };
492
493        // Suggest quoting idents, e.g. in `#[cfg(key = value)]`. We don't use `Token::ident` and
494        // don't `uninterpolate` the token to avoid suggesting anything butchered or questionable
495        // when macro metavariables are involved.
496        if self.parser.prev_token == token::Eq
497            && let token::Ident(..) = self.parser.token.kind
498        {
499            let before = self.parser.token.span.shrink_to_lo();
500            while let token::Ident(..) = self.parser.token.kind {
501                self.parser.bump();
502            }
503            err.quote_ident_sugg = Some(InvalidMetaItemQuoteIdentSugg {
504                before,
505                after: self.parser.prev_token.span.shrink_to_hi(),
506            });
507        }
508
509        if self.parser.token == token::Minus
510            && self
511                .parser
512                .look_ahead(1, |t| matches!(t.kind, rustc_ast::token::TokenKind::Literal { .. }))
513        {
514            err.remove_neg_sugg =
515                Some(InvalidMetaItemRemoveNegSugg { negative_sign: self.parser.token.span });
516            self.parser.bump();
517            self.parser.bump();
518        }
519
520        self.parser.dcx().create_err(err)
521    }
522
523    fn parse(
524        tokens: TokenStream,
525        psess: &'sess ParseSess,
526        span: Span,
527        should_emit: ShouldEmit,
528    ) -> PResult<'sess, MetaItemListParser<'static>> {
529        let mut parser = Parser::new(psess, tokens, None);
530        let mut this = MetaItemListParserContext { parser: &mut parser, should_emit };
531
532        // Presumably, the majority of the time there will only be one attr.
533        let mut sub_parsers = ThinVec::with_capacity(1);
534        while this.parser.token != token::Eof {
535            sub_parsers.push(this.parse_meta_item_inner()?);
536
537            if !this.parser.eat(exp!(Comma)) {
538                break;
539            }
540        }
541
542        if parser.token != token::Eof {
543            parser.unexpected()?;
544        }
545
546        Ok(MetaItemListParser { sub_parsers, span })
547    }
548}
549
550#[derive(Debug, Clone)]
551pub struct MetaItemListParser<'a> {
552    sub_parsers: ThinVec<MetaItemOrLitParser<'a>>,
553    pub span: Span,
554}
555
556impl<'a> MetaItemListParser<'a> {
557    pub(crate) fn new<'sess>(
558        tokens: &'a TokenStream,
559        span: Span,
560        psess: &'sess ParseSess,
561        should_emit: ShouldEmit,
562    ) -> Result<Self, Diag<'sess>> {
563        MetaItemListParserContext::parse(tokens.clone(), psess, span, should_emit)
564    }
565
566    /// Lets you pick and choose as what you want to parse each element in the list
567    pub fn mixed(&self) -> impl Iterator<Item = &MetaItemOrLitParser<'a>> {
568        self.sub_parsers.iter()
569    }
570
571    pub fn len(&self) -> usize {
572        self.sub_parsers.len()
573    }
574
575    pub fn is_empty(&self) -> bool {
576        self.len() == 0
577    }
578
579    /// Returns Some if the list contains only a single element.
580    ///
581    /// Inside the Some is the parser to parse this single element.
582    pub fn single(&self) -> Option<&MetaItemOrLitParser<'a>> {
583        let mut iter = self.mixed();
584        iter.next().filter(|_| iter.next().is_none())
585    }
586}