]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_expand/src/mbe/metavar_expr.rs
Auto merge of #95604 - nbdd0121:used2, r=petrochenkov
[rust.git] / compiler / rustc_expand / src / mbe / metavar_expr.rs
1 use rustc_ast::token;
2 use rustc_ast::tokenstream::{Cursor, TokenStream, TokenTree};
3 use rustc_ast::{LitIntType, LitKind};
4 use rustc_ast_pretty::pprust;
5 use rustc_errors::{Applicability, PResult};
6 use rustc_session::parse::ParseSess;
7 use rustc_span::symbol::Ident;
8 use rustc_span::Span;
9
10 /// A meta-variable expression, for expansions based on properties of meta-variables.
11 #[derive(Debug, Clone, PartialEq, Encodable, Decodable)]
12 crate enum MetaVarExpr {
13     /// The number of repetitions of an identifier, optionally limited to a number
14     /// of outer-most repetition depths. If the depth limit is `None` then the depth is unlimited.
15     Count(Ident, Option<usize>),
16
17     /// Ignore a meta-variable for repetition without expansion.
18     Ignore(Ident),
19
20     /// The index of the repetition at a particular depth, where 0 is the inner-most
21     /// repetition. The `usize` is the depth.
22     Index(usize),
23
24     /// The length of the repetition at a particular depth, where 0 is the inner-most
25     /// repetition. The `usize` is the depth.
26     Length(usize),
27 }
28
29 impl MetaVarExpr {
30     /// Attempt to parse a meta-variable expression from a token stream.
31     crate fn parse<'sess>(
32         input: &TokenStream,
33         outer_span: Span,
34         sess: &'sess ParseSess,
35     ) -> PResult<'sess, MetaVarExpr> {
36         let mut tts = input.trees();
37         let ident = parse_ident(&mut tts, sess, outer_span)?;
38         let Some(TokenTree::Delimited(_, token::Paren, args)) = tts.next() else {
39             let msg = "meta-variable expression parameter must be wrapped in parentheses";
40             return Err(sess.span_diagnostic.struct_span_err(ident.span, msg));
41         };
42         check_trailing_token(&mut tts, sess)?;
43         let mut iter = args.trees();
44         let rslt = match &*ident.as_str() {
45             "count" => parse_count(&mut iter, sess, ident.span)?,
46             "ignore" => MetaVarExpr::Ignore(parse_ident(&mut iter, sess, ident.span)?),
47             "index" => MetaVarExpr::Index(parse_depth(&mut iter, sess, ident.span)?),
48             "length" => MetaVarExpr::Length(parse_depth(&mut iter, sess, ident.span)?),
49             _ => {
50                 let err_msg = "unrecognized meta-variable expression";
51                 let mut err = sess.span_diagnostic.struct_span_err(ident.span, err_msg);
52                 err.span_suggestion(
53                     ident.span,
54                     "supported expressions are count, ignore, index and length",
55                     String::new(),
56                     Applicability::MachineApplicable,
57                 );
58                 return Err(err);
59             }
60         };
61         check_trailing_token(&mut iter, sess)?;
62         Ok(rslt)
63     }
64
65     crate fn ident(&self) -> Option<Ident> {
66         match *self {
67             MetaVarExpr::Count(ident, _) | MetaVarExpr::Ignore(ident) => Some(ident),
68             MetaVarExpr::Index(..) | MetaVarExpr::Length(..) => None,
69         }
70     }
71 }
72
73 // Checks if there are any remaining tokens. For example, `${ignore(ident ... a b c ...)}`
74 fn check_trailing_token<'sess>(iter: &mut Cursor, sess: &'sess ParseSess) -> PResult<'sess, ()> {
75     if let Some(tt) = iter.next() {
76         let mut diag = sess.span_diagnostic.struct_span_err(
77             tt.span(),
78             &format!("unexpected token: {}", pprust::tt_to_string(&tt)),
79         );
80         diag.span_note(tt.span(), "meta-variable expression must not have trailing tokens");
81         Err(diag)
82     } else {
83         Ok(())
84     }
85 }
86
87 /// Parse a meta-variable `count` expression: `count(ident[, depth])`
88 fn parse_count<'sess>(
89     iter: &mut Cursor,
90     sess: &'sess ParseSess,
91     span: Span,
92 ) -> PResult<'sess, MetaVarExpr> {
93     let ident = parse_ident(iter, sess, span)?;
94     let depth = if try_eat_comma(iter) { Some(parse_depth(iter, sess, span)?) } else { None };
95     Ok(MetaVarExpr::Count(ident, depth))
96 }
97
98 /// Parses the depth used by index(depth) and length(depth).
99 fn parse_depth<'sess>(
100     iter: &mut Cursor,
101     sess: &'sess ParseSess,
102     span: Span,
103 ) -> PResult<'sess, usize> {
104     let Some(tt) = iter.next() else { return Ok(0) };
105     let TokenTree::Token(token::Token {
106         kind: token::TokenKind::Literal(lit), ..
107     }) = tt else {
108         return Err(sess.span_diagnostic.struct_span_err(
109             span,
110             "meta-variable expression depth must be a literal"
111         ));
112     };
113     if let Ok(lit_kind) = LitKind::from_lit_token(lit)
114         && let LitKind::Int(n_u128, LitIntType::Unsuffixed) = lit_kind
115         && let Ok(n_usize) = usize::try_from(n_u128)
116     {
117         Ok(n_usize)
118     }
119     else {
120         let msg = "only unsuffixes integer literals are supported in meta-variable expressions";
121         Err(sess.span_diagnostic.struct_span_err(span, msg))
122     }
123 }
124
125 /// Parses an generic ident
126 fn parse_ident<'sess>(
127     iter: &mut Cursor,
128     sess: &'sess ParseSess,
129     span: Span,
130 ) -> PResult<'sess, Ident> {
131     if let Some(tt) = iter.next() && let TokenTree::Token(token) = tt {
132         if let Some((elem, false)) = token.ident() {
133             return Ok(elem);
134         }
135         let token_str = pprust::token_to_string(&token);
136         let mut err = sess.span_diagnostic.struct_span_err(
137             span,
138             &format!("expected identifier, found `{}`", &token_str)
139         );
140         err.span_suggestion(
141             token.span,
142             &format!("try removing `{}`", &token_str),
143             String::new(),
144             Applicability::MaybeIncorrect,
145         );
146         return Err(err);
147     }
148     Err(sess.span_diagnostic.struct_span_err(span, "expected identifier"))
149 }
150
151 /// Tries to move the iterator forward returning `true` if there is a comma. If not, then the
152 /// iterator is not modified and the result is `false`.
153 fn try_eat_comma(iter: &mut Cursor) -> bool {
154     if let Some(TokenTree::Token(token::Token { kind: token::Comma, .. })) = iter.look_ahead(0) {
155         let _ = iter.next();
156         return true;
157     }
158     false
159 }