rustc_expand/mbe/
macro_check.rs

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
108use std::iter;
109
110use rustc_ast::token::{Delimiter, IdentIsRaw, Token, TokenKind};
111use rustc_ast::{DUMMY_NODE_ID, NodeId};
112use rustc_data_structures::fx::FxHashMap;
113use rustc_errors::MultiSpan;
114use rustc_lint_defs::BuiltinLintDiag;
115use rustc_session::lint::builtin::{META_VARIABLE_MISUSE, MISSING_FRAGMENT_SPECIFIER};
116use rustc_session::parse::ParseSess;
117use rustc_span::edition::Edition;
118use rustc_span::{ErrorGuaranteed, MacroRulesNormalizedIdent, Span, kw};
119use smallvec::SmallVec;
120
121use super::quoted::VALID_FRAGMENT_NAMES_MSG;
122use crate::errors;
123use crate::mbe::{KleeneToken, TokenTree};
124
125/// Stack represented as linked list.
126///
127/// Those are used for environments because they grow incrementally and are not mutable.
128enum Stack<'a, T> {
129    /// Empty stack.
130    Empty,
131    /// A non-empty stack.
132    Push {
133        /// The top element.
134        top: T,
135        /// The previous elements.
136        prev: &'a Stack<'a, T>,
137    },
138}
139
140impl<'a, T> Stack<'a, T> {
141    /// Returns whether a stack is empty.
142    fn is_empty(&self) -> bool {
143        matches!(*self, Stack::Empty)
144    }
145
146    /// Returns a new stack with an element of top.
147    fn push(&'a self, top: T) -> Stack<'a, T> {
148        Stack::Push { top, prev: self }
149    }
150}
151
152impl<'a, T> Iterator for &'a Stack<'a, T> {
153    type Item = &'a T;
154
155    // Iterates from top to bottom of the stack.
156    fn next(&mut self) -> Option<&'a T> {
157        match self {
158            Stack::Empty => None,
159            Stack::Push { top, prev } => {
160                *self = prev;
161                Some(top)
162            }
163        }
164    }
165}
166
167impl From<&Stack<'_, KleeneToken>> for SmallVec<[KleeneToken; 1]> {
168    fn from(ops: &Stack<'_, KleeneToken>) -> SmallVec<[KleeneToken; 1]> {
169        let mut ops: SmallVec<[KleeneToken; 1]> = ops.cloned().collect();
170        // The stack is innermost on top. We want outermost first.
171        ops.reverse();
172        ops
173    }
174}
175
176/// Information attached to a meta-variable binder in LHS.
177struct BinderInfo {
178    /// The span of the meta-variable in LHS.
179    span: Span,
180    /// The stack of Kleene operators (outermost first).
181    ops: SmallVec<[KleeneToken; 1]>,
182}
183
184/// An environment of meta-variables to their binder information.
185type Binders = FxHashMap<MacroRulesNormalizedIdent, BinderInfo>;
186
187/// The state at which we entered a macro definition in the RHS of another macro definition.
188struct MacroState<'a> {
189    /// The binders of the branch where we entered the macro definition.
190    binders: &'a Binders,
191    /// The stack of Kleene operators (outermost first) where we entered the macro definition.
192    ops: SmallVec<[KleeneToken; 1]>,
193}
194
195/// Checks that meta-variables are used correctly in a macro definition.
196///
197/// Arguments:
198/// - `psess` is used to emit diagnostics and lints
199/// - `node_id` is used to emit lints
200/// - `span` is used when no spans are available
201/// - `lhses` and `rhses` should have the same length and represent the macro definition
202pub(super) fn check_meta_variables(
203    psess: &ParseSess,
204    node_id: NodeId,
205    span: Span,
206    lhses: &[TokenTree],
207    rhses: &[TokenTree],
208) -> Result<(), ErrorGuaranteed> {
209    if lhses.len() != rhses.len() {
210        psess.dcx().span_bug(span, "length mismatch between LHSes and RHSes")
211    }
212    let mut guar = None;
213    for (lhs, rhs) in iter::zip(lhses, rhses) {
214        let mut binders = Binders::default();
215        check_binders(psess, node_id, lhs, &Stack::Empty, &mut binders, &Stack::Empty, &mut guar);
216        check_occurrences(psess, node_id, rhs, &Stack::Empty, &binders, &Stack::Empty, &mut guar);
217    }
218    guar.map_or(Ok(()), Err)
219}
220
221/// Checks `lhs` as part of the LHS of a macro definition, extends `binders` with new binders, and
222/// sets `valid` to false in case of errors.
223///
224/// Arguments:
225/// - `psess` is used to emit diagnostics and lints
226/// - `node_id` is used to emit lints
227/// - `lhs` is checked as part of a LHS
228/// - `macros` is the stack of possible outer macros
229/// - `binders` contains the binders of the LHS
230/// - `ops` is the stack of Kleene operators from the LHS
231/// - `guar` is set in case of errors
232fn check_binders(
233    psess: &ParseSess,
234    node_id: NodeId,
235    lhs: &TokenTree,
236    macros: &Stack<'_, MacroState<'_>>,
237    binders: &mut Binders,
238    ops: &Stack<'_, KleeneToken>,
239    guar: &mut Option<ErrorGuaranteed>,
240) {
241    match *lhs {
242        TokenTree::Token(..) => {}
243        // This can only happen when checking a nested macro because this LHS is then in the RHS of
244        // the outer macro. See ui/macros/macro-of-higher-order.rs where $y:$fragment in the
245        // LHS of the nested macro (and RHS of the outer macro) is parsed as MetaVar(y) Colon
246        // MetaVar(fragment) and not as MetaVarDecl(y, fragment).
247        TokenTree::MetaVar(span, name) => {
248            if macros.is_empty() {
249                psess.dcx().span_bug(span, "unexpected MetaVar in lhs");
250            }
251            let name = MacroRulesNormalizedIdent::new(name);
252            // There are 3 possibilities:
253            if let Some(prev_info) = binders.get(&name) {
254                // 1. The meta-variable is already bound in the current LHS: This is an error.
255                let mut span = MultiSpan::from_span(span);
256                span.push_span_label(prev_info.span, "previous declaration");
257                buffer_lint(psess, span, node_id, BuiltinLintDiag::DuplicateMatcherBinding);
258            } else if get_binder_info(macros, binders, name).is_none() {
259                // 2. The meta-variable is free: This is a binder.
260                binders.insert(name, BinderInfo { span, ops: ops.into() });
261            } else {
262                // 3. The meta-variable is bound: This is an occurrence.
263                check_occurrences(psess, node_id, lhs, macros, binders, ops, guar);
264            }
265        }
266        // Similarly, this can only happen when checking a toplevel macro.
267        TokenTree::MetaVarDecl(span, name, kind) => {
268            if kind.is_none() && node_id != DUMMY_NODE_ID {
269                // FIXME: Report this as a hard error eventually and remove equivalent errors from
270                // `parse_tt_inner` and `nameize`. Until then the error may be reported twice, once
271                // as a hard error and then once as a buffered lint.
272                if span.edition() >= Edition::Edition2024 {
273                    psess.dcx().emit_err(errors::MissingFragmentSpecifier {
274                        span,
275                        add_span: span.shrink_to_hi(),
276                        valid: VALID_FRAGMENT_NAMES_MSG,
277                    });
278                } else {
279                    psess.buffer_lint(
280                        MISSING_FRAGMENT_SPECIFIER,
281                        span,
282                        node_id,
283                        BuiltinLintDiag::MissingFragmentSpecifier,
284                    );
285                }
286            }
287            if !macros.is_empty() {
288                psess.dcx().span_bug(span, "unexpected MetaVarDecl in nested lhs");
289            }
290            let name = MacroRulesNormalizedIdent::new(name);
291            if let Some(prev_info) = get_binder_info(macros, binders, name) {
292                // Duplicate binders at the top-level macro definition are errors. The lint is only
293                // for nested macro definitions.
294                *guar = Some(
295                    psess
296                        .dcx()
297                        .emit_err(errors::DuplicateMatcherBinding { span, prev: prev_info.span }),
298                );
299            } else {
300                binders.insert(name, BinderInfo { span, ops: ops.into() });
301            }
302        }
303        // `MetaVarExpr` can not appear in the LHS of a macro arm
304        TokenTree::MetaVarExpr(..) => {}
305        TokenTree::Delimited(.., ref del) => {
306            for tt in &del.tts {
307                check_binders(psess, node_id, tt, macros, binders, ops, guar);
308            }
309        }
310        TokenTree::Sequence(_, ref seq) => {
311            let ops = ops.push(seq.kleene);
312            for tt in &seq.tts {
313                check_binders(psess, node_id, tt, macros, binders, &ops, guar);
314            }
315        }
316    }
317}
318
319/// Returns the binder information of a meta-variable.
320///
321/// Arguments:
322/// - `macros` is the stack of possible outer macros
323/// - `binders` contains the current binders
324/// - `name` is the name of the meta-variable we are looking for
325fn get_binder_info<'a>(
326    mut macros: &'a Stack<'a, MacroState<'a>>,
327    binders: &'a Binders,
328    name: MacroRulesNormalizedIdent,
329) -> Option<&'a BinderInfo> {
330    binders.get(&name).or_else(|| macros.find_map(|state| state.binders.get(&name)))
331}
332
333/// Checks `rhs` as part of the RHS of a macro definition and sets `valid` to false in case of
334/// errors.
335///
336/// Arguments:
337/// - `psess` is used to emit diagnostics and lints
338/// - `node_id` is used to emit lints
339/// - `rhs` is checked as part of a RHS
340/// - `macros` is the stack of possible outer macros
341/// - `binders` contains the binders of the associated LHS
342/// - `ops` is the stack of Kleene operators from the RHS
343/// - `guar` is set in case of errors
344fn check_occurrences(
345    psess: &ParseSess,
346    node_id: NodeId,
347    rhs: &TokenTree,
348    macros: &Stack<'_, MacroState<'_>>,
349    binders: &Binders,
350    ops: &Stack<'_, KleeneToken>,
351    guar: &mut Option<ErrorGuaranteed>,
352) {
353    match *rhs {
354        TokenTree::Token(..) => {}
355        TokenTree::MetaVarDecl(span, _name, _kind) => {
356            psess.dcx().span_bug(span, "unexpected MetaVarDecl in rhs")
357        }
358        TokenTree::MetaVar(span, name) => {
359            let name = MacroRulesNormalizedIdent::new(name);
360            check_ops_is_prefix(psess, node_id, macros, binders, ops, span, name);
361        }
362        TokenTree::MetaVarExpr(dl, ref mve) => {
363            mve.for_each_metavar((), |_, ident| {
364                let name = MacroRulesNormalizedIdent::new(*ident);
365                check_ops_is_prefix(psess, node_id, macros, binders, ops, dl.entire(), name);
366            });
367        }
368        TokenTree::Delimited(.., ref del) => {
369            check_nested_occurrences(psess, node_id, &del.tts, macros, binders, ops, guar);
370        }
371        TokenTree::Sequence(_, ref seq) => {
372            let ops = ops.push(seq.kleene);
373            check_nested_occurrences(psess, node_id, &seq.tts, macros, binders, &ops, guar);
374        }
375    }
376}
377
378/// Represents the processed prefix of a nested macro.
379#[derive(Clone, Copy, PartialEq, Eq)]
380enum NestedMacroState {
381    /// Nothing that matches a nested macro definition was processed yet.
382    Empty,
383    /// The token `macro_rules` was processed.
384    MacroRules,
385    /// The tokens `macro_rules!` were processed.
386    MacroRulesNot,
387    /// The tokens `macro_rules!` followed by a name were processed. The name may be either directly
388    /// an identifier or a meta-variable (that hopefully would be instantiated by an identifier).
389    MacroRulesNotName,
390    /// The keyword `macro` was processed.
391    Macro,
392    /// The keyword `macro` followed by a name was processed.
393    MacroName,
394    /// The keyword `macro` followed by a name and a token delimited by parentheses was processed.
395    MacroNameParen,
396}
397
398/// Checks `tts` as part of the RHS of a macro definition, tries to recognize nested macro
399/// definitions, and sets `valid` to false in case of errors.
400///
401/// Arguments:
402/// - `psess` is used to emit diagnostics and lints
403/// - `node_id` is used to emit lints
404/// - `tts` is checked as part of a RHS and may contain macro definitions
405/// - `macros` is the stack of possible outer macros
406/// - `binders` contains the binders of the associated LHS
407/// - `ops` is the stack of Kleene operators from the RHS
408/// - `guar` is set in case of errors
409fn check_nested_occurrences(
410    psess: &ParseSess,
411    node_id: NodeId,
412    tts: &[TokenTree],
413    macros: &Stack<'_, MacroState<'_>>,
414    binders: &Binders,
415    ops: &Stack<'_, KleeneToken>,
416    guar: &mut Option<ErrorGuaranteed>,
417) {
418    let mut state = NestedMacroState::Empty;
419    let nested_macros = macros.push(MacroState { binders, ops: ops.into() });
420    let mut nested_binders = Binders::default();
421    for tt in tts {
422        match (state, tt) {
423            (
424                NestedMacroState::Empty,
425                &TokenTree::Token(Token { kind: TokenKind::Ident(name, IdentIsRaw::No), .. }),
426            ) => {
427                if name == kw::MacroRules {
428                    state = NestedMacroState::MacroRules;
429                } else if name == kw::Macro {
430                    state = NestedMacroState::Macro;
431                }
432            }
433            (
434                NestedMacroState::MacroRules,
435                &TokenTree::Token(Token { kind: TokenKind::Not, .. }),
436            ) => {
437                state = NestedMacroState::MacroRulesNot;
438            }
439            (
440                NestedMacroState::MacroRulesNot,
441                &TokenTree::Token(Token { kind: TokenKind::Ident(..), .. }),
442            ) => {
443                state = NestedMacroState::MacroRulesNotName;
444            }
445            (NestedMacroState::MacroRulesNot, &TokenTree::MetaVar(..)) => {
446                state = NestedMacroState::MacroRulesNotName;
447                // We check that the meta-variable is correctly used.
448                check_occurrences(psess, node_id, tt, macros, binders, ops, guar);
449            }
450            (NestedMacroState::MacroRulesNotName, TokenTree::Delimited(.., del))
451            | (NestedMacroState::MacroName, TokenTree::Delimited(.., del))
452                if del.delim == Delimiter::Brace =>
453            {
454                let macro_rules = state == NestedMacroState::MacroRulesNotName;
455                state = NestedMacroState::Empty;
456                let rest =
457                    check_nested_macro(psess, node_id, macro_rules, &del.tts, &nested_macros, guar);
458                // If we did not check the whole macro definition, then check the rest as if outside
459                // the macro definition.
460                check_nested_occurrences(
461                    psess,
462                    node_id,
463                    &del.tts[rest..],
464                    macros,
465                    binders,
466                    ops,
467                    guar,
468                );
469            }
470            (
471                NestedMacroState::Macro,
472                &TokenTree::Token(Token { kind: TokenKind::Ident(..), .. }),
473            ) => {
474                state = NestedMacroState::MacroName;
475            }
476            (NestedMacroState::Macro, &TokenTree::MetaVar(..)) => {
477                state = NestedMacroState::MacroName;
478                // We check that the meta-variable is correctly used.
479                check_occurrences(psess, node_id, tt, macros, binders, ops, guar);
480            }
481            (NestedMacroState::MacroName, TokenTree::Delimited(.., del))
482                if del.delim == Delimiter::Parenthesis =>
483            {
484                state = NestedMacroState::MacroNameParen;
485                nested_binders = Binders::default();
486                check_binders(
487                    psess,
488                    node_id,
489                    tt,
490                    &nested_macros,
491                    &mut nested_binders,
492                    &Stack::Empty,
493                    guar,
494                );
495            }
496            (NestedMacroState::MacroNameParen, TokenTree::Delimited(.., del))
497                if del.delim == Delimiter::Brace =>
498            {
499                state = NestedMacroState::Empty;
500                check_occurrences(
501                    psess,
502                    node_id,
503                    tt,
504                    &nested_macros,
505                    &nested_binders,
506                    &Stack::Empty,
507                    guar,
508                );
509            }
510            (_, tt) => {
511                state = NestedMacroState::Empty;
512                check_occurrences(psess, node_id, tt, macros, binders, ops, guar);
513            }
514        }
515    }
516}
517
518/// Checks the body of nested macro, returns where the check stopped, and sets `valid` to false in
519/// case of errors.
520///
521/// The token trees are checked as long as they look like a list of (LHS) => {RHS} token trees. This
522/// check is a best-effort to detect a macro definition. It returns the position in `tts` where we
523/// stopped checking because we detected we were not in a macro definition anymore.
524///
525/// Arguments:
526/// - `psess` is used to emit diagnostics and lints
527/// - `node_id` is used to emit lints
528/// - `macro_rules` specifies whether the macro is `macro_rules`
529/// - `tts` is checked as a list of (LHS) => {RHS}
530/// - `macros` is the stack of outer macros
531/// - `guar` is set in case of errors
532fn check_nested_macro(
533    psess: &ParseSess,
534    node_id: NodeId,
535    macro_rules: bool,
536    tts: &[TokenTree],
537    macros: &Stack<'_, MacroState<'_>>,
538    guar: &mut Option<ErrorGuaranteed>,
539) -> usize {
540    let n = tts.len();
541    let mut i = 0;
542    let separator = if macro_rules { TokenKind::Semi } else { TokenKind::Comma };
543    loop {
544        // We expect 3 token trees: `(LHS) => {RHS}`. The separator is checked after.
545        if i + 2 >= n
546            || !tts[i].is_delimited()
547            || !tts[i + 1].is_token(&TokenKind::FatArrow)
548            || !tts[i + 2].is_delimited()
549        {
550            break;
551        }
552        let lhs = &tts[i];
553        let rhs = &tts[i + 2];
554        let mut binders = Binders::default();
555        check_binders(psess, node_id, lhs, macros, &mut binders, &Stack::Empty, guar);
556        check_occurrences(psess, node_id, rhs, macros, &binders, &Stack::Empty, guar);
557        // Since the last semicolon is optional for `macro_rules` macros and decl_macro are not terminated,
558        // we increment our checked position by how many token trees we already checked (the 3
559        // above) before checking for the separator.
560        i += 3;
561        if i == n || !tts[i].is_token(&separator) {
562            break;
563        }
564        // We increment our checked position for the semicolon.
565        i += 1;
566    }
567    i
568}
569
570/// Checks that a meta-variable occurrence is valid.
571///
572/// Arguments:
573/// - `psess` is used to emit diagnostics and lints
574/// - `node_id` is used to emit lints
575/// - `macros` is the stack of possible outer macros
576/// - `binders` contains the binders of the associated LHS
577/// - `ops` is the stack of Kleene operators from the RHS
578/// - `span` is the span of the meta-variable to check
579/// - `name` is the name of the meta-variable to check
580fn check_ops_is_prefix(
581    psess: &ParseSess,
582    node_id: NodeId,
583    macros: &Stack<'_, MacroState<'_>>,
584    binders: &Binders,
585    ops: &Stack<'_, KleeneToken>,
586    span: Span,
587    name: MacroRulesNormalizedIdent,
588) {
589    let macros = macros.push(MacroState { binders, ops: ops.into() });
590    // Accumulates the stacks the operators of each state until (and including when) the
591    // meta-variable is found. The innermost stack is first.
592    let mut acc: SmallVec<[&SmallVec<[KleeneToken; 1]>; 1]> = SmallVec::new();
593    for state in &macros {
594        acc.push(&state.ops);
595        if let Some(binder) = state.binders.get(&name) {
596            // This variable concatenates the stack of operators from the RHS of the LHS where the
597            // meta-variable was defined to where it is used (in possibly nested macros). The
598            // outermost operator is first.
599            let mut occurrence_ops: SmallVec<[KleeneToken; 2]> = SmallVec::new();
600            // We need to iterate from the end to start with outermost stack.
601            for ops in acc.iter().rev() {
602                occurrence_ops.extend_from_slice(ops);
603            }
604            ops_is_prefix(psess, node_id, span, name, &binder.ops, &occurrence_ops);
605            return;
606        }
607    }
608    buffer_lint(psess, span.into(), node_id, BuiltinLintDiag::UnknownMacroVariable(name));
609}
610
611/// Returns whether `binder_ops` is a prefix of `occurrence_ops`.
612///
613/// The stack of Kleene operators of a meta-variable occurrence just needs to have the stack of
614/// Kleene operators of its binder as a prefix.
615///
616/// Consider $i in the following example:
617/// ```ignore (illustrative)
618/// ( $( $i:ident = $($j:ident),+ );* ) => { $($( $i += $j; )+)* }
619/// ```
620/// It occurs under the Kleene stack ["*", "+"] and is bound under ["*"] only.
621///
622/// Arguments:
623/// - `psess` is used to emit diagnostics and lints
624/// - `node_id` is used to emit lints
625/// - `span` is the span of the meta-variable being check
626/// - `name` is the name of the meta-variable being check
627/// - `binder_ops` is the stack of Kleene operators for the binder
628/// - `occurrence_ops` is the stack of Kleene operators for the occurrence
629fn ops_is_prefix(
630    psess: &ParseSess,
631    node_id: NodeId,
632    span: Span,
633    name: MacroRulesNormalizedIdent,
634    binder_ops: &[KleeneToken],
635    occurrence_ops: &[KleeneToken],
636) {
637    for (i, binder) in binder_ops.iter().enumerate() {
638        if i >= occurrence_ops.len() {
639            let mut span = MultiSpan::from_span(span);
640            span.push_span_label(binder.span, "expected repetition");
641            buffer_lint(psess, span, node_id, BuiltinLintDiag::MetaVariableStillRepeating(name));
642            return;
643        }
644        let occurrence = &occurrence_ops[i];
645        if occurrence.op != binder.op {
646            let mut span = MultiSpan::from_span(span);
647            span.push_span_label(binder.span, "expected repetition");
648            span.push_span_label(occurrence.span, "conflicting repetition");
649            buffer_lint(psess, span, node_id, BuiltinLintDiag::MetaVariableWrongOperator);
650            return;
651        }
652    }
653}
654
655fn buffer_lint(psess: &ParseSess, span: MultiSpan, node_id: NodeId, diag: BuiltinLintDiag) {
656    // Macros loaded from other crates have dummy node ids.
657    if node_id != DUMMY_NODE_ID {
658        psess.buffer_lint(META_VARIABLE_MISUSE, span, node_id, diag);
659    }
660}