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