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, DelimArgs, Expr, ExprKind, LitKind, MetaItemLit, NormalAttr, Path};
12use rustc_ast_pretty::pprust;
13use rustc_errors::PResult;
14use rustc_hir::{self as hir, AttrPath};
15use rustc_parse::exp;
16use rustc_parse::parser::{Parser, PathStyle, token_descr};
17use rustc_session::errors::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.len() == segments.len() && self.segments().zip(segments).all(|(a, b)| a.name == *b)
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(MetaItemListParser::new(args, psess, should_emit)?)
128            }
129            AttrArgs::Eq { eq_span, expr } => Self::NameValue(NameValueParser {
130                eq_span: *eq_span,
131                value: expr_to_lit(psess, &expr, expr.span, should_emit)?,
132                value_span: expr.span,
133            }),
134        })
135    }
136
137    /// Asserts that this MetaItem is a list
138    ///
139    /// Some examples:
140    ///
141    /// - `#[allow(clippy::complexity)]`: `(clippy::complexity)` is a list
142    /// - `#[rustfmt::skip::macros(target_macro_name)]`: `(target_macro_name)` is a list
143    pub fn list(&self) -> Option<&MetaItemListParser<'a>> {
144        match self {
145            Self::List(l) => Some(l),
146            Self::NameValue(_) | Self::NoArgs => None,
147        }
148    }
149
150    /// Asserts that this MetaItem is a name-value pair.
151    ///
152    /// Some examples:
153    ///
154    /// - `#[clippy::cyclomatic_complexity = "100"]`: `clippy::cyclomatic_complexity = "100"` is a name value pair,
155    ///   where the name is a path (`clippy::cyclomatic_complexity`). You already checked the path
156    ///   to get an `ArgParser`, so this method will effectively only assert that the `= "100"` is
157    ///   there
158    /// - `#[doc = "hello"]`: `doc = "hello`  is also a name value pair
159    pub fn name_value(&self) -> Option<&NameValueParser> {
160        match self {
161            Self::NameValue(n) => Some(n),
162            Self::List(_) | Self::NoArgs => None,
163        }
164    }
165
166    /// Assert that there were no args.
167    /// If there were, get a span to the arguments
168    /// (to pass to [`AcceptContext::expected_no_args`](crate::context::AcceptContext::expected_no_args)).
169    pub fn no_args(&self) -> Result<(), Span> {
170        match self {
171            Self::NoArgs => Ok(()),
172            Self::List(args) => Err(args.span),
173            Self::NameValue(args) => Err(args.eq_span.to(args.value_span)),
174        }
175    }
176}
177
178/// Inside lists, values could be either literals, or more deeply nested meta items.
179/// This enum represents that.
180///
181/// Choose which one you want using the provided methods.
182#[derive(Debug, Clone)]
183pub enum MetaItemOrLitParser<'a> {
184    MetaItemParser(MetaItemParser<'a>),
185    Lit(MetaItemLit),
186    Err(Span, ErrorGuaranteed),
187}
188
189impl<'a> MetaItemOrLitParser<'a> {
190    pub fn span(&self) -> Span {
191        match self {
192            MetaItemOrLitParser::MetaItemParser(generic_meta_item_parser) => {
193                generic_meta_item_parser.span()
194            }
195            MetaItemOrLitParser::Lit(meta_item_lit) => meta_item_lit.span,
196            MetaItemOrLitParser::Err(span, _) => *span,
197        }
198    }
199
200    pub fn lit(&self) -> Option<&MetaItemLit> {
201        match self {
202            MetaItemOrLitParser::Lit(meta_item_lit) => Some(meta_item_lit),
203            _ => None,
204        }
205    }
206
207    pub fn meta_item(&self) -> Option<&MetaItemParser<'a>> {
208        match self {
209            MetaItemOrLitParser::MetaItemParser(parser) => Some(parser),
210            _ => None,
211        }
212    }
213}
214
215/// Utility that deconstructs a MetaItem into usable parts.
216///
217/// MetaItems are syntactically extremely flexible, but specific attributes want to parse
218/// them in custom, more restricted ways. This can be done using this struct.
219///
220/// MetaItems consist of some path, and some args. The args could be empty. In other words:
221///
222/// - `name` -> args are empty
223/// - `name(...)` -> args are a [`list`](ArgParser::list), which is the bit between the parentheses
224/// - `name = value`-> arg is [`name_value`](ArgParser::name_value), where the argument is the
225///   `= value` part
226///
227/// The syntax of MetaItems can be found at <https://doc.rust-lang.org/reference/attributes.html>
228#[derive(Clone)]
229pub struct MetaItemParser<'a> {
230    path: PathParser<'a>,
231    args: ArgParser<'a>,
232}
233
234impl<'a> Debug for MetaItemParser<'a> {
235    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
236        f.debug_struct("MetaItemParser")
237            .field("path", &self.path)
238            .field("args", &self.args)
239            .finish()
240    }
241}
242
243impl<'a> MetaItemParser<'a> {
244    /// Create a new parser from a [`NormalAttr`], which is stored inside of any
245    /// [`ast::Attribute`](rustc_ast::Attribute)
246    pub fn from_attr<'sess>(
247        attr: &'a NormalAttr,
248        parts: &[Symbol],
249        psess: &'sess ParseSess,
250        should_emit: ShouldEmit,
251    ) -> Option<Self> {
252        Some(Self {
253            path: PathParser(Cow::Borrowed(&attr.item.path)),
254            args: ArgParser::from_attr_args(&attr.item.args, parts, psess, should_emit)?,
255        })
256    }
257}
258
259impl<'a> MetaItemParser<'a> {
260    pub fn span(&self) -> Span {
261        if let Some(other) = self.args.span() {
262            self.path.span().with_hi(other.hi())
263        } else {
264            self.path.span()
265        }
266    }
267
268    /// Gets just the path, without the args. Some examples:
269    ///
270    /// - `#[rustfmt::skip]`: `rustfmt::skip` is a path
271    /// - `#[allow(clippy::complexity)]`: `clippy::complexity` is a path
272    /// - `#[inline]`: `inline` is a single segment path
273    pub fn path(&self) -> &PathParser<'a> {
274        &self.path
275    }
276
277    /// Gets just the args parser, without caring about the path.
278    pub fn args(&self) -> &ArgParser<'a> {
279        &self.args
280    }
281
282    /// Asserts that this MetaItem starts with a word, or single segment path.
283    ///
284    /// Some examples:
285    /// - `#[inline]`: `inline` is a word
286    /// - `#[rustfmt::skip]`: `rustfmt::skip` is a path,
287    ///   and not a word and should instead be parsed using [`path`](Self::path)
288    pub fn word_is(&self, sym: Symbol) -> Option<&ArgParser<'a>> {
289        self.path().word_is(sym).then(|| self.args())
290    }
291}
292
293#[derive(Clone)]
294pub struct NameValueParser {
295    pub eq_span: Span,
296    value: MetaItemLit,
297    pub value_span: Span,
298}
299
300impl Debug for NameValueParser {
301    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
302        f.debug_struct("NameValueParser")
303            .field("eq_span", &self.eq_span)
304            .field("value", &self.value)
305            .field("value_span", &self.value_span)
306            .finish()
307    }
308}
309
310impl NameValueParser {
311    pub fn value_as_lit(&self) -> &MetaItemLit {
312        &self.value
313    }
314
315    pub fn value_as_str(&self) -> Option<Symbol> {
316        self.value_as_lit().kind.str()
317    }
318}
319
320fn expr_to_lit(
321    psess: &ParseSess,
322    expr: &Expr,
323    span: Span,
324    should_emit: ShouldEmit,
325) -> Option<MetaItemLit> {
326    if let ExprKind::Lit(token_lit) = expr.kind {
327        let res = MetaItemLit::from_token_lit(token_lit, expr.span);
328        match res {
329            Ok(lit) => {
330                if token_lit.suffix.is_some() {
331                    psess
332                        .dcx()
333                        .create_err(SuffixedLiteralInAttribute { span: lit.span })
334                        .emit_unless_delay(!should_emit.should_emit());
335                    None
336                } else {
337                    if should_emit.should_emit() && !lit.kind.is_unsuffixed() {
338                        // Emit error and continue, we can still parse the attribute as if the suffix isn't there
339                        psess.dcx().emit_err(SuffixedLiteralInAttribute { span: lit.span });
340                    }
341
342                    Some(lit)
343                }
344            }
345            Err(err) => {
346                let guar = report_lit_error(psess, err, token_lit, expr.span);
347                let lit = MetaItemLit {
348                    symbol: token_lit.symbol,
349                    suffix: token_lit.suffix,
350                    kind: LitKind::Err(guar),
351                    span: expr.span,
352                };
353                Some(lit)
354            }
355        }
356    } else {
357        // Example cases:
358        // - `#[foo = 1+1]`: results in `ast::ExprKind::BinOp`.
359        // - `#[foo = include_str!("nonexistent-file.rs")]`:
360        //   results in `ast::ExprKind::Err`. In that case we delay
361        //   the error because an earlier error will have already
362        //   been reported.
363        let msg = "attribute value must be a literal";
364        let mut err = psess.dcx().struct_span_err(span, msg);
365        if let ExprKind::Err(_) = expr.kind {
366            err.downgrade_to_delayed_bug();
367        }
368
369        err.emit_unless_delay(!should_emit.should_emit());
370        None
371    }
372}
373
374struct MetaItemListParserContext<'a, 'sess> {
375    parser: &'a mut Parser<'sess>,
376    should_emit: ShouldEmit,
377}
378
379impl<'a, 'sess> MetaItemListParserContext<'a, 'sess> {
380    fn parse_unsuffixed_meta_item_lit(&mut self) -> PResult<'sess, MetaItemLit> {
381        let uninterpolated_span = self.parser.token_uninterpolated_span();
382        let Some(token_lit) = self.parser.eat_token_lit() else {
383            return self.parser.handle_missing_lit(Parser::mk_meta_item_lit_char);
384        };
385
386        let lit = match MetaItemLit::from_token_lit(token_lit, self.parser.prev_token.span) {
387            Ok(lit) => lit,
388            Err(err) => {
389                let guar =
390                    report_lit_error(&self.parser.psess, err, token_lit, uninterpolated_span);
391                // Pack possible quotes and prefixes from the original literal into
392                // the error literal's symbol so they can be pretty-printed faithfully.
393                let suffixless_lit = token::Lit::new(token_lit.kind, token_lit.symbol, None);
394                let symbol = Symbol::intern(&suffixless_lit.to_string());
395                let token_lit = token::Lit::new(token::Err(guar), symbol, token_lit.suffix);
396                MetaItemLit::from_token_lit(token_lit, uninterpolated_span).unwrap()
397            }
398        };
399
400        if self.should_emit.should_emit() && !lit.kind.is_unsuffixed() {
401            // Emit error and continue, we can still parse the attribute as if the suffix isn't there
402            self.parser.dcx().emit_err(SuffixedLiteralInAttribute { span: lit.span });
403        }
404
405        Ok(lit)
406    }
407
408    fn parse_attr_item(&mut self) -> PResult<'sess, MetaItemParser<'static>> {
409        if let Some(MetaVarKind::Meta { has_meta_form }) = self.parser.token.is_metavar_seq() {
410            return if has_meta_form {
411                let attr_item = self
412                    .parser
413                    .eat_metavar_seq(MetaVarKind::Meta { has_meta_form: true }, |this| {
414                        MetaItemListParserContext { parser: this, should_emit: self.should_emit }
415                            .parse_attr_item()
416                    })
417                    .unwrap();
418                Ok(attr_item)
419            } else {
420                self.parser.unexpected_any()
421            };
422        }
423
424        let path = self.parser.parse_path(PathStyle::Mod)?;
425
426        // Check style of arguments that this meta item has
427        let args = if self.parser.check(exp!(OpenParen)) {
428            let start = self.parser.token.span;
429            let (sub_parsers, _) = self.parser.parse_paren_comma_seq(|parser| {
430                MetaItemListParserContext { parser, should_emit: self.should_emit }
431                    .parse_meta_item_inner()
432            })?;
433            let end = self.parser.prev_token.span;
434            ArgParser::List(MetaItemListParser { sub_parsers, span: start.with_hi(end.hi()) })
435        } else if self.parser.eat(exp!(Eq)) {
436            let eq_span = self.parser.prev_token.span;
437            let value = self.parse_unsuffixed_meta_item_lit()?;
438
439            ArgParser::NameValue(NameValueParser { eq_span, value, value_span: value.span })
440        } else {
441            ArgParser::NoArgs
442        };
443
444        Ok(MetaItemParser { path: PathParser(Cow::Owned(path)), args })
445    }
446
447    fn parse_meta_item_inner(&mut self) -> PResult<'sess, MetaItemOrLitParser<'static>> {
448        match self.parse_unsuffixed_meta_item_lit() {
449            Ok(lit) => return Ok(MetaItemOrLitParser::Lit(lit)),
450            Err(err) => err.cancel(), // we provide a better error below
451        }
452
453        match self.parse_attr_item() {
454            Ok(mi) => return Ok(MetaItemOrLitParser::MetaItemParser(mi)),
455            Err(err) => err.cancel(), // we provide a better error below
456        }
457
458        let mut err = InvalidMetaItem {
459            span: self.parser.token.span,
460            descr: token_descr(&self.parser.token),
461            quote_ident_sugg: None,
462            remove_neg_sugg: None,
463        };
464
465        // Suggest quoting idents, e.g. in `#[cfg(key = value)]`. We don't use `Token::ident` and
466        // don't `uninterpolate` the token to avoid suggesting anything butchered or questionable
467        // when macro metavariables are involved.
468        if self.parser.prev_token == token::Eq
469            && let token::Ident(..) = self.parser.token.kind
470        {
471            let before = self.parser.token.span.shrink_to_lo();
472            while let token::Ident(..) = self.parser.token.kind {
473                self.parser.bump();
474            }
475            err.quote_ident_sugg = Some(InvalidMetaItemQuoteIdentSugg {
476                before,
477                after: self.parser.prev_token.span.shrink_to_hi(),
478            });
479        }
480
481        if self.parser.token == token::Minus
482            && self
483                .parser
484                .look_ahead(1, |t| matches!(t.kind, rustc_ast::token::TokenKind::Literal { .. }))
485        {
486            err.remove_neg_sugg =
487                Some(InvalidMetaItemRemoveNegSugg { negative_sign: self.parser.token.span });
488            self.parser.bump();
489            self.parser.bump();
490        }
491
492        Err(self.parser.dcx().create_err(err))
493    }
494
495    fn parse(
496        tokens: TokenStream,
497        psess: &'sess ParseSess,
498        span: Span,
499        should_emit: ShouldEmit,
500    ) -> PResult<'sess, MetaItemListParser<'static>> {
501        let mut parser = Parser::new(psess, tokens, None);
502        let mut this = MetaItemListParserContext { parser: &mut parser, should_emit };
503
504        // Presumably, the majority of the time there will only be one attr.
505        let mut sub_parsers = ThinVec::with_capacity(1);
506        while this.parser.token != token::Eof {
507            sub_parsers.push(this.parse_meta_item_inner()?);
508
509            if !this.parser.eat(exp!(Comma)) {
510                break;
511            }
512        }
513
514        if parser.token != token::Eof {
515            parser.unexpected()?;
516        }
517
518        Ok(MetaItemListParser { sub_parsers, span })
519    }
520}
521
522#[derive(Debug, Clone)]
523pub struct MetaItemListParser<'a> {
524    sub_parsers: ThinVec<MetaItemOrLitParser<'a>>,
525    pub span: Span,
526}
527
528impl<'a> MetaItemListParser<'a> {
529    fn new<'sess>(
530        delim: &'a DelimArgs,
531        psess: &'sess ParseSess,
532        should_emit: ShouldEmit,
533    ) -> Option<Self> {
534        match MetaItemListParserContext::parse(
535            delim.tokens.clone(),
536            psess,
537            delim.dspan.entire(),
538            should_emit,
539        ) {
540            Ok(s) => Some(s),
541            Err(e) => {
542                e.emit_unless_delay(!should_emit.should_emit());
543                None
544            }
545        }
546    }
547
548    /// Lets you pick and choose as what you want to parse each element in the list
549    pub fn mixed(&self) -> impl Iterator<Item = &MetaItemOrLitParser<'a>> {
550        self.sub_parsers.iter()
551    }
552
553    pub fn len(&self) -> usize {
554        self.sub_parsers.len()
555    }
556
557    pub fn is_empty(&self) -> bool {
558        self.len() == 0
559    }
560
561    /// Returns Some if the list contains only a single element.
562    ///
563    /// Inside the Some is the parser to parse this single element.
564    pub fn single(&self) -> Option<&MetaItemOrLitParser<'a>> {
565        let mut iter = self.mixed();
566        iter.next().filter(|_| iter.next().is_none())
567    }
568}