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