1 //! Checks that meta-variables in macro definition are correctly declared and used.
2 //!
3 //! # What is checked
4 //!
5 //! ## Meta-variables must not be bound twice
6 //!
7 //! ```compile_fail
8 //! macro_rules! foo { ($x:tt $x:tt) => { $x }; }
9 //! ```
10 //!
11 //! This check is sound (no false-negative) and complete (no false-positive).
12 //!
13 //! ## Meta-variables must not be free
14 //!
15 //! ```
16 //! macro_rules! foo { () => { $x }; }
17 //! ```
18 //!
19 //! This check is also done at macro instantiation but only if the branch is taken.
20 //!
21 //! ## Meta-variables must repeat at least as many times as their binder
22 //!
23 //! ```
24 //! macro_rules! foo { ($($x:tt)*) => { $x }; }
25 //! ```
26 //!
27 //! This check is also done at macro instantiation but only if the branch is taken.
28 //!
29 //! ## Meta-variables must repeat with the same Kleene operators as their binder
30 //!
31 //! ```
32 //! macro_rules! foo { ($($x:tt)+) => { $($x)* }; }
33 //! ```
34 //!
35 //! This check is not done at macro instantiation.
36 //!
37 //! # Disclaimer
38 //!
39 //! In the presence of nested macros (a macro defined in a macro), those checks may have false
40 //! positives and false negatives. We try to detect those cases by recognizing potential macro
41 //! definitions in RHSes, but nested macros may be hidden through the use of particular values of
42 //! meta-variables.
43 //!
44 //! ## Examples of false positive
45 //!
46 //! False positives can come from cases where we don't recognize a nested macro, because it depends
47 //! on particular values of meta-variables. In the following example, we think both instances of
48 //! `$x` are free, which is a correct statement if `$name` is anything but `macro_rules`. But when
49 //! `$name` is `macro_rules`, like in the instantiation below, then `$x:tt` is actually a binder of
50 //! the nested macro and `$x` is bound to it.
51 //!
52 //! ```
53 //! macro_rules! foo { ($name:ident) => { $name! bar { ($x:tt) => { $x }; } }; }
54 //! foo!(macro_rules);
55 //! ```
56 //!
57 //! False positives can also come from cases where we think there is a nested macro while there
58 //! isn't. In the following example, we think `$x` is free, which is incorrect because `bar` is not
59 //! a nested macro since it is not evaluated as code by `stringify!`.
60 //!
61 //! ```
62 //! macro_rules! foo { () => { stringify!(macro_rules! bar { () => { $x }; }) }; }
63 //! ```
64 //!
65 //! ## Examples of false negative
66 //!
67 //! False negatives can come from cases where we don't recognize a meta-variable, because it depends
68 //! on particular values of meta-variables. In the following examples, we don't see that if `$d` is
69 //! instantiated with `$` then `$d z` becomes `$z` in the nested macro definition and is thus a free
70 //! meta-variable. Note however, that if `foo` is instantiated, then we would check the definition
71 //! of `bar` and would see the issue.
72 //!
73 //! ```
74 //! macro_rules! foo { ($d:tt) => { macro_rules! bar { ($y:tt) => { $d z }; } }; }
75 //! ```
76 //!
77 //! # How it is checked
78 //!
79 //! There are 3 main functions: `check_binders`, `check_occurrences`, and `check_nested_macro`. They
80 //! all need some kind of environment.
81 //!
82 //! ## Environments
83 //!
84 //! Environments are used to pass information.
85 //!
86 //! ### From LHS to RHS
87 //!
88 //! When checking a LHS with `check_binders`, we produce (and use) an environment for binders,
89 //! namely `Binders`. This is a mapping from binder name to information about that binder: the span
90 //! of the binder for error messages and the stack of Kleene operators under which it was bound in
91 //! the LHS.
92 //!
93 //! This environment is used by both the LHS and RHS. The LHS uses it to detect duplicate binders.
94 //! The RHS uses it to detect the other errors.
95 //!
96 //! ### From outer macro to inner macro
97 //!
98 //! When checking the RHS of an outer macro and we detect a nested macro definition, we push the
99 //! current state, namely `MacroState`, to an environment of nested macro definitions. Each state
100 //! stores the LHS binders when entering the macro definition as well as the stack of Kleene
101 //! operators under which the inner macro is defined in the RHS.
102 //!
103 //! This environment is a stack representing the nesting of macro definitions. As such, the stack of
104 //! Kleene operators under which a meta-variable is repeating is the concatenation of the stacks
105 //! stored when entering a macro definition starting from the state in which the meta-variable is
106 //! bound.
107 use crate::errors;
108 use crate::mbe::{KleeneToken, TokenTree};
109
110 use rustc_ast::token::{Delimiter, Token, TokenKind};
111 use rustc_ast::{NodeId, DUMMY_NODE_ID};
112 use rustc_data_structures::fx::FxHashMap;
113 use rustc_errors::{DiagnosticMessage, MultiSpan};
114 use rustc_session::lint::builtin::{META_VARIABLE_MISUSE, MISSING_FRAGMENT_SPECIFIER};
115 use rustc_session::parse::ParseSess;
116 use rustc_span::symbol::kw;
117 use rustc_span::{symbol::MacroRulesNormalizedIdent, Span};
118
119 use smallvec::SmallVec;
120
121 use std::iter;
122
123 /// Stack represented as linked list.
124 ///
125 /// Those are used for environments because they grow incrementally and are not mutable.
126 enum Stack<'a, T> {
127 /// Empty stack.
128 Empty,
129 /// A non-empty stack.
130 Push {
131 /// The top element.
132 top: T,
133 /// The previous elements.
134 prev: &'a Stack<'a, T>,
135 },
136 }
137
138 impl<'a, T> Stack<'a, T> {
139 /// Returns whether a stack is empty.
is_empty(&self) -> bool140 fn is_empty(&self) -> bool {
141 matches!(*self, Stack::Empty)
142 }
143
144 /// Returns a new stack with an element of top.
push(&'a self, top: T) -> Stack<'a, T>145 fn push(&'a self, top: T) -> Stack<'a, T> {
146 Stack::Push { top, prev: self }
147 }
148 }
149
150 impl<'a, T> Iterator for &'a Stack<'a, T> {
151 type Item = &'a T;
152
153 // Iterates from top to bottom of the stack.
next(&mut self) -> Option<&'a T>154 fn next(&mut self) -> Option<&'a T> {
155 match self {
156 Stack::Empty => None,
157 Stack::Push { top, prev } => {
158 *self = prev;
159 Some(top)
160 }
161 }
162 }
163 }
164
165 impl From<&Stack<'_, KleeneToken>> for SmallVec<[KleeneToken; 1]> {
from(ops: &Stack<'_, KleeneToken>) -> SmallVec<[KleeneToken; 1]>166 fn from(ops: &Stack<'_, KleeneToken>) -> SmallVec<[KleeneToken; 1]> {
167 let mut ops: SmallVec<[KleeneToken; 1]> = ops.cloned().collect();
168 // The stack is innermost on top. We want outermost first.
169 ops.reverse();
170 ops
171 }
172 }
173
174 /// Information attached to a meta-variable binder in LHS.
175 struct BinderInfo {
176 /// The span of the meta-variable in LHS.
177 span: Span,
178 /// The stack of Kleene operators (outermost first).
179 ops: SmallVec<[KleeneToken; 1]>,
180 }
181
182 /// An environment of meta-variables to their binder information.
183 type Binders = FxHashMap<MacroRulesNormalizedIdent, BinderInfo>;
184
185 /// The state at which we entered a macro definition in the RHS of another macro definition.
186 struct MacroState<'a> {
187 /// The binders of the branch where we entered the macro definition.
188 binders: &'a Binders,
189 /// The stack of Kleene operators (outermost first) where we entered the macro definition.
190 ops: SmallVec<[KleeneToken; 1]>,
191 }
192
193 /// Checks that meta-variables are used correctly in a macro definition.
194 ///
195 /// Arguments:
196 /// - `sess` is used to emit diagnostics and lints
197 /// - `node_id` is used to emit lints
198 /// - `span` is used when no spans are available
199 /// - `lhses` and `rhses` should have the same length and represent the macro definition
check_meta_variables( sess: &ParseSess, node_id: NodeId, span: Span, lhses: &[TokenTree], rhses: &[TokenTree], ) -> bool200 pub(super) fn check_meta_variables(
201 sess: &ParseSess,
202 node_id: NodeId,
203 span: Span,
204 lhses: &[TokenTree],
205 rhses: &[TokenTree],
206 ) -> bool {
207 if lhses.len() != rhses.len() {
208 sess.span_diagnostic.span_bug(span, "length mismatch between LHSes and RHSes")
209 }
210 let mut valid = true;
211 for (lhs, rhs) in iter::zip(lhses, rhses) {
212 let mut binders = Binders::default();
213 check_binders(sess, node_id, lhs, &Stack::Empty, &mut binders, &Stack::Empty, &mut valid);
214 check_occurrences(sess, node_id, rhs, &Stack::Empty, &binders, &Stack::Empty, &mut valid);
215 }
216 valid
217 }
218
219 /// Checks `lhs` as part of the LHS of a macro definition, extends `binders` with new binders, and
220 /// sets `valid` to false in case of errors.
221 ///
222 /// Arguments:
223 /// - `sess` is used to emit diagnostics and lints
224 /// - `node_id` is used to emit lints
225 /// - `lhs` is checked as part of a LHS
226 /// - `macros` is the stack of possible outer macros
227 /// - `binders` contains the binders of the LHS
228 /// - `ops` is the stack of Kleene operators from the LHS
229 /// - `valid` is set in case of errors
check_binders( sess: &ParseSess, node_id: NodeId, lhs: &TokenTree, macros: &Stack<'_, MacroState<'_>>, binders: &mut Binders, ops: &Stack<'_, KleeneToken>, valid: &mut bool, )230 fn check_binders(
231 sess: &ParseSess,
232 node_id: NodeId,
233 lhs: &TokenTree,
234 macros: &Stack<'_, MacroState<'_>>,
235 binders: &mut Binders,
236 ops: &Stack<'_, KleeneToken>,
237 valid: &mut bool,
238 ) {
239 match *lhs {
240 TokenTree::Token(..) => {}
241 // This can only happen when checking a nested macro because this LHS is then in the RHS of
242 // the outer macro. See ui/macros/macro-of-higher-order.rs where $y:$fragment in the
243 // LHS of the nested macro (and RHS of the outer macro) is parsed as MetaVar(y) Colon
244 // MetaVar(fragment) and not as MetaVarDecl(y, fragment).
245 TokenTree::MetaVar(span, name) => {
246 if macros.is_empty() {
247 sess.span_diagnostic.span_bug(span, "unexpected MetaVar in lhs");
248 }
249 let name = MacroRulesNormalizedIdent::new(name);
250 // There are 3 possibilities:
251 if let Some(prev_info) = binders.get(&name) {
252 // 1. The meta-variable is already bound in the current LHS: This is an error.
253 let mut span = MultiSpan::from_span(span);
254 span.push_span_label(prev_info.span, "previous declaration");
255 buffer_lint(sess, span, node_id, "duplicate matcher binding");
256 } else if get_binder_info(macros, binders, name).is_none() {
257 // 2. The meta-variable is free: This is a binder.
258 binders.insert(name, BinderInfo { span, ops: ops.into() });
259 } else {
260 // 3. The meta-variable is bound: This is an occurrence.
261 check_occurrences(sess, node_id, lhs, macros, binders, ops, valid);
262 }
263 }
264 // Similarly, this can only happen when checking a toplevel macro.
265 TokenTree::MetaVarDecl(span, name, kind) => {
266 if kind.is_none() && node_id != DUMMY_NODE_ID {
267 // FIXME: Report this as a hard error eventually and remove equivalent errors from
268 // `parse_tt_inner` and `nameize`. Until then the error may be reported twice, once
269 // as a hard error and then once as a buffered lint.
270 sess.buffer_lint(
271 MISSING_FRAGMENT_SPECIFIER,
272 span,
273 node_id,
274 "missing fragment specifier",
275 );
276 }
277 if !macros.is_empty() {
278 sess.span_diagnostic.span_bug(span, "unexpected MetaVarDecl in nested lhs");
279 }
280 let name = MacroRulesNormalizedIdent::new(name);
281 if let Some(prev_info) = get_binder_info(macros, binders, name) {
282 // Duplicate binders at the top-level macro definition are errors. The lint is only
283 // for nested macro definitions.
284 sess.span_diagnostic
285 .emit_err(errors::DuplicateMatcherBinding { span, prev: prev_info.span });
286 *valid = false;
287 } else {
288 binders.insert(name, BinderInfo { span, ops: ops.into() });
289 }
290 }
291 // `MetaVarExpr` can not appear in the LHS of a macro arm
292 TokenTree::MetaVarExpr(..) => {}
293 TokenTree::Delimited(_, ref del) => {
294 for tt in &del.tts {
295 check_binders(sess, node_id, tt, macros, binders, ops, valid);
296 }
297 }
298 TokenTree::Sequence(_, ref seq) => {
299 let ops = ops.push(seq.kleene);
300 for tt in &seq.tts {
301 check_binders(sess, node_id, tt, macros, binders, &ops, valid);
302 }
303 }
304 }
305 }
306
307 /// Returns the binder information of a meta-variable.
308 ///
309 /// Arguments:
310 /// - `macros` is the stack of possible outer macros
311 /// - `binders` contains the current binders
312 /// - `name` is the name of the meta-variable we are looking for
get_binder_info<'a>( mut macros: &'a Stack<'a, MacroState<'a>>, binders: &'a Binders, name: MacroRulesNormalizedIdent, ) -> Option<&'a BinderInfo>313 fn get_binder_info<'a>(
314 mut macros: &'a Stack<'a, MacroState<'a>>,
315 binders: &'a Binders,
316 name: MacroRulesNormalizedIdent,
317 ) -> Option<&'a BinderInfo> {
318 binders.get(&name).or_else(|| macros.find_map(|state| state.binders.get(&name)))
319 }
320
321 /// Checks `rhs` as part of the RHS of a macro definition and sets `valid` to false in case of
322 /// errors.
323 ///
324 /// Arguments:
325 /// - `sess` is used to emit diagnostics and lints
326 /// - `node_id` is used to emit lints
327 /// - `rhs` is checked as part of a RHS
328 /// - `macros` is the stack of possible outer macros
329 /// - `binders` contains the binders of the associated LHS
330 /// - `ops` is the stack of Kleene operators from the RHS
331 /// - `valid` is set in case of errors
check_occurrences( sess: &ParseSess, node_id: NodeId, rhs: &TokenTree, macros: &Stack<'_, MacroState<'_>>, binders: &Binders, ops: &Stack<'_, KleeneToken>, valid: &mut bool, )332 fn check_occurrences(
333 sess: &ParseSess,
334 node_id: NodeId,
335 rhs: &TokenTree,
336 macros: &Stack<'_, MacroState<'_>>,
337 binders: &Binders,
338 ops: &Stack<'_, KleeneToken>,
339 valid: &mut bool,
340 ) {
341 match *rhs {
342 TokenTree::Token(..) => {}
343 TokenTree::MetaVarDecl(span, _name, _kind) => {
344 sess.span_diagnostic.span_bug(span, "unexpected MetaVarDecl in rhs")
345 }
346 TokenTree::MetaVar(span, name) => {
347 let name = MacroRulesNormalizedIdent::new(name);
348 check_ops_is_prefix(sess, node_id, macros, binders, ops, span, name);
349 }
350 TokenTree::MetaVarExpr(dl, ref mve) => {
351 let Some(name) = mve.ident().map(MacroRulesNormalizedIdent::new) else {
352 return;
353 };
354 check_ops_is_prefix(sess, node_id, macros, binders, ops, dl.entire(), name);
355 }
356 TokenTree::Delimited(_, ref del) => {
357 check_nested_occurrences(sess, node_id, &del.tts, macros, binders, ops, valid);
358 }
359 TokenTree::Sequence(_, ref seq) => {
360 let ops = ops.push(seq.kleene);
361 check_nested_occurrences(sess, node_id, &seq.tts, macros, binders, &ops, valid);
362 }
363 }
364 }
365
366 /// Represents the processed prefix of a nested macro.
367 #[derive(Clone, Copy, PartialEq, Eq)]
368 enum NestedMacroState {
369 /// Nothing that matches a nested macro definition was processed yet.
370 Empty,
371 /// The token `macro_rules` was processed.
372 MacroRules,
373 /// The tokens `macro_rules!` were processed.
374 MacroRulesNot,
375 /// The tokens `macro_rules!` followed by a name were processed. The name may be either directly
376 /// an identifier or a meta-variable (that hopefully would be instantiated by an identifier).
377 MacroRulesNotName,
378 /// The keyword `macro` was processed.
379 Macro,
380 /// The keyword `macro` followed by a name was processed.
381 MacroName,
382 /// The keyword `macro` followed by a name and a token delimited by parentheses was processed.
383 MacroNameParen,
384 }
385
386 /// Checks `tts` as part of the RHS of a macro definition, tries to recognize nested macro
387 /// definitions, and sets `valid` to false in case of errors.
388 ///
389 /// Arguments:
390 /// - `sess` is used to emit diagnostics and lints
391 /// - `node_id` is used to emit lints
392 /// - `tts` is checked as part of a RHS and may contain macro definitions
393 /// - `macros` is the stack of possible outer macros
394 /// - `binders` contains the binders of the associated LHS
395 /// - `ops` is the stack of Kleene operators from the RHS
396 /// - `valid` is set in case of errors
check_nested_occurrences( sess: &ParseSess, node_id: NodeId, tts: &[TokenTree], macros: &Stack<'_, MacroState<'_>>, binders: &Binders, ops: &Stack<'_, KleeneToken>, valid: &mut bool, )397 fn check_nested_occurrences(
398 sess: &ParseSess,
399 node_id: NodeId,
400 tts: &[TokenTree],
401 macros: &Stack<'_, MacroState<'_>>,
402 binders: &Binders,
403 ops: &Stack<'_, KleeneToken>,
404 valid: &mut bool,
405 ) {
406 let mut state = NestedMacroState::Empty;
407 let nested_macros = macros.push(MacroState { binders, ops: ops.into() });
408 let mut nested_binders = Binders::default();
409 for tt in tts {
410 match (state, tt) {
411 (
412 NestedMacroState::Empty,
413 &TokenTree::Token(Token { kind: TokenKind::Ident(name, false), .. }),
414 ) => {
415 if name == kw::MacroRules {
416 state = NestedMacroState::MacroRules;
417 } else if name == kw::Macro {
418 state = NestedMacroState::Macro;
419 }
420 }
421 (
422 NestedMacroState::MacroRules,
423 &TokenTree::Token(Token { kind: TokenKind::Not, .. }),
424 ) => {
425 state = NestedMacroState::MacroRulesNot;
426 }
427 (
428 NestedMacroState::MacroRulesNot,
429 &TokenTree::Token(Token { kind: TokenKind::Ident(..), .. }),
430 ) => {
431 state = NestedMacroState::MacroRulesNotName;
432 }
433 (NestedMacroState::MacroRulesNot, &TokenTree::MetaVar(..)) => {
434 state = NestedMacroState::MacroRulesNotName;
435 // We check that the meta-variable is correctly used.
436 check_occurrences(sess, node_id, tt, macros, binders, ops, valid);
437 }
438 (NestedMacroState::MacroRulesNotName, TokenTree::Delimited(_, del))
439 | (NestedMacroState::MacroName, TokenTree::Delimited(_, del))
440 if del.delim == Delimiter::Brace =>
441 {
442 let macro_rules = state == NestedMacroState::MacroRulesNotName;
443 state = NestedMacroState::Empty;
444 let rest =
445 check_nested_macro(sess, node_id, macro_rules, &del.tts, &nested_macros, valid);
446 // If we did not check the whole macro definition, then check the rest as if outside
447 // the macro definition.
448 check_nested_occurrences(
449 sess,
450 node_id,
451 &del.tts[rest..],
452 macros,
453 binders,
454 ops,
455 valid,
456 );
457 }
458 (
459 NestedMacroState::Macro,
460 &TokenTree::Token(Token { kind: TokenKind::Ident(..), .. }),
461 ) => {
462 state = NestedMacroState::MacroName;
463 }
464 (NestedMacroState::Macro, &TokenTree::MetaVar(..)) => {
465 state = NestedMacroState::MacroName;
466 // We check that the meta-variable is correctly used.
467 check_occurrences(sess, node_id, tt, macros, binders, ops, valid);
468 }
469 (NestedMacroState::MacroName, TokenTree::Delimited(_, del))
470 if del.delim == Delimiter::Parenthesis =>
471 {
472 state = NestedMacroState::MacroNameParen;
473 nested_binders = Binders::default();
474 check_binders(
475 sess,
476 node_id,
477 tt,
478 &nested_macros,
479 &mut nested_binders,
480 &Stack::Empty,
481 valid,
482 );
483 }
484 (NestedMacroState::MacroNameParen, TokenTree::Delimited(_, del))
485 if del.delim == Delimiter::Brace =>
486 {
487 state = NestedMacroState::Empty;
488 check_occurrences(
489 sess,
490 node_id,
491 tt,
492 &nested_macros,
493 &nested_binders,
494 &Stack::Empty,
495 valid,
496 );
497 }
498 (_, tt) => {
499 state = NestedMacroState::Empty;
500 check_occurrences(sess, node_id, tt, macros, binders, ops, valid);
501 }
502 }
503 }
504 }
505
506 /// Checks the body of nested macro, returns where the check stopped, and sets `valid` to false in
507 /// case of errors.
508 ///
509 /// The token trees are checked as long as they look like a list of (LHS) => {RHS} token trees. This
510 /// check is a best-effort to detect a macro definition. It returns the position in `tts` where we
511 /// stopped checking because we detected we were not in a macro definition anymore.
512 ///
513 /// Arguments:
514 /// - `sess` is used to emit diagnostics and lints
515 /// - `node_id` is used to emit lints
516 /// - `macro_rules` specifies whether the macro is `macro_rules`
517 /// - `tts` is checked as a list of (LHS) => {RHS}
518 /// - `macros` is the stack of outer macros
519 /// - `valid` is set in case of errors
check_nested_macro( sess: &ParseSess, node_id: NodeId, macro_rules: bool, tts: &[TokenTree], macros: &Stack<'_, MacroState<'_>>, valid: &mut bool, ) -> usize520 fn check_nested_macro(
521 sess: &ParseSess,
522 node_id: NodeId,
523 macro_rules: bool,
524 tts: &[TokenTree],
525 macros: &Stack<'_, MacroState<'_>>,
526 valid: &mut bool,
527 ) -> usize {
528 let n = tts.len();
529 let mut i = 0;
530 let separator = if macro_rules { TokenKind::Semi } else { TokenKind::Comma };
531 loop {
532 // We expect 3 token trees: `(LHS) => {RHS}`. The separator is checked after.
533 if i + 2 >= n
534 || !tts[i].is_delimited()
535 || !tts[i + 1].is_token(&TokenKind::FatArrow)
536 || !tts[i + 2].is_delimited()
537 {
538 break;
539 }
540 let lhs = &tts[i];
541 let rhs = &tts[i + 2];
542 let mut binders = Binders::default();
543 check_binders(sess, node_id, lhs, macros, &mut binders, &Stack::Empty, valid);
544 check_occurrences(sess, node_id, rhs, macros, &binders, &Stack::Empty, valid);
545 // Since the last semicolon is optional for `macro_rules` macros and decl_macro are not terminated,
546 // we increment our checked position by how many token trees we already checked (the 3
547 // above) before checking for the separator.
548 i += 3;
549 if i == n || !tts[i].is_token(&separator) {
550 break;
551 }
552 // We increment our checked position for the semicolon.
553 i += 1;
554 }
555 i
556 }
557
558 /// Checks that a meta-variable occurrence is valid.
559 ///
560 /// Arguments:
561 /// - `sess` is used to emit diagnostics and lints
562 /// - `node_id` is used to emit lints
563 /// - `macros` is the stack of possible outer macros
564 /// - `binders` contains the binders of the associated LHS
565 /// - `ops` is the stack of Kleene operators from the RHS
566 /// - `span` is the span of the meta-variable to check
567 /// - `name` is the name of the meta-variable to check
check_ops_is_prefix( sess: &ParseSess, node_id: NodeId, macros: &Stack<'_, MacroState<'_>>, binders: &Binders, ops: &Stack<'_, KleeneToken>, span: Span, name: MacroRulesNormalizedIdent, )568 fn check_ops_is_prefix(
569 sess: &ParseSess,
570 node_id: NodeId,
571 macros: &Stack<'_, MacroState<'_>>,
572 binders: &Binders,
573 ops: &Stack<'_, KleeneToken>,
574 span: Span,
575 name: MacroRulesNormalizedIdent,
576 ) {
577 let macros = macros.push(MacroState { binders, ops: ops.into() });
578 // Accumulates the stacks the operators of each state until (and including when) the
579 // meta-variable is found. The innermost stack is first.
580 let mut acc: SmallVec<[&SmallVec<[KleeneToken; 1]>; 1]> = SmallVec::new();
581 for state in ¯os {
582 acc.push(&state.ops);
583 if let Some(binder) = state.binders.get(&name) {
584 // This variable concatenates the stack of operators from the RHS of the LHS where the
585 // meta-variable was defined to where it is used (in possibly nested macros). The
586 // outermost operator is first.
587 let mut occurrence_ops: SmallVec<[KleeneToken; 2]> = SmallVec::new();
588 // We need to iterate from the end to start with outermost stack.
589 for ops in acc.iter().rev() {
590 occurrence_ops.extend_from_slice(ops);
591 }
592 ops_is_prefix(sess, node_id, span, name, &binder.ops, &occurrence_ops);
593 return;
594 }
595 }
596 buffer_lint(sess, span.into(), node_id, format!("unknown macro variable `{}`", name));
597 }
598
599 /// Returns whether `binder_ops` is a prefix of `occurrence_ops`.
600 ///
601 /// The stack of Kleene operators of a meta-variable occurrence just needs to have the stack of
602 /// Kleene operators of its binder as a prefix.
603 ///
604 /// Consider $i in the following example:
605 /// ```ignore (illustrative)
606 /// ( $( $i:ident = $($j:ident),+ );* ) => { $($( $i += $j; )+)* }
607 /// ```
608 /// It occurs under the Kleene stack ["*", "+"] and is bound under ["*"] only.
609 ///
610 /// Arguments:
611 /// - `sess` is used to emit diagnostics and lints
612 /// - `node_id` is used to emit lints
613 /// - `span` is the span of the meta-variable being check
614 /// - `name` is the name of the meta-variable being check
615 /// - `binder_ops` is the stack of Kleene operators for the binder
616 /// - `occurrence_ops` is the stack of Kleene operators for the occurrence
ops_is_prefix( sess: &ParseSess, node_id: NodeId, span: Span, name: MacroRulesNormalizedIdent, binder_ops: &[KleeneToken], occurrence_ops: &[KleeneToken], )617 fn ops_is_prefix(
618 sess: &ParseSess,
619 node_id: NodeId,
620 span: Span,
621 name: MacroRulesNormalizedIdent,
622 binder_ops: &[KleeneToken],
623 occurrence_ops: &[KleeneToken],
624 ) {
625 for (i, binder) in binder_ops.iter().enumerate() {
626 if i >= occurrence_ops.len() {
627 let mut span = MultiSpan::from_span(span);
628 span.push_span_label(binder.span, "expected repetition");
629 let message = format!("variable '{}' is still repeating at this depth", name);
630 buffer_lint(sess, span, node_id, message);
631 return;
632 }
633 let occurrence = &occurrence_ops[i];
634 if occurrence.op != binder.op {
635 let mut span = MultiSpan::from_span(span);
636 span.push_span_label(binder.span, "expected repetition");
637 span.push_span_label(occurrence.span, "conflicting repetition");
638 let message = "meta-variable repeats with different Kleene operator";
639 buffer_lint(sess, span, node_id, message);
640 return;
641 }
642 }
643 }
644
buffer_lint( sess: &ParseSess, span: MultiSpan, node_id: NodeId, message: impl Into<DiagnosticMessage>, )645 fn buffer_lint(
646 sess: &ParseSess,
647 span: MultiSpan,
648 node_id: NodeId,
649 message: impl Into<DiagnosticMessage>,
650 ) {
651 // Macros loaded from other crates have dummy node ids.
652 if node_id != DUMMY_NODE_ID {
653 sess.buffer_lint(&META_VARIABLE_MISUSE, span, node_id, message);
654 }
655 }
656