rustc_parse/parser/
nonterminal.rs

1use std::sync::Arc;
2
3use rustc_ast::HasTokens;
4use rustc_ast::ptr::P;
5use rustc_ast::token::Nonterminal::*;
6use rustc_ast::token::NtExprKind::*;
7use rustc_ast::token::NtPatKind::*;
8use rustc_ast::token::{
9    self, Delimiter, InvisibleOrigin, MetaVarKind, Nonterminal, NonterminalKind, Token,
10};
11use rustc_ast_pretty::pprust;
12use rustc_errors::PResult;
13use rustc_span::{Ident, kw};
14
15use crate::errors::UnexpectedNonterminal;
16use crate::parser::pat::{CommaRecoveryMode, RecoverColon, RecoverComma};
17use crate::parser::{FollowedByType, ForceCollect, ParseNtResult, Parser, PathStyle};
18
19impl<'a> Parser<'a> {
20    /// Checks whether a non-terminal may begin with a particular token.
21    ///
22    /// Returning `false` is a *stability guarantee* that such a matcher will *never* begin with
23    /// that token. Be conservative (return true) if not sure. Inlined because it has a single call
24    /// site.
25    #[inline]
26    pub fn nonterminal_may_begin_with(kind: NonterminalKind, token: &Token) -> bool {
27        /// Checks whether the non-terminal may contain a single (non-keyword) identifier.
28        fn may_be_ident(kind: MetaVarKind) -> bool {
29            match kind {
30                MetaVarKind::Stmt
31                | MetaVarKind::Pat(_)
32                | MetaVarKind::Expr { .. }
33                | MetaVarKind::Ty
34                | MetaVarKind::Literal // `true`, `false`
35                | MetaVarKind::Meta
36                | MetaVarKind::Path => true,
37
38                MetaVarKind::Item
39                | MetaVarKind::Block
40                | MetaVarKind::Vis => false,
41
42                MetaVarKind::Ident
43                | MetaVarKind::Lifetime
44                | MetaVarKind::TT => unreachable!(),
45            }
46        }
47
48        /// Old variant of `may_be_ident`. Being phased out.
49        fn nt_may_be_ident(nt: &Nonterminal) -> bool {
50            match nt {
51                NtStmt(_)
52                | NtPat(_)
53                | NtExpr(_)
54                | NtTy(_)
55                | NtLiteral(_) // `true`, `false`
56                | NtMeta(_)
57                | NtPath(_) => true,
58
59                NtItem(_)
60                | NtBlock(_)
61                | NtVis(_) => false,
62            }
63        }
64
65        match kind {
66            // `expr_2021` and earlier
67            NonterminalKind::Expr(Expr2021 { .. }) => {
68                token.can_begin_expr()
69                // This exception is here for backwards compatibility.
70                && !token.is_keyword(kw::Let)
71                // This exception is here for backwards compatibility.
72                && !token.is_keyword(kw::Const)
73            }
74            // Current edition expressions
75            NonterminalKind::Expr(Expr) => {
76                // In Edition 2024, `_` is considered an expression, so we
77                // need to allow it here because `token.can_begin_expr()` does
78                // not consider `_` to be an expression.
79                //
80                // Because `can_begin_expr` is used elsewhere, we need to reduce
81                // the scope of where the `_` is considered an expression to
82                // just macro parsing code.
83                (token.can_begin_expr() || token.is_keyword(kw::Underscore))
84                // This exception is here for backwards compatibility.
85                && !token.is_keyword(kw::Let)
86            }
87            NonterminalKind::Ty => token.can_begin_type(),
88            NonterminalKind::Ident => get_macro_ident(token).is_some(),
89            NonterminalKind::Literal => token.can_begin_literal_maybe_minus(),
90            NonterminalKind::Vis => match token.kind {
91                // The follow-set of :vis + "priv" keyword + interpolated
92                token::Comma
93                | token::Ident(..)
94                | token::NtIdent(..)
95                | token::NtLifetime(..)
96                | token::Interpolated(_)
97                | token::OpenDelim(Delimiter::Invisible(InvisibleOrigin::MetaVar(_))) => true,
98                _ => token.can_begin_type(),
99            },
100            NonterminalKind::Block => match &token.kind {
101                token::OpenDelim(Delimiter::Brace) => true,
102                token::NtLifetime(..) => true,
103                token::Interpolated(nt) => match &**nt {
104                    NtBlock(_) | NtStmt(_) | NtExpr(_) | NtLiteral(_) => true,
105                    NtItem(_) | NtPat(_) | NtTy(_) | NtMeta(_) | NtPath(_) | NtVis(_) => false,
106                },
107                token::OpenDelim(Delimiter::Invisible(InvisibleOrigin::MetaVar(k))) => match k {
108                    MetaVarKind::Block
109                    | MetaVarKind::Stmt
110                    | MetaVarKind::Expr { .. }
111                    | MetaVarKind::Literal => true,
112                    MetaVarKind::Item
113                    | MetaVarKind::Pat(_)
114                    | MetaVarKind::Ty
115                    | MetaVarKind::Meta
116                    | MetaVarKind::Path
117                    | MetaVarKind::Vis => false,
118                    MetaVarKind::Lifetime | MetaVarKind::Ident | MetaVarKind::TT => {
119                        unreachable!()
120                    }
121                },
122                _ => false,
123            },
124            NonterminalKind::Path | NonterminalKind::Meta => match &token.kind {
125                token::PathSep | token::Ident(..) | token::NtIdent(..) => true,
126                token::Interpolated(nt) => nt_may_be_ident(nt),
127                token::OpenDelim(Delimiter::Invisible(InvisibleOrigin::MetaVar(kind))) => {
128                    may_be_ident(*kind)
129                }
130                _ => false,
131            },
132            NonterminalKind::Pat(pat_kind) => token.can_begin_pattern(pat_kind),
133            NonterminalKind::Lifetime => match &token.kind {
134                token::Lifetime(..) | token::NtLifetime(..) => true,
135                _ => false,
136            },
137            NonterminalKind::TT | NonterminalKind::Item | NonterminalKind::Stmt => {
138                !matches!(token.kind, token::CloseDelim(_))
139            }
140        }
141    }
142
143    /// Parse a non-terminal (e.g. MBE `:pat` or `:ident`). Inlined because there is only one call
144    /// site.
145    #[inline]
146    pub fn parse_nonterminal(&mut self, kind: NonterminalKind) -> PResult<'a, ParseNtResult> {
147        // A `macro_rules!` invocation may pass a captured item/expr to a proc-macro,
148        // which requires having captured tokens available. Since we cannot determine
149        // in advance whether or not a proc-macro will be (transitively) invoked,
150        // we always capture tokens for any `Nonterminal` which needs them.
151        let mut nt = match kind {
152            // Note that TT is treated differently to all the others.
153            NonterminalKind::TT => return Ok(ParseNtResult::Tt(self.parse_token_tree())),
154            NonterminalKind::Item => match self.parse_item(ForceCollect::Yes)? {
155                Some(item) => NtItem(item),
156                None => {
157                    return Err(self
158                        .dcx()
159                        .create_err(UnexpectedNonterminal::Item(self.token.span)));
160                }
161            },
162            NonterminalKind::Block => {
163                // While a block *expression* may have attributes (e.g. `#[my_attr] { ... }`),
164                // the ':block' matcher does not support them
165                NtBlock(self.collect_tokens_no_attrs(|this| this.parse_block())?)
166            }
167            NonterminalKind::Stmt => match self.parse_stmt(ForceCollect::Yes)? {
168                Some(s) => NtStmt(P(s)),
169                None => {
170                    return Err(self
171                        .dcx()
172                        .create_err(UnexpectedNonterminal::Statement(self.token.span)));
173                }
174            },
175            NonterminalKind::Pat(pat_kind) => {
176                NtPat(self.collect_tokens_no_attrs(|this| match pat_kind {
177                    PatParam { .. } => this.parse_pat_no_top_alt(None, None),
178                    PatWithOr => this.parse_pat_no_top_guard(
179                        None,
180                        RecoverComma::No,
181                        RecoverColon::No,
182                        CommaRecoveryMode::EitherTupleOrPipe,
183                    ),
184                })?)
185            }
186            NonterminalKind::Expr(_) => NtExpr(self.parse_expr_force_collect()?),
187            NonterminalKind::Literal => {
188                // The `:literal` matcher does not support attributes
189                NtLiteral(self.collect_tokens_no_attrs(|this| this.parse_literal_maybe_minus())?)
190            }
191            NonterminalKind::Ty => {
192                NtTy(self.collect_tokens_no_attrs(|this| this.parse_ty_no_question_mark_recover())?)
193            }
194            // this could be handled like a token, since it is one
195            NonterminalKind::Ident => {
196                return if let Some((ident, is_raw)) = get_macro_ident(&self.token) {
197                    self.bump();
198                    Ok(ParseNtResult::Ident(ident, is_raw))
199                } else {
200                    Err(self.dcx().create_err(UnexpectedNonterminal::Ident {
201                        span: self.token.span,
202                        token: self.token.clone(),
203                    }))
204                };
205            }
206            NonterminalKind::Path => {
207                NtPath(P(self.collect_tokens_no_attrs(|this| this.parse_path(PathStyle::Type))?))
208            }
209            NonterminalKind::Meta => NtMeta(P(self.parse_attr_item(ForceCollect::Yes)?)),
210            NonterminalKind::Vis => {
211                NtVis(P(self
212                    .collect_tokens_no_attrs(|this| this.parse_visibility(FollowedByType::Yes))?))
213            }
214            NonterminalKind::Lifetime => {
215                // We want to keep `'keyword` parsing, just like `keyword` is still
216                // an ident for nonterminal purposes.
217                return if let Some((ident, is_raw)) = self.token.lifetime() {
218                    self.bump();
219                    Ok(ParseNtResult::Lifetime(ident, is_raw))
220                } else {
221                    Err(self.dcx().create_err(UnexpectedNonterminal::Lifetime {
222                        span: self.token.span,
223                        token: self.token.clone(),
224                    }))
225                };
226            }
227        };
228
229        // If tokens are supported at all, they should be collected.
230        if matches!(nt.tokens_mut(), Some(None)) {
231            panic!(
232                "Missing tokens for nt {:?} at {:?}: {:?}",
233                nt,
234                nt.use_span(),
235                pprust::nonterminal_to_string(&nt)
236            );
237        }
238
239        Ok(ParseNtResult::Nt(Arc::new(nt)))
240    }
241}
242
243/// The token is an identifier, but not `_`.
244/// We prohibit passing `_` to macros expecting `ident` for now.
245fn get_macro_ident(token: &Token) -> Option<(Ident, token::IdentIsRaw)> {
246    token.ident().filter(|(ident, _)| ident.name != kw::Underscore)
247}