]>
Commit | Line | Data |
---|---|---|
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 |
107 | use crate::mbe::{KleeneToken, TokenTree}; |
108 | ||
74b04a01 | 109 | use rustc_ast::token::{DelimToken, Token, TokenKind}; |
3dfed10e | 110 | use rustc_ast::{NodeId, DUMMY_NODE_ID}; |
dfeec247 XL |
111 | use rustc_data_structures::fx::FxHashMap; |
112 | use rustc_session::lint::builtin::META_VARIABLE_MISUSE; | |
113 | use rustc_session::parse::ParseSess; | |
74b04a01 | 114 | use rustc_span::symbol::kw; |
ba9703b0 | 115 | use rustc_span::{symbol::MacroRulesNormalizedIdent, MultiSpan, Span}; |
416331ca | 116 | |
416331ca | 117 | use smallvec::SmallVec; |
416331ca | 118 | |
cdc7bbd5 XL |
119 | use 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. | |
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 { | |
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 | ||
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. | |
ba9703b0 | 181 | type Binders = FxHashMap<MacroRulesNormalizedIdent, BinderInfo>; |
416331ca XL |
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 | |
e74abb32 | 198 | pub(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 | |
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 | } | |
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 | |
303 | fn 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 | |
322 | fn 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)] | |
358 | enum 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 | |
387 | fn 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 | |
516 | fn 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 | |
564 | fn 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 ¯os { | |
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 | |
613 | fn 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 | ||
641 | fn 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 | } |