]> git.proxmox.com Git - rustc.git/blame - compiler/rustc_expand/src/mbe/macro_check.rs
New upstream version 1.61.0+dfsg1
[rustc.git] / compiler / rustc_expand / src / mbe / macro_check.rs
CommitLineData
416331ca
XL
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.
e74abb32
XL
107use crate::mbe::{KleeneToken, TokenTree};
108
74b04a01 109use rustc_ast::token::{DelimToken, Token, TokenKind};
3dfed10e 110use rustc_ast::{NodeId, DUMMY_NODE_ID};
dfeec247
XL
111use rustc_data_structures::fx::FxHashMap;
112use rustc_session::lint::builtin::META_VARIABLE_MISUSE;
113use rustc_session::parse::ParseSess;
74b04a01 114use rustc_span::symbol::kw;
ba9703b0 115use rustc_span::{symbol::MacroRulesNormalizedIdent, MultiSpan, Span};
416331ca 116
416331ca 117use smallvec::SmallVec;
416331ca 118
cdc7bbd5
XL
119use std::iter;
120
416331ca
XL
121/// Stack represented as linked list.
122///
123/// Those are used for environments because they grow incrementally and are not mutable.
124enum 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
136impl<'a, T> Stack<'a, T> {
137 /// Returns whether a stack is empty.
138 fn is_empty(&self) -> bool {
29967ef6 139 matches!(*self, Stack::Empty)
416331ca
XL
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
148impl<'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
163impl 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.
173struct 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.
ba9703b0 181type Binders = FxHashMap<MacroRulesNormalizedIdent, BinderInfo>;
416331ca
XL
182
183/// The state at which we entered a macro definition in the RHS of another macro definition.
184struct 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
e74abb32 198pub(super) fn check_meta_variables(
416331ca
XL
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;
cdc7bbd5 209 for (lhs, rhs) in iter::zip(lhses, rhses) {
416331ca
XL
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
228fn 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 }
ba9703b0 247 let name = MacroRulesNormalizedIdent::new(name);
416331ca
XL
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 }
ba9703b0 267 let name = MacroRulesNormalizedIdent::new(name);
416331ca
XL
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")
60c5eb7d
XL
273 .span_label(span, "duplicate binding")
274 .span_label(prev_info.span, "previous binding")
416331ca
XL
275 .emit();
276 *valid = false;
277 } else {
278 binders.insert(name, BinderInfo { span, ops: ops.into() });
279 }
280 }
5e7ed085
FG
281 // `MetaVarExpr` can not appear in the LHS of a macro arm
282 TokenTree::MetaVarExpr(..) => {}
416331ca 283 TokenTree::Delimited(_, ref del) => {
5e7ed085 284 for tt in del.inner_tts() {
416331ca
XL
285 check_binders(sess, node_id, tt, macros, binders, ops, valid);
286 }
287 }
288 TokenTree::Sequence(_, ref seq) => {
289 let ops = ops.push(seq.kleene);
290 for tt in &seq.tts {
291 check_binders(sess, node_id, tt, macros, binders, &ops, valid);
292 }
293 }
294 }
295}
296
297/// Returns the binder information of a meta-variable.
298///
299/// Arguments:
300/// - `macros` is the stack of possible outer macros
301/// - `binders` contains the current binders
302/// - `name` is the name of the meta-variable we are looking for
303fn get_binder_info<'a>(
304 mut macros: &'a Stack<'a, MacroState<'a>>,
305 binders: &'a Binders,
ba9703b0 306 name: MacroRulesNormalizedIdent,
416331ca
XL
307) -> Option<&'a BinderInfo> {
308 binders.get(&name).or_else(|| macros.find_map(|state| state.binders.get(&name)))
309}
310
311/// Checks `rhs` as part of the RHS of a macro definition and sets `valid` to false in case of
312/// errors.
313///
314/// Arguments:
315/// - `sess` is used to emit diagnostics and lints
316/// - `node_id` is used to emit lints
317/// - `rhs` is checked as part of a RHS
318/// - `macros` is the stack of possible outer macros
319/// - `binders` contains the binders of the associated LHS
320/// - `ops` is the stack of Kleene operators from the RHS
321/// - `valid` is set in case of errors
322fn check_occurrences(
323 sess: &ParseSess,
324 node_id: NodeId,
325 rhs: &TokenTree,
326 macros: &Stack<'_, MacroState<'_>>,
327 binders: &Binders,
328 ops: &Stack<'_, KleeneToken>,
329 valid: &mut bool,
330) {
331 match *rhs {
332 TokenTree::Token(..) => {}
333 TokenTree::MetaVarDecl(span, _name, _kind) => {
334 sess.span_diagnostic.span_bug(span, "unexpected MetaVarDecl in rhs")
335 }
336 TokenTree::MetaVar(span, name) => {
ba9703b0 337 let name = MacroRulesNormalizedIdent::new(name);
416331ca
XL
338 check_ops_is_prefix(sess, node_id, macros, binders, ops, span, name);
339 }
5e7ed085
FG
340 TokenTree::MetaVarExpr(dl, ref mve) => {
341 let Some(name) = mve.ident().map(MacroRulesNormalizedIdent::new) else {
342 return;
343 };
344 check_ops_is_prefix(sess, node_id, macros, binders, ops, dl.entire(), name);
345 }
416331ca 346 TokenTree::Delimited(_, ref del) => {
5e7ed085 347 check_nested_occurrences(sess, node_id, del.inner_tts(), macros, binders, ops, valid);
416331ca
XL
348 }
349 TokenTree::Sequence(_, ref seq) => {
350 let ops = ops.push(seq.kleene);
351 check_nested_occurrences(sess, node_id, &seq.tts, macros, binders, &ops, valid);
352 }
353 }
354}
355
356/// Represents the processed prefix of a nested macro.
357#[derive(Clone, Copy, PartialEq, Eq)]
358enum NestedMacroState {
359 /// Nothing that matches a nested macro definition was processed yet.
360 Empty,
361 /// The token `macro_rules` was processed.
362 MacroRules,
363 /// The tokens `macro_rules!` were processed.
364 MacroRulesNot,
365 /// The tokens `macro_rules!` followed by a name were processed. The name may be either directly
366 /// an identifier or a meta-variable (that hopefully would be instantiated by an identifier).
367 MacroRulesNotName,
368 /// The keyword `macro` was processed.
369 Macro,
370 /// The keyword `macro` followed by a name was processed.
371 MacroName,
372 /// The keyword `macro` followed by a name and a token delimited by parentheses was processed.
373 MacroNameParen,
374}
375
376/// Checks `tts` as part of the RHS of a macro definition, tries to recognize nested macro
377/// definitions, and sets `valid` to false in case of errors.
378///
379/// Arguments:
380/// - `sess` is used to emit diagnostics and lints
381/// - `node_id` is used to emit lints
382/// - `tts` is checked as part of a RHS and may contain macro definitions
383/// - `macros` is the stack of possible outer macros
384/// - `binders` contains the binders of the associated LHS
385/// - `ops` is the stack of Kleene operators from the RHS
386/// - `valid` is set in case of errors
387fn check_nested_occurrences(
388 sess: &ParseSess,
389 node_id: NodeId,
390 tts: &[TokenTree],
391 macros: &Stack<'_, MacroState<'_>>,
392 binders: &Binders,
393 ops: &Stack<'_, KleeneToken>,
394 valid: &mut bool,
395) {
396 let mut state = NestedMacroState::Empty;
397 let nested_macros = macros.push(MacroState { binders, ops: ops.into() });
398 let mut nested_binders = Binders::default();
399 for tt in tts {
400 match (state, tt) {
401 (
402 NestedMacroState::Empty,
403 &TokenTree::Token(Token { kind: TokenKind::Ident(name, false), .. }),
404 ) => {
74b04a01 405 if name == kw::MacroRules {
416331ca
XL
406 state = NestedMacroState::MacroRules;
407 } else if name == kw::Macro {
408 state = NestedMacroState::Macro;
409 }
410 }
411 (
412 NestedMacroState::MacroRules,
413 &TokenTree::Token(Token { kind: TokenKind::Not, .. }),
414 ) => {
415 state = NestedMacroState::MacroRulesNot;
416 }
417 (
418 NestedMacroState::MacroRulesNot,
419 &TokenTree::Token(Token { kind: TokenKind::Ident(..), .. }),
420 ) => {
421 state = NestedMacroState::MacroRulesNotName;
422 }
423 (NestedMacroState::MacroRulesNot, &TokenTree::MetaVar(..)) => {
424 state = NestedMacroState::MacroRulesNotName;
425 // We check that the meta-variable is correctly used.
426 check_occurrences(sess, node_id, tt, macros, binders, ops, valid);
427 }
428 (NestedMacroState::MacroRulesNotName, &TokenTree::Delimited(_, ref del))
429 | (NestedMacroState::MacroName, &TokenTree::Delimited(_, ref del))
430 if del.delim == DelimToken::Brace =>
431 {
ba9703b0 432 let macro_rules = state == NestedMacroState::MacroRulesNotName;
416331ca 433 state = NestedMacroState::Empty;
5e7ed085
FG
434 let rest = check_nested_macro(
435 sess,
436 node_id,
437 macro_rules,
438 del.inner_tts(),
439 &nested_macros,
440 valid,
441 );
416331ca
XL
442 // If we did not check the whole macro definition, then check the rest as if outside
443 // the macro definition.
444 check_nested_occurrences(
445 sess,
446 node_id,
5e7ed085 447 &del.inner_tts()[rest..],
416331ca
XL
448 macros,
449 binders,
450 ops,
451 valid,
452 );
453 }
454 (
455 NestedMacroState::Macro,
456 &TokenTree::Token(Token { kind: TokenKind::Ident(..), .. }),
457 ) => {
458 state = NestedMacroState::MacroName;
459 }
460 (NestedMacroState::Macro, &TokenTree::MetaVar(..)) => {
461 state = NestedMacroState::MacroName;
462 // We check that the meta-variable is correctly used.
463 check_occurrences(sess, node_id, tt, macros, binders, ops, valid);
464 }
465 (NestedMacroState::MacroName, &TokenTree::Delimited(_, ref del))
466 if del.delim == DelimToken::Paren =>
467 {
468 state = NestedMacroState::MacroNameParen;
469 nested_binders = Binders::default();
470 check_binders(
471 sess,
472 node_id,
473 tt,
474 &nested_macros,
475 &mut nested_binders,
476 &Stack::Empty,
477 valid,
478 );
479 }
480 (NestedMacroState::MacroNameParen, &TokenTree::Delimited(_, ref del))
481 if del.delim == DelimToken::Brace =>
482 {
483 state = NestedMacroState::Empty;
484 check_occurrences(
485 sess,
486 node_id,
487 tt,
488 &nested_macros,
489 &nested_binders,
490 &Stack::Empty,
491 valid,
492 );
493 }
494 (_, ref tt) => {
495 state = NestedMacroState::Empty;
496 check_occurrences(sess, node_id, tt, macros, binders, ops, valid);
497 }
498 }
499 }
500}
501
502/// Checks the body of nested macro, returns where the check stopped, and sets `valid` to false in
503/// case of errors.
504///
505/// The token trees are checked as long as they look like a list of (LHS) => {RHS} token trees. This
506/// check is a best-effort to detect a macro definition. It returns the position in `tts` where we
507/// stopped checking because we detected we were not in a macro definition anymore.
508///
509/// Arguments:
510/// - `sess` is used to emit diagnostics and lints
511/// - `node_id` is used to emit lints
ba9703b0 512/// - `macro_rules` specifies whether the macro is `macro_rules`
416331ca
XL
513/// - `tts` is checked as a list of (LHS) => {RHS}
514/// - `macros` is the stack of outer macros
515/// - `valid` is set in case of errors
516fn check_nested_macro(
517 sess: &ParseSess,
518 node_id: NodeId,
ba9703b0 519 macro_rules: bool,
416331ca
XL
520 tts: &[TokenTree],
521 macros: &Stack<'_, MacroState<'_>>,
522 valid: &mut bool,
523) -> usize {
524 let n = tts.len();
525 let mut i = 0;
ba9703b0 526 let separator = if macro_rules { TokenKind::Semi } else { TokenKind::Comma };
416331ca
XL
527 loop {
528 // We expect 3 token trees: `(LHS) => {RHS}`. The separator is checked after.
529 if i + 2 >= n
530 || !tts[i].is_delimited()
531 || !tts[i + 1].is_token(&TokenKind::FatArrow)
532 || !tts[i + 2].is_delimited()
533 {
534 break;
535 }
536 let lhs = &tts[i];
537 let rhs = &tts[i + 2];
538 let mut binders = Binders::default();
539 check_binders(sess, node_id, lhs, macros, &mut binders, &Stack::Empty, valid);
540 check_occurrences(sess, node_id, rhs, macros, &binders, &Stack::Empty, valid);
ba9703b0 541 // Since the last semicolon is optional for `macro_rules` macros and decl_macro are not terminated,
416331ca
XL
542 // we increment our checked position by how many token trees we already checked (the 3
543 // above) before checking for the separator.
544 i += 3;
545 if i == n || !tts[i].is_token(&separator) {
546 break;
547 }
548 // We increment our checked position for the semicolon.
549 i += 1;
550 }
551 i
552}
553
554/// Checks that a meta-variable occurrence is valid.
555///
556/// Arguments:
557/// - `sess` is used to emit diagnostics and lints
558/// - `node_id` is used to emit lints
559/// - `macros` is the stack of possible outer macros
560/// - `binders` contains the binders of the associated LHS
561/// - `ops` is the stack of Kleene operators from the RHS
562/// - `span` is the span of the meta-variable to check
563/// - `name` is the name of the meta-variable to check
564fn check_ops_is_prefix(
565 sess: &ParseSess,
566 node_id: NodeId,
567 macros: &Stack<'_, MacroState<'_>>,
568 binders: &Binders,
569 ops: &Stack<'_, KleeneToken>,
570 span: Span,
ba9703b0 571 name: MacroRulesNormalizedIdent,
416331ca
XL
572) {
573 let macros = macros.push(MacroState { binders, ops: ops.into() });
574 // Accumulates the stacks the operators of each state until (and including when) the
575 // meta-variable is found. The innermost stack is first.
576 let mut acc: SmallVec<[&SmallVec<[KleeneToken; 1]>; 1]> = SmallVec::new();
577 for state in &macros {
578 acc.push(&state.ops);
579 if let Some(binder) = state.binders.get(&name) {
580 // This variable concatenates the stack of operators from the RHS of the LHS where the
581 // meta-variable was defined to where it is used (in possibly nested macros). The
582 // outermost operator is first.
583 let mut occurrence_ops: SmallVec<[KleeneToken; 2]> = SmallVec::new();
584 // We need to iterate from the end to start with outermost stack.
585 for ops in acc.iter().rev() {
586 occurrence_ops.extend_from_slice(ops);
587 }
588 ops_is_prefix(sess, node_id, span, name, &binder.ops, &occurrence_ops);
589 return;
590 }
591 }
592 buffer_lint(sess, span.into(), node_id, &format!("unknown macro variable `{}`", name));
593}
594
595/// Returns whether `binder_ops` is a prefix of `occurrence_ops`.
596///
597/// The stack of Kleene operators of a meta-variable occurrence just needs to have the stack of
598/// Kleene operators of its binder as a prefix.
599///
600/// Consider $i in the following example:
601///
602/// ( $( $i:ident = $($j:ident),+ );* ) => { $($( $i += $j; )+)* }
603///
604/// It occurs under the Kleene stack ["*", "+"] and is bound under ["*"] only.
605///
606/// Arguments:
607/// - `sess` is used to emit diagnostics and lints
608/// - `node_id` is used to emit lints
609/// - `span` is the span of the meta-variable being check
610/// - `name` is the name of the meta-variable being check
611/// - `binder_ops` is the stack of Kleene operators for the binder
612/// - `occurrence_ops` is the stack of Kleene operators for the occurrence
613fn ops_is_prefix(
614 sess: &ParseSess,
615 node_id: NodeId,
616 span: Span,
ba9703b0 617 name: MacroRulesNormalizedIdent,
416331ca
XL
618 binder_ops: &[KleeneToken],
619 occurrence_ops: &[KleeneToken],
620) {
621 for (i, binder) in binder_ops.iter().enumerate() {
622 if i >= occurrence_ops.len() {
623 let mut span = MultiSpan::from_span(span);
624 span.push_span_label(binder.span, "expected repetition".into());
625 let message = &format!("variable '{}' is still repeating at this depth", name);
626 buffer_lint(sess, span, node_id, message);
627 return;
628 }
629 let occurrence = &occurrence_ops[i];
630 if occurrence.op != binder.op {
631 let mut span = MultiSpan::from_span(span);
632 span.push_span_label(binder.span, "expected repetition".into());
633 span.push_span_label(occurrence.span, "conflicting repetition".into());
634 let message = "meta-variable repeats with different Kleene operator";
635 buffer_lint(sess, span, node_id, message);
636 return;
637 }
638 }
639}
640
641fn buffer_lint(sess: &ParseSess, span: MultiSpan, node_id: NodeId, message: &str) {
f035d41b
XL
642 // Macros loaded from other crates have dummy node ids.
643 if node_id != DUMMY_NODE_ID {
644 sess.buffer_lint(&META_VARIABLE_MISUSE, span, node_id, message);
645 }
416331ca 646}