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