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