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