1use rustc_ast::token::{self, Delimiter, IdentIsRaw, Lit, Token, TokenKind};
2use rustc_ast::tokenstream::{TokenStream, TokenStreamIter, TokenTree};
3use rustc_ast::{LitIntType, LitKind};
4use rustc_ast_pretty::pprust;
5use rustc_errors::{Applicability, PResult};
6use rustc_macros::{Decodable, Encodable};
7use rustc_session::parse::ParseSess;
8use rustc_span::{Ident, Span, Symbol, sym};
9
10use crate::errors;
11
12pub(crate) const RAW_IDENT_ERR: &str = "`${concat(..)}` currently does not support raw identifiers";
13pub(crate) const UNSUPPORTED_CONCAT_ELEM_ERR: &str = "expected identifier or string literal";
14
15#[derive(Debug, PartialEq, Encodable, Decodable)]
17pub(crate) enum MetaVarExpr {
18 Concat(Box<[MetaVarExprConcatElem]>),
20
21 Count(Ident, usize),
23
24 Ignore(Ident),
26
27 Index(usize),
30
31 Len(usize),
34}
35
36impl MetaVarExpr {
37 pub(crate) fn parse<'psess>(
39 input: &TokenStream,
40 outer_span: Span,
41 psess: &'psess ParseSess,
42 ) -> PResult<'psess, MetaVarExpr> {
43 let mut iter = input.iter();
44 let ident = parse_ident(&mut iter, psess, outer_span)?;
45 let next = iter.next();
46 let Some(TokenTree::Delimited(.., Delimiter::Parenthesis, args)) = next else {
47 let (unexpected_span, insert_span) = match next {
50 Some(TokenTree::Delimited(..)) => (None, None),
51 Some(tt) => (Some(tt.span()), None),
52 None => (None, Some(ident.span.shrink_to_hi())),
53 };
54 let err =
55 errors::MveMissingParen { ident_span: ident.span, unexpected_span, insert_span };
56 return Err(psess.dcx().create_err(err));
57 };
58
59 if iter.peek().is_some() {
61 let span = iter_span(&iter).expect("checked is_some above");
62 let err = errors::MveExtraTokens {
63 span,
64 ident_span: ident.span,
65 extra_count: iter.count(),
66 ..Default::default()
67 };
68 return Err(psess.dcx().create_err(err));
69 }
70
71 let mut iter = args.iter();
72 let rslt = match ident.name {
73 sym::concat => parse_concat(&mut iter, psess, outer_span, ident.span)?,
74 sym::count => parse_count(&mut iter, psess, ident.span)?,
75 sym::ignore => {
76 eat_dollar(&mut iter, psess, ident.span)?;
77 MetaVarExpr::Ignore(parse_ident(&mut iter, psess, ident.span)?)
78 }
79 sym::index => MetaVarExpr::Index(parse_depth(&mut iter, psess, ident.span)?),
80 sym::len => MetaVarExpr::Len(parse_depth(&mut iter, psess, ident.span)?),
81 _ => {
82 let err = errors::MveUnrecognizedExpr {
83 span: ident.span,
84 valid_expr_list: "`count`, `ignore`, `index`, `len`, and `concat`",
85 };
86 return Err(psess.dcx().create_err(err));
87 }
88 };
89 check_trailing_tokens(&mut iter, psess, ident)?;
90 Ok(rslt)
91 }
92
93 pub(crate) fn for_each_metavar<A>(&self, mut aux: A, mut cb: impl FnMut(A, &Ident) -> A) -> A {
94 match self {
95 MetaVarExpr::Concat(elems) => {
96 for elem in elems {
97 if let MetaVarExprConcatElem::Var(ident) = elem {
98 aux = cb(aux, ident)
99 }
100 }
101 aux
102 }
103 MetaVarExpr::Count(ident, _) | MetaVarExpr::Ignore(ident) => cb(aux, ident),
104 MetaVarExpr::Index(..) | MetaVarExpr::Len(..) => aux,
105 }
106 }
107}
108
109fn check_trailing_tokens<'psess>(
112 iter: &mut TokenStreamIter<'_>,
113 psess: &'psess ParseSess,
114 ident: Ident,
115) -> PResult<'psess, ()> {
116 if iter.peek().is_none() {
117 return Ok(());
119 }
120
121 let (min_or_exact_args, max_args) = match ident.name {
123 sym::concat => panic!("concat takes unlimited tokens but didn't eat them all"),
124 sym::ignore => (1, None),
125 sym::count => (1, Some(2)),
127 sym::index | sym::len => (0, Some(1)),
129 other => unreachable!("unknown MVEs should be rejected earlier (got `{other}`)"),
130 };
131
132 let err = errors::MveExtraTokens {
133 span: iter_span(iter).expect("checked is_none above"),
134 ident_span: ident.span,
135 extra_count: iter.count(),
136
137 exact_args_note: if max_args.is_some() { None } else { Some(()) },
138 range_args_note: if max_args.is_some() { Some(()) } else { None },
139 min_or_exact_args,
140 max_args: max_args.unwrap_or_default(),
141 name: ident.to_string(),
142 };
143 Err(psess.dcx().create_err(err))
144}
145
146fn iter_span(iter: &TokenStreamIter<'_>) -> Option<Span> {
148 let mut iter = iter.clone(); let first_sp = iter.next()?.span();
150 let last_sp = iter.last().map(TokenTree::span).unwrap_or(first_sp);
151 let span = first_sp.with_hi(last_sp.hi());
152 Some(span)
153}
154
155#[derive(Debug, Decodable, Encodable, PartialEq)]
158pub(crate) enum MetaVarExprConcatElem {
159 Ident(Ident),
162 Literal(Symbol),
164 Var(Ident),
167}
168
169fn parse_concat<'psess>(
171 iter: &mut TokenStreamIter<'_>,
172 psess: &'psess ParseSess,
173 outer_span: Span,
174 expr_ident_span: Span,
175) -> PResult<'psess, MetaVarExpr> {
176 let mut result = Vec::new();
177 loop {
178 let is_var = try_eat_dollar(iter);
179 let token = parse_token(iter, psess, outer_span)?;
180 let element = if is_var {
181 MetaVarExprConcatElem::Var(parse_ident_from_token(psess, token)?)
182 } else if let TokenKind::Literal(Lit { kind: token::LitKind::Str, symbol, suffix: None }) =
183 token.kind
184 {
185 MetaVarExprConcatElem::Literal(symbol)
186 } else {
187 match parse_ident_from_token(psess, token) {
188 Err(err) => {
189 err.cancel();
190 return Err(psess
191 .dcx()
192 .struct_span_err(token.span, UNSUPPORTED_CONCAT_ELEM_ERR));
193 }
194 Ok(elem) => MetaVarExprConcatElem::Ident(elem),
195 }
196 };
197 result.push(element);
198 if iter.peek().is_none() {
199 break;
200 }
201 if !try_eat_comma(iter) {
202 return Err(psess.dcx().struct_span_err(outer_span, "expected comma"));
203 }
204 }
205 if result.len() < 2 {
206 return Err(psess
207 .dcx()
208 .struct_span_err(expr_ident_span, "`concat` must have at least two elements"));
209 }
210 Ok(MetaVarExpr::Concat(result.into()))
211}
212
213fn parse_count<'psess>(
215 iter: &mut TokenStreamIter<'_>,
216 psess: &'psess ParseSess,
217 span: Span,
218) -> PResult<'psess, MetaVarExpr> {
219 eat_dollar(iter, psess, span)?;
220 let ident = parse_ident(iter, psess, span)?;
221 let depth = if try_eat_comma(iter) {
222 if iter.peek().is_none() {
223 return Err(psess.dcx().struct_span_err(
224 span,
225 "`count` followed by a comma must have an associated index indicating its depth",
226 ));
227 }
228 parse_depth(iter, psess, span)?
229 } else {
230 0
231 };
232 Ok(MetaVarExpr::Count(ident, depth))
233}
234
235fn parse_depth<'psess>(
237 iter: &mut TokenStreamIter<'_>,
238 psess: &'psess ParseSess,
239 span: Span,
240) -> PResult<'psess, usize> {
241 let Some(tt) = iter.next() else { return Ok(0) };
242 let TokenTree::Token(Token { kind: TokenKind::Literal(lit), .. }, _) = tt else {
243 return Err(psess
244 .dcx()
245 .struct_span_err(span, "meta-variable expression depth must be a literal"));
246 };
247 if let Ok(lit_kind) = LitKind::from_token_lit(*lit)
248 && let LitKind::Int(n_u128, LitIntType::Unsuffixed) = lit_kind
249 && let Ok(n_usize) = usize::try_from(n_u128.get())
250 {
251 Ok(n_usize)
252 } else {
253 let msg = "only unsuffixes integer literals are supported in meta-variable expressions";
254 Err(psess.dcx().struct_span_err(span, msg))
255 }
256}
257
258fn parse_ident<'psess>(
260 iter: &mut TokenStreamIter<'_>,
261 psess: &'psess ParseSess,
262 fallback_span: Span,
263) -> PResult<'psess, Ident> {
264 let token = parse_token(iter, psess, fallback_span)?;
265 parse_ident_from_token(psess, token)
266}
267
268fn parse_ident_from_token<'psess>(
269 psess: &'psess ParseSess,
270 token: &Token,
271) -> PResult<'psess, Ident> {
272 if let Some((elem, is_raw)) = token.ident() {
273 if let IdentIsRaw::Yes = is_raw {
274 return Err(psess.dcx().struct_span_err(elem.span, RAW_IDENT_ERR));
275 }
276 return Ok(elem);
277 }
278 let token_str = pprust::token_to_string(token);
279 let mut err = psess
280 .dcx()
281 .struct_span_err(token.span, format!("expected identifier, found `{token_str}`"));
282 err.span_suggestion(
283 token.span,
284 format!("try removing `{token_str}`"),
285 "",
286 Applicability::MaybeIncorrect,
287 );
288 Err(err)
289}
290
291fn parse_token<'psess, 't>(
292 iter: &mut TokenStreamIter<'t>,
293 psess: &'psess ParseSess,
294 fallback_span: Span,
295) -> PResult<'psess, &'t Token> {
296 let Some(tt) = iter.next() else {
297 return Err(psess.dcx().struct_span_err(fallback_span, UNSUPPORTED_CONCAT_ELEM_ERR));
298 };
299 let TokenTree::Token(token, _) = tt else {
300 return Err(psess.dcx().struct_span_err(tt.span(), UNSUPPORTED_CONCAT_ELEM_ERR));
301 };
302 Ok(token)
303}
304
305fn try_eat_comma(iter: &mut TokenStreamIter<'_>) -> bool {
308 if let Some(TokenTree::Token(Token { kind: token::Comma, .. }, _)) = iter.peek() {
309 let _ = iter.next();
310 return true;
311 }
312 false
313}
314
315fn try_eat_dollar(iter: &mut TokenStreamIter<'_>) -> bool {
318 if let Some(TokenTree::Token(Token { kind: token::Dollar, .. }, _)) = iter.peek() {
319 let _ = iter.next();
320 return true;
321 }
322 false
323}
324
325fn eat_dollar<'psess>(
327 iter: &mut TokenStreamIter<'_>,
328 psess: &'psess ParseSess,
329 span: Span,
330) -> PResult<'psess, ()> {
331 if try_eat_dollar(iter) {
332 return Ok(());
333 }
334 Err(psess.dcx().struct_span_err(
335 span,
336 "meta-variables within meta-variable expressions must be referenced using a dollar sign",
337 ))
338}