]>
Commit | Line | Data |
---|---|---|
e74abb32 | 1 | use crate::base::ExtCtxt; |
5e7ed085 FG |
2 | use crate::mbe::macro_parser::{MatchedNonterminal, MatchedSeq, MatchedTokenTree, NamedMatch}; |
3 | use crate::mbe::{self, MetaVarExpr}; | |
74b04a01 | 4 | use rustc_ast::mut_visit::{self, MutVisitor}; |
04454e1e | 5 | use rustc_ast::token::{self, Delimiter, Token, TokenKind}; |
064997fb | 6 | use rustc_ast::tokenstream::{DelimSpan, Spacing, TokenStream, TokenTree}; |
dfeec247 | 7 | use rustc_data_structures::fx::FxHashMap; |
ba9703b0 | 8 | use rustc_errors::{pluralize, PResult}; |
5e7ed085 | 9 | use rustc_errors::{DiagnosticBuilder, ErrorGuaranteed}; |
136023e0 | 10 | use rustc_span::hygiene::{LocalExpnId, Transparency}; |
5e7ed085 | 11 | use rustc_span::symbol::{sym, Ident, MacroRulesNormalizedIdent}; |
04454e1e | 12 | use rustc_span::Span; |
9fa01778 XL |
13 | |
14 | use smallvec::{smallvec, SmallVec}; | |
8bb4bdeb | 15 | use std::mem; |
223e47cc | 16 | |
e1599b0c | 17 | // A Marker adds the given mark to the syntax context. |
136023e0 | 18 | struct Marker(LocalExpnId, Transparency); |
e1599b0c XL |
19 | |
20 | impl MutVisitor for Marker { | |
3c0e092e | 21 | const VISIT_TOKENS: bool = true; |
e1599b0c | 22 | |
29967ef6 | 23 | fn visit_span(&mut self, span: &mut Span) { |
136023e0 | 24 | *span = span.apply_mark(self.0.to_expn_id(), self.1) |
e1599b0c XL |
25 | } |
26 | } | |
27 | ||
48663c56 | 28 | /// An iterator over the token trees in a delimited token tree (`{ ... }`) or a sequence (`$(...)`). |
04454e1e FG |
29 | enum Frame<'a> { |
30 | Delimited { tts: &'a [mbe::TokenTree], idx: usize, delim: Delimiter, span: DelimSpan }, | |
31 | Sequence { tts: &'a [mbe::TokenTree], idx: usize, sep: Option<Token> }, | |
1a4d82fc JJ |
32 | } |
33 | ||
04454e1e | 34 | impl<'a> Frame<'a> { |
48663c56 | 35 | /// Construct a new frame around the delimited set of tokens. |
04454e1e FG |
36 | fn new(src: &'a mbe::Delimited, span: DelimSpan) -> Frame<'a> { |
37 | Frame::Delimited { tts: &src.tts, idx: 0, delim: src.delim, span } | |
32a655c1 | 38 | } |
223e47cc LB |
39 | } |
40 | ||
04454e1e FG |
41 | impl<'a> Iterator for Frame<'a> { |
42 | type Item = &'a mbe::TokenTree; | |
1a4d82fc | 43 | |
04454e1e FG |
44 | fn next(&mut self) -> Option<&'a mbe::TokenTree> { |
45 | match self { | |
46 | Frame::Delimited { tts, ref mut idx, .. } | |
47 | | Frame::Sequence { tts, ref mut idx, .. } => { | |
48 | let res = tts.get(*idx); | |
8bb4bdeb | 49 | *idx += 1; |
5e7ed085 | 50 | res |
8bb4bdeb | 51 | } |
970d7e83 LB |
52 | } |
53 | } | |
223e47cc | 54 | } |
223e47cc | 55 | |
48663c56 XL |
56 | /// This can do Macro-By-Example transcription. |
57 | /// - `interp` is a map of meta-variables to the tokens (non-terminals) they matched in the | |
58 | /// invocation. We are assuming we already know there is a match. | |
59 | /// - `src` is the RHS of the MBE, that is, the "example" we are filling in. | |
60 | /// | |
61 | /// For example, | |
62 | /// | |
63 | /// ```rust | |
64 | /// macro_rules! foo { | |
65 | /// ($id:ident) => { println!("{}", stringify!($id)); } | |
66 | /// } | |
67 | /// | |
68 | /// foo!(bar); | |
69 | /// ``` | |
70 | /// | |
71 | /// `interp` would contain `$id => bar` and `src` would contain `println!("{}", stringify!($id));`. | |
72 | /// | |
73 | /// `transcribe` would return a `TokenStream` containing `println!("{}", stringify!(bar));`. | |
74 | /// | |
75 | /// Along the way, we do some additional error checking. | |
ba9703b0 XL |
76 | pub(super) fn transcribe<'a>( |
77 | cx: &ExtCtxt<'a>, | |
78 | interp: &FxHashMap<MacroRulesNormalizedIdent, NamedMatch>, | |
04454e1e FG |
79 | src: &mbe::Delimited, |
80 | src_span: DelimSpan, | |
e1599b0c | 81 | transparency: Transparency, |
ba9703b0 | 82 | ) -> PResult<'a, TokenStream> { |
48663c56 | 83 | // Nothing for us to transcribe... |
04454e1e | 84 | if src.tts.is_empty() { |
ba9703b0 | 85 | return Ok(TokenStream::default()); |
48663c56 XL |
86 | } |
87 | ||
88 | // We descend into the RHS (`src`), expanding things as we go. This stack contains the things | |
89 | // we have yet to expand/are still expanding. We start the stack off with the whole RHS. | |
04454e1e | 90 | let mut stack: SmallVec<[Frame<'_>; 1]> = smallvec![Frame::new(&src, src_span)]; |
48663c56 XL |
91 | |
92 | // As we descend in the RHS, we will need to be able to match nested sequences of matchers. | |
93 | // `repeats` keeps track of where we are in matching at each level, with the last element being | |
94 | // the most deeply nested sequence. This is used as a stack. | |
8bb4bdeb | 95 | let mut repeats = Vec::new(); |
48663c56 XL |
96 | |
97 | // `result` contains resulting token stream from the TokenTree we just finished processing. At | |
98 | // the end, this will contain the full result of transcription, but at arbitrary points during | |
99 | // `transcribe`, `result` will contain subsets of the final result. | |
100 | // | |
101 | // Specifically, as we descend into each TokenTree, we will push the existing results onto the | |
102 | // `result_stack` and clear `results`. We will then produce the results of transcribing the | |
103 | // TokenTree into `results`. Then, as we unwind back out of the `TokenTree`, we will pop the | |
104 | // `result_stack` and append `results` too it to produce the new `results` up to that point. | |
105 | // | |
106 | // Thus, if we try to pop the `result_stack` and it is empty, we have reached the top-level | |
107 | // again, and we are done transcribing. | |
064997fb | 108 | let mut result: Vec<TokenTree> = Vec::new(); |
8bb4bdeb | 109 | let mut result_stack = Vec::new(); |
e1599b0c | 110 | let mut marker = Marker(cx.current_expansion.id, transparency); |
223e47cc | 111 | |
223e47cc | 112 | loop { |
48663c56 | 113 | // Look at the last frame on the stack. |
3c0e092e XL |
114 | // If it still has a TokenTree we have not looked at yet, use that tree. |
115 | let Some(tree) = stack.last_mut().unwrap().next() else { | |
74b04a01 XL |
116 | // This else-case never produces a value for `tree` (it `continue`s or `return`s). |
117 | ||
48663c56 XL |
118 | // Otherwise, if we have just reached the end of a sequence and we can keep repeating, |
119 | // go back to the beginning of the sequence. | |
dc9dc135 XL |
120 | if let Frame::Sequence { idx, sep, .. } = stack.last_mut().unwrap() { |
121 | let (repeat_idx, repeat_len) = repeats.last_mut().unwrap(); | |
8bb4bdeb | 122 | *repeat_idx += 1; |
dc9dc135 | 123 | if repeat_idx < repeat_len { |
8bb4bdeb | 124 | *idx = 0; |
dc9dc135 | 125 | if let Some(sep) = sep { |
064997fb | 126 | result.push(TokenTree::Token(sep.clone(), Spacing::Alone)); |
8bb4bdeb | 127 | } |
48663c56 | 128 | continue; |
8bb4bdeb | 129 | } |
1a4d82fc | 130 | } |
8bb4bdeb | 131 | |
48663c56 XL |
132 | // We are done with the top of the stack. Pop it. Depending on what it was, we do |
133 | // different things. Note that the outermost item must be the delimited, wrapped RHS | |
134 | // that was passed in originally to `transcribe`. | |
8bb4bdeb | 135 | match stack.pop().unwrap() { |
48663c56 | 136 | // Done with a sequence. Pop from repeats. |
8bb4bdeb XL |
137 | Frame::Sequence { .. } => { |
138 | repeats.pop(); | |
139 | } | |
48663c56 XL |
140 | |
141 | // We are done processing a Delimited. If this is the top-level delimited, we are | |
142 | // done. Otherwise, we unwind the result_stack to append what we have produced to | |
143 | // any previous results. | |
04454e1e | 144 | Frame::Delimited { delim, span, .. } => { |
8bb4bdeb | 145 | if result_stack.is_empty() { |
48663c56 | 146 | // No results left to compute! We are back at the top-level. |
ba9703b0 | 147 | return Ok(TokenStream::new(result)); |
8bb4bdeb | 148 | } |
48663c56 XL |
149 | |
150 | // Step back into the parent Delimited. | |
04454e1e | 151 | let tree = TokenTree::Delimited(span, delim, TokenStream::new(result)); |
8bb4bdeb | 152 | result = result_stack.pop().unwrap(); |
064997fb | 153 | result.push(tree); |
8bb4bdeb | 154 | } |
223e47cc | 155 | } |
48663c56 | 156 | continue; |
1a4d82fc | 157 | }; |
8bb4bdeb | 158 | |
48663c56 XL |
159 | // At this point, we know we are in the middle of a TokenTree (the last one on `stack`). |
160 | // `tree` contains the next `TokenTree` to be processed. | |
8bb4bdeb | 161 | match tree { |
48663c56 XL |
162 | // We are descending into a sequence. We first make sure that the matchers in the RHS |
163 | // and the matches in `interp` have the same shape. Otherwise, either the caller or the | |
164 | // macro writer has made a mistake. | |
04454e1e | 165 | seq @ mbe::TokenTree::Sequence(_, delimited) => { |
48663c56 | 166 | match lockstep_iter_size(&seq, interp, &repeats) { |
8bb4bdeb | 167 | LockstepIterSize::Unconstrained => { |
ba9703b0 | 168 | return Err(cx.struct_span_err( |
48663c56 XL |
169 | seq.span(), /* blame macro writer */ |
170 | "attempted to repeat an expression containing no syntax variables \ | |
171 | matched as repeating at this depth", | |
ba9703b0 | 172 | )); |
1a4d82fc | 173 | } |
48663c56 | 174 | |
a2a8927a | 175 | LockstepIterSize::Contradiction(msg) => { |
48663c56 XL |
176 | // FIXME: this really ought to be caught at macro definition time... It |
177 | // happens when two meta-variables are used in the same repetition in a | |
178 | // sequence, but they come from different sequence matchers and repeat | |
179 | // different amounts. | |
a2a8927a | 180 | return Err(cx.struct_span_err(seq.span(), &msg)); |
1a4d82fc | 181 | } |
48663c56 | 182 | |
8bb4bdeb | 183 | LockstepIterSize::Constraint(len, _) => { |
48663c56 XL |
184 | // We do this to avoid an extra clone above. We know that this is a |
185 | // sequence already. | |
3c0e092e | 186 | let mbe::TokenTree::Sequence(sp, seq) = seq else { |
48663c56 XL |
187 | unreachable!() |
188 | }; | |
189 | ||
190 | // Is the repetition empty? | |
1a4d82fc | 191 | if len == 0 { |
e74abb32 | 192 | if seq.kleene.op == mbe::KleeneOp::OneOrMore { |
48663c56 XL |
193 | // FIXME: this really ought to be caught at macro definition |
194 | // time... It happens when the Kleene operator in the matcher and | |
195 | // the body for the same meta-variable do not match. | |
ba9703b0 XL |
196 | return Err(cx.struct_span_err( |
197 | sp.entire(), | |
198 | "this must repeat at least once", | |
199 | )); | |
1a4d82fc | 200 | } |
8bb4bdeb | 201 | } else { |
5e7ed085 | 202 | // 0 is the initial counter (we have done 0 repetitions so far). `len` |
cdc7bbd5 | 203 | // is the total number of repetitions we should generate. |
8bb4bdeb | 204 | repeats.push((0, len)); |
48663c56 XL |
205 | |
206 | // The first time we encounter the sequence we push it to the stack. It | |
207 | // then gets reused (see the beginning of the loop) until we are done | |
208 | // repeating. | |
8bb4bdeb XL |
209 | stack.push(Frame::Sequence { |
210 | idx: 0, | |
211 | sep: seq.separator.clone(), | |
04454e1e | 212 | tts: &delimited.tts, |
8bb4bdeb | 213 | }); |
1a4d82fc | 214 | } |
1a4d82fc | 215 | } |
223e47cc | 216 | } |
223e47cc | 217 | } |
48663c56 XL |
218 | |
219 | // Replace the meta-var with the matched token tree from the invocation. | |
04454e1e | 220 | mbe::TokenTree::MetaVar(mut sp, mut original_ident) => { |
48663c56 XL |
221 | // Find the matched nonterminal from the macro invocation, and use it to replace |
222 | // the meta-var. | |
04454e1e | 223 | let ident = MacroRulesNormalizedIdent::new(original_ident); |
48663c56 | 224 | if let Some(cur_matched) = lookup_cur_matched(ident, interp, &repeats) { |
5e7ed085 FG |
225 | match cur_matched { |
226 | MatchedTokenTree(ref tt) => { | |
29967ef6 XL |
227 | // `tt`s are emitted into the output stream directly as "raw tokens", |
228 | // without wrapping them into groups. | |
5e7ed085 | 229 | let token = tt.clone(); |
064997fb | 230 | result.push(token); |
5e7ed085 FG |
231 | } |
232 | MatchedNonterminal(ref nt) => { | |
29967ef6 | 233 | // Other variables are emitted into the output stream as groups with |
04454e1e | 234 | // `Delimiter::Invisible` to maintain parsing priorities. |
5e7ed085 | 235 | // `Interpolated` is currently used for such groups in rustc parser. |
e1599b0c | 236 | marker.visit_span(&mut sp); |
064997fb FG |
237 | let token = TokenTree::token_alone(token::Interpolated(nt.clone()), sp); |
238 | result.push(token); | |
5e7ed085 FG |
239 | } |
240 | MatchedSeq(..) => { | |
241 | // We were unable to descend far enough. This is an error. | |
242 | return Err(cx.struct_span_err( | |
243 | sp, /* blame the macro writer */ | |
244 | &format!("variable '{}' is still repeating at this depth", ident), | |
245 | )); | |
246 | } | |
1a4d82fc | 247 | } |
041b39d2 | 248 | } else { |
48663c56 XL |
249 | // If we aren't able to match the meta-var, we push it back into the result but |
250 | // with modified syntax context. (I believe this supports nested macros). | |
e1599b0c | 251 | marker.visit_span(&mut sp); |
04454e1e | 252 | marker.visit_ident(&mut original_ident); |
064997fb FG |
253 | result.push(TokenTree::token_alone(token::Dollar, sp)); |
254 | result.push(TokenTree::Token( | |
255 | Token::from_ast_ident(original_ident), | |
256 | Spacing::Alone, | |
257 | )); | |
1a4d82fc JJ |
258 | } |
259 | } | |
48663c56 | 260 | |
5e7ed085 FG |
261 | // Replace meta-variable expressions with the result of their expansion. |
262 | mbe::TokenTree::MetaVarExpr(sp, expr) => { | |
263 | transcribe_metavar_expr(cx, expr, interp, &mut marker, &repeats, &mut result, &sp)?; | |
264 | } | |
265 | ||
48663c56 XL |
266 | // If we are entering a new delimiter, we push its contents to the `stack` to be |
267 | // processed, and we push all of the currently produced results to the `result_stack`. | |
268 | // We will produce all of the results of the inside of the `Delimited` and then we will | |
269 | // jump back out of the Delimited, pop the result_stack and add the new results back to | |
270 | // the previous results (from outside the Delimited). | |
e74abb32 | 271 | mbe::TokenTree::Delimited(mut span, delimited) => { |
60c5eb7d | 272 | mut_visit::visit_delim_span(&mut span, &mut marker); |
04454e1e FG |
273 | stack.push(Frame::Delimited { |
274 | tts: &delimited.tts, | |
275 | delim: delimited.delim, | |
276 | idx: 0, | |
277 | span, | |
278 | }); | |
416331ca | 279 | result_stack.push(mem::take(&mut result)); |
1a4d82fc | 280 | } |
48663c56 XL |
281 | |
282 | // Nothing much to do here. Just push the token to the result, being careful to | |
283 | // preserve syntax context. | |
e74abb32 | 284 | mbe::TokenTree::Token(token) => { |
04454e1e FG |
285 | let mut token = token.clone(); |
286 | mut_visit::visit_token(&mut token, &mut marker); | |
064997fb FG |
287 | let tt = TokenTree::Token(token, Spacing::Alone); |
288 | result.push(tt); | |
041b39d2 | 289 | } |
48663c56 XL |
290 | |
291 | // There should be no meta-var declarations in the invocation of a macro. | |
e74abb32 | 292 | mbe::TokenTree::MetaVarDecl(..) => panic!("unexpected `TokenTree::MetaVarDecl"), |
8bb4bdeb XL |
293 | } |
294 | } | |
295 | } | |
296 | ||
48663c56 XL |
297 | /// Lookup the meta-var named `ident` and return the matched token tree from the invocation using |
298 | /// the set of matches `interpolations`. | |
299 | /// | |
300 | /// See the definition of `repeats` in the `transcribe` function. `repeats` is used to descend | |
301 | /// into the right place in nested matchers. If we attempt to descend too far, the macro writer has | |
302 | /// made a mistake, and we return `None`. | |
416331ca | 303 | fn lookup_cur_matched<'a>( |
ba9703b0 XL |
304 | ident: MacroRulesNormalizedIdent, |
305 | interpolations: &'a FxHashMap<MacroRulesNormalizedIdent, NamedMatch>, | |
48663c56 | 306 | repeats: &[(usize, usize)], |
416331ca | 307 | ) -> Option<&'a NamedMatch> { |
8bb4bdeb | 308 | interpolations.get(&ident).map(|matched| { |
416331ca | 309 | let mut matched = matched; |
041b39d2 | 310 | for &(idx, _) in repeats { |
416331ca | 311 | match matched { |
5e7ed085 | 312 | MatchedTokenTree(_) | MatchedNonterminal(_) => break, |
60c5eb7d | 313 | MatchedSeq(ref ads) => matched = ads.get(idx).unwrap(), |
223e47cc | 314 | } |
041b39d2 XL |
315 | } |
316 | ||
317 | matched | |
8bb4bdeb XL |
318 | }) |
319 | } | |
320 | ||
48663c56 XL |
321 | /// An accumulator over a TokenTree to be used with `fold`. During transcription, we need to make |
322 | /// sure that the size of each sequence and all of its nested sequences are the same as the sizes | |
323 | /// of all the matched (nested) sequences in the macro invocation. If they don't match, somebody | |
324 | /// has made a mistake (either the macro writer or caller). | |
8bb4bdeb XL |
325 | #[derive(Clone)] |
326 | enum LockstepIterSize { | |
48663c56 XL |
327 | /// No constraints on length of matcher. This is true for any TokenTree variants except a |
328 | /// `MetaVar` with an actual `MatchedSeq` (as opposed to a `MatchedNonterminal`). | |
8bb4bdeb | 329 | Unconstrained, |
48663c56 XL |
330 | |
331 | /// A `MetaVar` with an actual `MatchedSeq`. The length of the match and the name of the | |
332 | /// meta-var are returned. | |
ba9703b0 | 333 | Constraint(usize, MacroRulesNormalizedIdent), |
48663c56 XL |
334 | |
335 | /// Two `Constraint`s on the same sequence had different lengths. This is an error. | |
8bb4bdeb XL |
336 | Contradiction(String), |
337 | } | |
338 | ||
48663c56 XL |
339 | impl LockstepIterSize { |
340 | /// Find incompatibilities in matcher/invocation sizes. | |
341 | /// - `Unconstrained` is compatible with everything. | |
342 | /// - `Contradiction` is incompatible with everything. | |
343 | /// - `Constraint(len)` is only compatible with other constraints of the same length. | |
344 | fn with(self, other: LockstepIterSize) -> LockstepIterSize { | |
8bb4bdeb XL |
345 | match self { |
346 | LockstepIterSize::Unconstrained => other, | |
347 | LockstepIterSize::Contradiction(_) => self, | |
348 | LockstepIterSize::Constraint(l_len, ref l_id) => match other { | |
0bf4aa26 | 349 | LockstepIterSize::Unconstrained => self, |
8bb4bdeb | 350 | LockstepIterSize::Contradiction(_) => other, |
0bf4aa26 | 351 | LockstepIterSize::Constraint(r_len, _) if l_len == r_len => self, |
8bb4bdeb | 352 | LockstepIterSize::Constraint(r_len, r_id) => { |
48663c56 | 353 | let msg = format!( |
e1599b0c XL |
354 | "meta-variable `{}` repeats {} time{}, but `{}` repeats {} time{}", |
355 | l_id, | |
356 | l_len, | |
60c5eb7d | 357 | pluralize!(l_len), |
e1599b0c XL |
358 | r_id, |
359 | r_len, | |
60c5eb7d | 360 | pluralize!(r_len), |
48663c56 | 361 | ); |
8bb4bdeb XL |
362 | LockstepIterSize::Contradiction(msg) |
363 | } | |
364 | }, | |
223e47cc LB |
365 | } |
366 | } | |
223e47cc | 367 | } |
8bb4bdeb | 368 | |
48663c56 XL |
369 | /// Given a `tree`, make sure that all sequences have the same length as the matches for the |
370 | /// appropriate meta-vars in `interpolations`. | |
371 | /// | |
372 | /// Note that if `repeats` does not match the exact correct depth of a meta-var, | |
cdc7bbd5 | 373 | /// `lookup_cur_matched` will return `None`, which is why this still works even in the presence of |
48663c56 | 374 | /// multiple nested matcher sequences. |
5e7ed085 FG |
375 | /// |
376 | /// Example: `$($($x $y)+*);+` -- we need to make sure that `x` and `y` repeat the same amount as | |
377 | /// each other at the given depth when the macro was invoked. If they don't it might mean they were | |
378 | /// declared at unequal depths or there was a compile bug. For example, if we have 3 repetitions of | |
379 | /// the outer sequence and 4 repetitions of the inner sequence for `x`, we should have the same for | |
380 | /// `y`; otherwise, we can't transcribe them both at the given depth. | |
48663c56 | 381 | fn lockstep_iter_size( |
e74abb32 | 382 | tree: &mbe::TokenTree, |
ba9703b0 | 383 | interpolations: &FxHashMap<MacroRulesNormalizedIdent, NamedMatch>, |
48663c56 XL |
384 | repeats: &[(usize, usize)], |
385 | ) -> LockstepIterSize { | |
e74abb32 | 386 | use mbe::TokenTree; |
8bb4bdeb | 387 | match *tree { |
5e7ed085 | 388 | TokenTree::Delimited(_, ref delimited) => { |
04454e1e | 389 | delimited.tts.iter().fold(LockstepIterSize::Unconstrained, |size, tt| { |
48663c56 | 390 | size.with(lockstep_iter_size(tt, interpolations, repeats)) |
8bb4bdeb | 391 | }) |
48663c56 | 392 | } |
8bb4bdeb XL |
393 | TokenTree::Sequence(_, ref seq) => { |
394 | seq.tts.iter().fold(LockstepIterSize::Unconstrained, |size, tt| { | |
48663c56 | 395 | size.with(lockstep_iter_size(tt, interpolations, repeats)) |
8bb4bdeb | 396 | }) |
48663c56 XL |
397 | } |
398 | TokenTree::MetaVar(_, name) | TokenTree::MetaVarDecl(_, name, _) => { | |
ba9703b0 | 399 | let name = MacroRulesNormalizedIdent::new(name); |
8bb4bdeb | 400 | match lookup_cur_matched(name, interpolations, repeats) { |
416331ca | 401 | Some(matched) => match matched { |
5e7ed085 | 402 | MatchedTokenTree(_) | MatchedNonterminal(_) => LockstepIterSize::Unconstrained, |
60c5eb7d | 403 | MatchedSeq(ref ads) => LockstepIterSize::Constraint(ads.len(), name), |
8bb4bdeb | 404 | }, |
48663c56 XL |
405 | _ => LockstepIterSize::Unconstrained, |
406 | } | |
407 | } | |
5e7ed085 FG |
408 | TokenTree::MetaVarExpr(_, ref expr) => { |
409 | let default_rslt = LockstepIterSize::Unconstrained; | |
410 | let Some(ident) = expr.ident() else { return default_rslt; }; | |
411 | let name = MacroRulesNormalizedIdent::new(ident); | |
412 | match lookup_cur_matched(name, interpolations, repeats) { | |
413 | Some(MatchedSeq(ref ads)) => { | |
414 | default_rslt.with(LockstepIterSize::Constraint(ads.len(), name)) | |
415 | } | |
416 | _ => default_rslt, | |
417 | } | |
418 | } | |
8bb4bdeb XL |
419 | TokenTree::Token(..) => LockstepIterSize::Unconstrained, |
420 | } | |
421 | } | |
5e7ed085 FG |
422 | |
423 | /// Used solely by the `count` meta-variable expression, counts the outer-most repetitions at a | |
424 | /// given optional nested depth. | |
425 | /// | |
426 | /// For example, a macro parameter of `$( { $( $foo:ident ),* } )*` called with `{ a, b } { c }`: | |
427 | /// | |
428 | /// * `[ $( ${count(foo)} ),* ]` will return [2, 1] with a, b = 2 and c = 1 | |
429 | /// * `[ $( ${count(foo, 0)} ),* ]` will be the same as `[ $( ${count(foo)} ),* ]` | |
430 | /// * `[ $( ${count(foo, 1)} ),* ]` will return an error because `${count(foo, 1)}` is | |
431 | /// declared inside a single repetition and the index `1` implies two nested repetitions. | |
432 | fn count_repetitions<'a>( | |
433 | cx: &ExtCtxt<'a>, | |
434 | depth_opt: Option<usize>, | |
435 | mut matched: &NamedMatch, | |
436 | repeats: &[(usize, usize)], | |
437 | sp: &DelimSpan, | |
438 | ) -> PResult<'a, usize> { | |
439 | // Recursively count the number of matches in `matched` at given depth | |
440 | // (or at the top-level of `matched` if no depth is given). | |
441 | fn count<'a>( | |
442 | cx: &ExtCtxt<'a>, | |
443 | declared_lhs_depth: usize, | |
444 | depth_opt: Option<usize>, | |
445 | matched: &NamedMatch, | |
446 | sp: &DelimSpan, | |
447 | ) -> PResult<'a, usize> { | |
448 | match matched { | |
449 | MatchedTokenTree(_) | MatchedNonterminal(_) => { | |
450 | if declared_lhs_depth == 0 { | |
451 | return Err(cx.struct_span_err( | |
452 | sp.entire(), | |
453 | "`count` can not be placed inside the inner-most repetition", | |
454 | )); | |
455 | } | |
456 | match depth_opt { | |
457 | None => Ok(1), | |
458 | Some(_) => Err(out_of_bounds_err(cx, declared_lhs_depth, sp.entire(), "count")), | |
459 | } | |
460 | } | |
461 | MatchedSeq(ref named_matches) => { | |
462 | let new_declared_lhs_depth = declared_lhs_depth + 1; | |
463 | match depth_opt { | |
464 | None => named_matches | |
465 | .iter() | |
466 | .map(|elem| count(cx, new_declared_lhs_depth, None, elem, sp)) | |
467 | .sum(), | |
468 | Some(0) => Ok(named_matches.len()), | |
469 | Some(depth) => named_matches | |
470 | .iter() | |
471 | .map(|elem| count(cx, new_declared_lhs_depth, Some(depth - 1), elem, sp)) | |
472 | .sum(), | |
473 | } | |
474 | } | |
475 | } | |
476 | } | |
477 | // `repeats` records all of the nested levels at which we are currently | |
478 | // matching meta-variables. The meta-var-expr `count($x)` only counts | |
479 | // matches that occur in this "subtree" of the `NamedMatch` where we | |
480 | // are currently transcribing, so we need to descend to that subtree | |
481 | // before we start counting. `matched` contains the various levels of the | |
482 | // tree as we descend, and its final value is the subtree we are currently at. | |
483 | for &(idx, _) in repeats { | |
484 | if let MatchedSeq(ref ads) = matched { | |
485 | matched = &ads[idx]; | |
486 | } | |
487 | } | |
488 | count(cx, 0, depth_opt, matched, sp) | |
489 | } | |
490 | ||
491 | /// Returns a `NamedMatch` item declared on the LHS given an arbitrary [Ident] | |
492 | fn matched_from_ident<'ctx, 'interp, 'rslt>( | |
493 | cx: &ExtCtxt<'ctx>, | |
494 | ident: Ident, | |
495 | interp: &'interp FxHashMap<MacroRulesNormalizedIdent, NamedMatch>, | |
496 | ) -> PResult<'ctx, &'rslt NamedMatch> | |
497 | where | |
498 | 'interp: 'rslt, | |
499 | { | |
500 | let span = ident.span; | |
501 | let key = MacroRulesNormalizedIdent::new(ident); | |
502 | interp.get(&key).ok_or_else(|| { | |
503 | cx.struct_span_err( | |
504 | span, | |
505 | &format!("variable `{}` is not recognized in meta-variable expression", key), | |
506 | ) | |
507 | }) | |
508 | } | |
509 | ||
510 | /// Used by meta-variable expressions when an user input is out of the actual declared bounds. For | |
511 | /// example, index(999999) in an repetition of only three elements. | |
512 | fn out_of_bounds_err<'a>( | |
513 | cx: &ExtCtxt<'a>, | |
514 | max: usize, | |
515 | span: Span, | |
516 | ty: &str, | |
517 | ) -> DiagnosticBuilder<'a, ErrorGuaranteed> { | |
064997fb FG |
518 | let msg = if max == 0 { |
519 | format!( | |
520 | "meta-variable expression `{ty}` with depth parameter \ | |
521 | must be called inside of a macro repetition" | |
522 | ) | |
523 | } else { | |
524 | format!( | |
525 | "depth parameter on meta-variable expression `{ty}` \ | |
526 | must be less than {max}" | |
527 | ) | |
528 | }; | |
529 | cx.struct_span_err(span, &msg) | |
5e7ed085 FG |
530 | } |
531 | ||
532 | fn transcribe_metavar_expr<'a>( | |
533 | cx: &ExtCtxt<'a>, | |
04454e1e | 534 | expr: &MetaVarExpr, |
5e7ed085 FG |
535 | interp: &FxHashMap<MacroRulesNormalizedIdent, NamedMatch>, |
536 | marker: &mut Marker, | |
537 | repeats: &[(usize, usize)], | |
064997fb | 538 | result: &mut Vec<TokenTree>, |
5e7ed085 FG |
539 | sp: &DelimSpan, |
540 | ) -> PResult<'a, ()> { | |
541 | let mut visited_span = || { | |
542 | let mut span = sp.entire(); | |
543 | marker.visit_span(&mut span); | |
544 | span | |
545 | }; | |
04454e1e | 546 | match *expr { |
5e7ed085 FG |
547 | MetaVarExpr::Count(original_ident, depth_opt) => { |
548 | let matched = matched_from_ident(cx, original_ident, interp)?; | |
549 | let count = count_repetitions(cx, depth_opt, matched, &repeats, sp)?; | |
064997fb | 550 | let tt = TokenTree::token_alone( |
5e7ed085 FG |
551 | TokenKind::lit(token::Integer, sym::integer(count), None), |
552 | visited_span(), | |
553 | ); | |
064997fb | 554 | result.push(tt); |
5e7ed085 FG |
555 | } |
556 | MetaVarExpr::Ignore(original_ident) => { | |
557 | // Used to ensure that `original_ident` is present in the LHS | |
558 | let _ = matched_from_ident(cx, original_ident, interp)?; | |
559 | } | |
560 | MetaVarExpr::Index(depth) => match repeats.iter().nth_back(depth) { | |
561 | Some((index, _)) => { | |
064997fb FG |
562 | result.push(TokenTree::token_alone( |
563 | TokenKind::lit(token::Integer, sym::integer(*index), None), | |
564 | visited_span(), | |
565 | )); | |
5e7ed085 FG |
566 | } |
567 | None => return Err(out_of_bounds_err(cx, repeats.len(), sp.entire(), "index")), | |
568 | }, | |
569 | MetaVarExpr::Length(depth) => match repeats.iter().nth_back(depth) { | |
570 | Some((_, length)) => { | |
064997fb FG |
571 | result.push(TokenTree::token_alone( |
572 | TokenKind::lit(token::Integer, sym::integer(*length), None), | |
573 | visited_span(), | |
574 | )); | |
5e7ed085 FG |
575 | } |
576 | None => return Err(out_of_bounds_err(cx, repeats.len(), sp.entire(), "length")), | |
577 | }, | |
578 | } | |
579 | Ok(()) | |
580 | } |