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