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