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};
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.as_str() {
73 "concat" => parse_concat(&mut iter, psess, outer_span, ident.span)?,
74 "count" => parse_count(&mut iter, psess, ident.span)?,
75 "ignore" => {
76 eat_dollar(&mut iter, psess, ident.span)?;
77 MetaVarExpr::Ignore(parse_ident(&mut iter, psess, ident.span)?)
78 }
79 "index" => MetaVarExpr::Index(parse_depth(&mut iter, psess, ident.span)?),
80 "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.as_str() {
123 "concat" => panic!("concat takes unlimited tokens but didn't eat them all"),
124 "ignore" => (1, None),
125 "count" => (1, Some(2)),
127 "index" => (0, Some(1)),
129 "len" => (0, Some(1)),
130 other => unreachable!("unknown MVEs should be rejected earlier (got `{other}`)"),
131 };
132
133 let err = errors::MveExtraTokens {
134 span: iter_span(iter).expect("checked is_none above"),
135 ident_span: ident.span,
136 extra_count: iter.count(),
137
138 exact_args_note: if max_args.is_some() { None } else { Some(()) },
139 range_args_note: if max_args.is_some() { Some(()) } else { None },
140 min_or_exact_args,
141 max_args: max_args.unwrap_or_default(),
142 name: ident.to_string(),
143 };
144 Err(psess.dcx().create_err(err))
145}
146
147fn iter_span(iter: &TokenStreamIter<'_>) -> Option<Span> {
149 let mut iter = iter.clone(); let first_sp = iter.next()?.span();
151 let last_sp = iter.last().map(TokenTree::span).unwrap_or(first_sp);
152 let span = first_sp.with_hi(last_sp.hi());
153 Some(span)
154}
155
156#[derive(Debug, Decodable, Encodable, PartialEq)]
159pub(crate) enum MetaVarExprConcatElem {
160 Ident(Ident),
163 Literal(Symbol),
165 Var(Ident),
168}
169
170fn parse_concat<'psess>(
172 iter: &mut TokenStreamIter<'_>,
173 psess: &'psess ParseSess,
174 outer_span: Span,
175 expr_ident_span: Span,
176) -> PResult<'psess, MetaVarExpr> {
177 let mut result = Vec::new();
178 loop {
179 let is_var = try_eat_dollar(iter);
180 let token = parse_token(iter, psess, outer_span)?;
181 let element = if is_var {
182 MetaVarExprConcatElem::Var(parse_ident_from_token(psess, token)?)
183 } else if let TokenKind::Literal(Lit { kind: token::LitKind::Str, symbol, suffix: None }) =
184 token.kind
185 {
186 MetaVarExprConcatElem::Literal(symbol)
187 } else {
188 match parse_ident_from_token(psess, token) {
189 Err(err) => {
190 err.cancel();
191 return Err(psess
192 .dcx()
193 .struct_span_err(token.span, UNSUPPORTED_CONCAT_ELEM_ERR));
194 }
195 Ok(elem) => MetaVarExprConcatElem::Ident(elem),
196 }
197 };
198 result.push(element);
199 if iter.peek().is_none() {
200 break;
201 }
202 if !try_eat_comma(iter) {
203 return Err(psess.dcx().struct_span_err(outer_span, "expected comma"));
204 }
205 }
206 if result.len() < 2 {
207 return Err(psess
208 .dcx()
209 .struct_span_err(expr_ident_span, "`concat` must have at least two elements"));
210 }
211 Ok(MetaVarExpr::Concat(result.into()))
212}
213
214fn parse_count<'psess>(
216 iter: &mut TokenStreamIter<'_>,
217 psess: &'psess ParseSess,
218 span: Span,
219) -> PResult<'psess, MetaVarExpr> {
220 eat_dollar(iter, psess, span)?;
221 let ident = parse_ident(iter, psess, span)?;
222 let depth = if try_eat_comma(iter) {
223 if iter.peek().is_none() {
224 return Err(psess.dcx().struct_span_err(
225 span,
226 "`count` followed by a comma must have an associated index indicating its depth",
227 ));
228 }
229 parse_depth(iter, psess, span)?
230 } else {
231 0
232 };
233 Ok(MetaVarExpr::Count(ident, depth))
234}
235
236fn parse_depth<'psess>(
238 iter: &mut TokenStreamIter<'_>,
239 psess: &'psess ParseSess,
240 span: Span,
241) -> PResult<'psess, usize> {
242 let Some(tt) = iter.next() else { return Ok(0) };
243 let TokenTree::Token(Token { kind: TokenKind::Literal(lit), .. }, _) = tt else {
244 return Err(psess
245 .dcx()
246 .struct_span_err(span, "meta-variable expression depth must be a literal"));
247 };
248 if let Ok(lit_kind) = LitKind::from_token_lit(*lit)
249 && let LitKind::Int(n_u128, LitIntType::Unsuffixed) = lit_kind
250 && let Ok(n_usize) = usize::try_from(n_u128.get())
251 {
252 Ok(n_usize)
253 } else {
254 let msg = "only unsuffixes integer literals are supported in meta-variable expressions";
255 Err(psess.dcx().struct_span_err(span, msg))
256 }
257}
258
259fn parse_ident<'psess>(
261 iter: &mut TokenStreamIter<'_>,
262 psess: &'psess ParseSess,
263 fallback_span: Span,
264) -> PResult<'psess, Ident> {
265 let token = parse_token(iter, psess, fallback_span)?;
266 parse_ident_from_token(psess, token)
267}
268
269fn parse_ident_from_token<'psess>(
270 psess: &'psess ParseSess,
271 token: &Token,
272) -> PResult<'psess, Ident> {
273 if let Some((elem, is_raw)) = token.ident() {
274 if let IdentIsRaw::Yes = is_raw {
275 return Err(psess.dcx().struct_span_err(elem.span, RAW_IDENT_ERR));
276 }
277 return Ok(elem);
278 }
279 let token_str = pprust::token_to_string(token);
280 let mut err = psess
281 .dcx()
282 .struct_span_err(token.span, format!("expected identifier, found `{token_str}`"));
283 err.span_suggestion(
284 token.span,
285 format!("try removing `{token_str}`"),
286 "",
287 Applicability::MaybeIncorrect,
288 );
289 Err(err)
290}
291
292fn parse_token<'psess, 't>(
293 iter: &mut TokenStreamIter<'t>,
294 psess: &'psess ParseSess,
295 fallback_span: Span,
296) -> PResult<'psess, &'t Token> {
297 let Some(tt) = iter.next() else {
298 return Err(psess.dcx().struct_span_err(fallback_span, UNSUPPORTED_CONCAT_ELEM_ERR));
299 };
300 let TokenTree::Token(token, _) = tt else {
301 return Err(psess.dcx().struct_span_err(tt.span(), UNSUPPORTED_CONCAT_ELEM_ERR));
302 };
303 Ok(token)
304}
305
306fn try_eat_comma(iter: &mut TokenStreamIter<'_>) -> bool {
309 if let Some(TokenTree::Token(Token { kind: token::Comma, .. }, _)) = iter.peek() {
310 let _ = iter.next();
311 return true;
312 }
313 false
314}
315
316fn try_eat_dollar(iter: &mut TokenStreamIter<'_>) -> bool {
319 if let Some(TokenTree::Token(Token { kind: token::Dollar, .. }, _)) = iter.peek() {
320 let _ = iter.next();
321 return true;
322 }
323 false
324}
325
326fn eat_dollar<'psess>(
328 iter: &mut TokenStreamIter<'_>,
329 psess: &'psess ParseSess,
330 span: Span,
331) -> PResult<'psess, ()> {
332 if try_eat_dollar(iter) {
333 return Ok(());
334 }
335 Err(psess.dcx().struct_span_err(
336 span,
337 "meta-variables within meta-variable expressions must be referenced using a dollar sign",
338 ))
339}