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
10pub(crate) const RAW_IDENT_ERR: &str = "`${concat(..)}` currently does not support raw identifiers";
11pub(crate) const UNSUPPORTED_CONCAT_ELEM_ERR: &str = "expected identifier or string literal";
12
13#[derive(Debug, PartialEq, Encodable, Decodable)]
15pub(crate) enum MetaVarExpr {
16 Concat(Box<[MetaVarExprConcatElem]>),
18
19 Count(Ident, usize),
21
22 Ignore(Ident),
24
25 Index(usize),
28
29 Len(usize),
32}
33
34impl MetaVarExpr {
35 pub(crate) fn parse<'psess>(
37 input: &TokenStream,
38 outer_span: Span,
39 psess: &'psess ParseSess,
40 ) -> PResult<'psess, MetaVarExpr> {
41 let mut iter = input.iter();
42 let ident = parse_ident(&mut iter, psess, outer_span)?;
43 let Some(TokenTree::Delimited(.., Delimiter::Parenthesis, args)) = iter.next() else {
44 let msg = "meta-variable expression parameter must be wrapped in parentheses";
45 return Err(psess.dcx().struct_span_err(ident.span, msg));
46 };
47 check_trailing_token(&mut iter, psess)?;
48 let mut iter = args.iter();
49 let rslt = match ident.as_str() {
50 "concat" => {
51 let mut result = Vec::new();
52 loop {
53 let is_var = try_eat_dollar(&mut iter);
54 let token = parse_token(&mut iter, psess, outer_span)?;
55 let element = if is_var {
56 MetaVarExprConcatElem::Var(parse_ident_from_token(psess, token)?)
57 } else if let TokenKind::Literal(Lit {
58 kind: token::LitKind::Str,
59 symbol,
60 suffix: None,
61 }) = token.kind
62 {
63 MetaVarExprConcatElem::Literal(symbol)
64 } else {
65 match parse_ident_from_token(psess, token) {
66 Err(err) => {
67 err.cancel();
68 return Err(psess
69 .dcx()
70 .struct_span_err(token.span, UNSUPPORTED_CONCAT_ELEM_ERR));
71 }
72 Ok(elem) => MetaVarExprConcatElem::Ident(elem),
73 }
74 };
75 result.push(element);
76 if iter.peek().is_none() {
77 break;
78 }
79 if !try_eat_comma(&mut iter) {
80 return Err(psess.dcx().struct_span_err(outer_span, "expected comma"));
81 }
82 }
83 if result.len() < 2 {
84 return Err(psess
85 .dcx()
86 .struct_span_err(ident.span, "`concat` must have at least two elements"));
87 }
88 MetaVarExpr::Concat(result.into())
89 }
90 "count" => parse_count(&mut iter, psess, ident.span)?,
91 "ignore" => {
92 eat_dollar(&mut iter, psess, ident.span)?;
93 MetaVarExpr::Ignore(parse_ident(&mut iter, psess, ident.span)?)
94 }
95 "index" => MetaVarExpr::Index(parse_depth(&mut iter, psess, ident.span)?),
96 "len" => MetaVarExpr::Len(parse_depth(&mut iter, psess, ident.span)?),
97 _ => {
98 let err_msg = "unrecognized meta-variable expression";
99 let mut err = psess.dcx().struct_span_err(ident.span, err_msg);
100 err.span_suggestion(
101 ident.span,
102 "supported expressions are count, ignore, index and len",
103 "",
104 Applicability::MachineApplicable,
105 );
106 return Err(err);
107 }
108 };
109 check_trailing_token(&mut iter, psess)?;
110 Ok(rslt)
111 }
112
113 pub(crate) fn for_each_metavar<A>(&self, mut aux: A, mut cb: impl FnMut(A, &Ident) -> A) -> A {
114 match self {
115 MetaVarExpr::Concat(elems) => {
116 for elem in elems {
117 if let MetaVarExprConcatElem::Var(ident) = elem {
118 aux = cb(aux, ident)
119 }
120 }
121 aux
122 }
123 MetaVarExpr::Count(ident, _) | MetaVarExpr::Ignore(ident) => cb(aux, ident),
124 MetaVarExpr::Index(..) | MetaVarExpr::Len(..) => aux,
125 }
126 }
127}
128
129#[derive(Debug, Decodable, Encodable, PartialEq)]
132pub(crate) enum MetaVarExprConcatElem {
133 Ident(Ident),
136 Literal(Symbol),
138 Var(Ident),
141}
142
143fn check_trailing_token<'psess>(
145 iter: &mut TokenStreamIter<'_>,
146 psess: &'psess ParseSess,
147) -> PResult<'psess, ()> {
148 if let Some(tt) = iter.next() {
149 let mut diag = psess
150 .dcx()
151 .struct_span_err(tt.span(), format!("unexpected token: {}", pprust::tt_to_string(tt)));
152 diag.span_note(tt.span(), "meta-variable expression must not have trailing tokens");
153 Err(diag)
154 } else {
155 Ok(())
156 }
157}
158
159fn parse_count<'psess>(
161 iter: &mut TokenStreamIter<'_>,
162 psess: &'psess ParseSess,
163 span: Span,
164) -> PResult<'psess, MetaVarExpr> {
165 eat_dollar(iter, psess, span)?;
166 let ident = parse_ident(iter, psess, span)?;
167 let depth = if try_eat_comma(iter) {
168 if iter.peek().is_none() {
169 return Err(psess.dcx().struct_span_err(
170 span,
171 "`count` followed by a comma must have an associated index indicating its depth",
172 ));
173 }
174 parse_depth(iter, psess, span)?
175 } else {
176 0
177 };
178 Ok(MetaVarExpr::Count(ident, depth))
179}
180
181fn parse_depth<'psess>(
183 iter: &mut TokenStreamIter<'_>,
184 psess: &'psess ParseSess,
185 span: Span,
186) -> PResult<'psess, usize> {
187 let Some(tt) = iter.next() else { return Ok(0) };
188 let TokenTree::Token(Token { kind: TokenKind::Literal(lit), .. }, _) = tt else {
189 return Err(psess
190 .dcx()
191 .struct_span_err(span, "meta-variable expression depth must be a literal"));
192 };
193 if let Ok(lit_kind) = LitKind::from_token_lit(*lit)
194 && let LitKind::Int(n_u128, LitIntType::Unsuffixed) = lit_kind
195 && let Ok(n_usize) = usize::try_from(n_u128.get())
196 {
197 Ok(n_usize)
198 } else {
199 let msg = "only unsuffixes integer literals are supported in meta-variable expressions";
200 Err(psess.dcx().struct_span_err(span, msg))
201 }
202}
203
204fn parse_ident<'psess>(
206 iter: &mut TokenStreamIter<'_>,
207 psess: &'psess ParseSess,
208 fallback_span: Span,
209) -> PResult<'psess, Ident> {
210 let token = parse_token(iter, psess, fallback_span)?;
211 parse_ident_from_token(psess, token)
212}
213
214fn parse_ident_from_token<'psess>(
215 psess: &'psess ParseSess,
216 token: &Token,
217) -> PResult<'psess, Ident> {
218 if let Some((elem, is_raw)) = token.ident() {
219 if let IdentIsRaw::Yes = is_raw {
220 return Err(psess.dcx().struct_span_err(elem.span, RAW_IDENT_ERR));
221 }
222 return Ok(elem);
223 }
224 let token_str = pprust::token_to_string(token);
225 let mut err = psess
226 .dcx()
227 .struct_span_err(token.span, format!("expected identifier, found `{token_str}`"));
228 err.span_suggestion(
229 token.span,
230 format!("try removing `{token_str}`"),
231 "",
232 Applicability::MaybeIncorrect,
233 );
234 Err(err)
235}
236
237fn parse_token<'psess, 't>(
238 iter: &mut TokenStreamIter<'t>,
239 psess: &'psess ParseSess,
240 fallback_span: Span,
241) -> PResult<'psess, &'t Token> {
242 let Some(tt) = iter.next() else {
243 return Err(psess.dcx().struct_span_err(fallback_span, UNSUPPORTED_CONCAT_ELEM_ERR));
244 };
245 let TokenTree::Token(token, _) = tt else {
246 return Err(psess.dcx().struct_span_err(tt.span(), UNSUPPORTED_CONCAT_ELEM_ERR));
247 };
248 Ok(token)
249}
250
251fn try_eat_comma(iter: &mut TokenStreamIter<'_>) -> bool {
254 if let Some(TokenTree::Token(Token { kind: token::Comma, .. }, _)) = iter.peek() {
255 let _ = iter.next();
256 return true;
257 }
258 false
259}
260
261fn try_eat_dollar(iter: &mut TokenStreamIter<'_>) -> bool {
264 if let Some(TokenTree::Token(Token { kind: token::Dollar, .. }, _)) = iter.peek() {
265 let _ = iter.next();
266 return true;
267 }
268 false
269}
270
271fn eat_dollar<'psess>(
273 iter: &mut TokenStreamIter<'_>,
274 psess: &'psess ParseSess,
275 span: Span,
276) -> PResult<'psess, ()> {
277 if try_eat_dollar(iter) {
278 return Ok(());
279 }
280 Err(psess.dcx().struct_span_err(
281 span,
282 "meta-variables within meta-variable expressions must be referenced using a dollar sign",
283 ))
284}