expandable_impl/lib.rs
1#![deny(missing_debug_implementations)]
2#![warn(
3 missing_docs,
4 clippy::cast_sign_loss,
5 clippy::cast_precision_loss,
6 clippy::cast_lossless,
7 clippy::cast_possible_wrap,
8 clippy::clear_with_drain,
9 clippy::dbg_macro,
10 clippy::deref_by_slicing,
11 clippy::doc_link_with_quotes,
12 clippy::doc_markdown,
13 clippy::explicit_deref_methods,
14 clippy::get_unwrap,
15 clippy::impl_trait_in_params,
16 clippy::inefficient_to_string,
17 clippy::redundant_else,
18 clippy::semicolon_if_nothing_returned,
19 clippy::should_panic_without_expect,
20 clippy::string_add,
21 clippy::string_to_string,
22 clippy::used_underscore_binding,
23 clippy::wildcard_imports
24)]
25
26//! <div class="title-block" style="text-align: center;" align="center">
27//! <h1><code>expandable-impl</code></h1>
28//! An opinionated, runtime-agnostic <code>macro_rules!</code> expansion
29//! checker. </div>
30//!
31//! <br />
32//! <br />
33#![doc = include_str!("../doc/00-top-image.md")]
34//!
35#![doc = include_str!("../doc/01-textbook-example.md")]
36//! Luckily for us, this crate provides the [`check_macro`] function, that
37//! (drumroll) checks that a macro is valid. It takes as argument the context
38//! in which the macro will be called and the content of the macro definition.
39//! Let's use it on `js_concat`:
40//!
41//! ```
42//! use expandable_impl::{InvocationContext, quote};
43//!
44//! let err = expandable_impl::check_macro(InvocationContext::Item, quote! {
45//! (@left:expr, @right:expr) => {
46//! @left ++ @right
47//! };
48//! })
49//! .unwrap_err();
50//!
51//! assert!(matches!(
52//! err,
53//! expandable_impl::Error::InvalidProducedAst { .. }
54//! ));
55//! ```
56//!
57//! ## Expansion context
58//!
59//! Macros can expand to different things depending on where they are called.
60//! As a result, `expandable-impl` must know what the macro expands to. This is
61//! represented by the [`InvocationContext`] enum.
62//!
63//! ## Runtime-agnostic?
64//!
65//! This crate does not depend on any "compiler-specific" data structure. It
66//! may be embedded anywhere. [`expandable`] is a crate that provides the
67//! features defined in this crate as a set of `proc_macro`. Feel free to
68//! embed this crate in your analysis tool!
69//!
70//! [`expandable`]: https://crates.io/crates/expandable
71#![doc = include_str!("../doc/02-what-can-it-detect.md")]
72//!
73#![doc = include_str!("../doc/03-opinionated.md")]
74//!
75#![doc = include_str!("../doc/98-msrv.md")]
76//!
77#![doc = include_str!("../doc/99-license.md")]
78
79use std::{marker::Copy, str::FromStr};
80
81pub use error::{Error, MacroRuleNode};
82use grammar::DynamicState;
83pub use grammar::TokenDescription;
84
85#[macro_use]
86mod macros;
87mod error;
88mod expansion;
89mod grammar;
90mod list;
91mod matcher;
92mod repetition_stack;
93
94#[doc(hidden)]
95pub mod span;
96mod substitution;
97
98/// The whole point.
99///
100/// This functions takes all the tokens that have been passed to the macro
101/// invocation and performs all the checks that have been implemented in this
102/// crate.
103pub fn check_macro<Span>(
104 ctx: InvocationContext,
105 input: Vec<TokenTree<Span>>,
106) -> Result<(), Error<Span>>
107where
108 Span: Copy + 'static,
109{
110 let mut iter = input.into_iter();
111
112 while let Some(head) = iter.next() {
113 let TokenTreeKind::Parenthesed(matcher) = head.kind else {
114 return Err(Error::ParsingFailed {
115 what: vec![MacroRuleNode::Matcher],
116 where_: head.span,
117 });
118 };
119
120 let matcher = matcher::TokenTree::from_generic(matcher)?;
121 let matcher = matcher::Matcher::from_generic(&matcher)?;
122
123 let Some(token) = iter.next() else {
124 return Err(Error::UnexpectedEnd {
125 last_token: Some(head.span),
126 });
127 };
128
129 let TokenTreeKind::Terminal(Terminal::FatArrow) = token.kind else {
130 return Err(Error::ParsingFailed {
131 what: vec![MacroRuleNode::Terminal(Terminal::FatArrow)],
132 where_: token.span,
133 });
134 };
135
136 let Some(token) = iter.next() else {
137 return Err(Error::UnexpectedEnd {
138 last_token: Some(token.span),
139 });
140 };
141
142 let TokenTreeKind::CurlyBraced(substitution) = token.kind else {
143 return Err(Error::ParsingFailed {
144 what: vec![MacroRuleNode::Transcriber],
145 where_: token.span,
146 });
147 };
148
149 let substitution = substitution::TokenTree::from_generic(substitution)?;
150 repetition_stack::check(&matcher, &substitution)?;
151
152 expansion::check_arm(ctx.to_state(), matcher, &substitution, token.span)?;
153
154 if let Some(semi) = iter.next() {
155 let TokenTreeKind::Terminal(Terminal::Semicolon) = semi.kind else {
156 return Err(Error::ParsingFailed {
157 what: vec![MacroRuleNode::Terminal(Terminal::Semicolon)],
158 where_: semi.span,
159 });
160 };
161 }
162 }
163
164 Ok(())
165}
166
167pub(crate) trait Spannable<Span> {
168 type Output;
169
170 fn with_span(self, span: Span) -> Self::Output;
171}
172
173/// An untyped tree of tokens.
174///
175/// This type allows the end-user to represent the tokens that is passed in the
176/// macro invocation. It is not _exactly_ the same as [`proc_macro::TokenTree`],
177/// as the tokens are grouped differently [^1]. Writing a
178/// "[`proc_macro::TokenTree`] to [`TokenTree`]" should not be too hard, but is
179/// not the scope of this crate.
180///
181/// [^1]: For instance, `+=` is represented as a single token in declarative
182/// macros but as "`+` followed by `=`" in procedural macros
183/// ([ref][declarative-macro-tokens-and-procedural-macro-tokens]).
184///
185/// [`proc_macro::TokenTree`]:
186/// https://doc.rust-lang.org/proc_macro/enum.TokenTree.html
187/// [declarative-macro-tokens-and-procedural-macro-tokens]:
188/// https://doc.rust-lang.org/reference/procedural-macros.html#declarative-macro-tokens-and-procedural-macro-tokens
189#[derive(Clone, Debug, PartialEq)]
190pub struct TokenTree<Span> {
191 /// What kind of token tree is this?
192 pub kind: TokenTreeKind<Span>,
193 /// Its position in the input code (useful for error message generation).
194 pub span: Span,
195}
196
197#[doc(hidden)]
198#[allow(non_snake_case, unused)]
199impl<Span> TokenTree<Span> {
200 pub fn terminal(span: Span, t: Terminal) -> TokenTree<Span> {
201 TokenTree {
202 kind: TokenTreeKind::Terminal(t),
203 span,
204 }
205 }
206
207 pub fn parenthesed(span: Span, i: Vec<TokenTree<Span>>) -> TokenTree<Span> {
208 TokenTree {
209 kind: TokenTreeKind::Parenthesed(i),
210 span,
211 }
212 }
213
214 pub fn curly_braced(span: Span, i: Vec<TokenTree<Span>>) -> TokenTree<Span> {
215 TokenTree {
216 kind: TokenTreeKind::CurlyBraced(i),
217 span,
218 }
219 }
220
221 pub fn bracketed(span: Span, i: Vec<TokenTree<Span>>) -> TokenTree<Span> {
222 TokenTree {
223 kind: TokenTreeKind::Bracketed(i),
224 span,
225 }
226 }
227}
228
229/// Represents the different types of token tree.
230#[derive(Clone, Debug, PartialEq)]
231pub enum TokenTreeKind<Span> {
232 /// A terminal (ie: a leaf tree node).
233 Terminal(Terminal),
234 /// A sequence of [`TokenTree`] that is delimited by parenthesis.
235 Parenthesed(Vec<TokenTree<Span>>),
236 /// A sequence of [`TokenTree`] that is delimited by curly brackets.
237 CurlyBraced(Vec<TokenTree<Span>>),
238 /// A sequence of [`TokenTree`] that is delimited by brackets.
239 Bracketed(Vec<TokenTree<Span>>),
240}
241
242impl_spannable!(TokenTreeKind<Span> => TokenTree);
243
244/// A terminal symbol.
245///
246/// # Multi-character operators
247///
248/// Multi-character operators (`+=`, `->`, ...) must _not_ be split in multiple
249/// [`Terminal`]. Any use of the [`check_macro`] function that does not respect
250/// this invariant will is subject to unexpected results.
251#[non_exhaustive]
252#[derive(Clone, Debug, Eq, Hash, PartialEq)]
253pub enum Terminal {
254 /// An identifier (`foo`, `bar`).
255 Ident(String),
256
257 // Keywords
258 /// The `as` keyword.
259 As,
260 /// The `async` keyword.
261 Async,
262 /// The `await` keyword.
263 Await,
264 /// The `break` keyword.
265 Break,
266 /// The `const` keyword.
267 Const,
268 /// The `continue` keyword.
269 Continue,
270 /// The `crate` keyword.
271 Crate,
272 /// The `dyn` keyword.
273 Dyn,
274 /// The `else` keyword.
275 Else,
276 /// The `enum` keyword.
277 Enum,
278 /// The `extern` keyword.
279 Extern,
280 /// The `false` keyword.
281 False,
282 /// The `fn` keyword.
283 Fn,
284 /// The `for` keyword.
285 For,
286 /// The `if` keyword.
287 If,
288 /// The `impl` keyword.
289 Impl,
290 /// The `in` keyword.
291 In,
292 /// The `let` keyword.
293 Let,
294 /// The `loop` keyword.
295 Loop,
296 /// The `match` keyword.
297 Match,
298 /// The `mod` keyword.
299 Mod,
300 /// The `move` keyword.
301 Move,
302 /// The `mut` keyword.
303 Mut,
304 /// The `pub` keyword.
305 Pub,
306 /// The `ref` keyword.
307 Ref,
308 /// The `return` keyword.
309 Return,
310 /// The `self` keyword.
311 Self_,
312 /// The `Self` keyword.
313 SelfUpper,
314 /// The `static` keyword.
315 Static,
316 /// The `struct` keyword.
317 Struct,
318 /// The `super` keyword.
319 Super,
320 /// The `trait` keyword.
321 Trait,
322 /// The `true` keyword.
323 True,
324 /// The `type` keyword.
325 Type,
326 /// The `union` keyword.
327 Union,
328 /// The `unsafe` keyword.
329 Unsafe,
330 /// The `use` keyword.
331 Use,
332 /// The `where` keyword.
333 Where,
334 /// The `while` keyword.
335 While,
336 /// The `abstract` keyword.
337 Abstract,
338 /// The `become` keyword.
339 Become,
340 /// The `box` keyword.
341 Box,
342 /// The `do` keyword.
343 Do,
344 /// The `final` keyword.
345 Final,
346 /// The `macro` keyword.
347 Macro,
348 /// The `override` keyword.
349 Override,
350 /// The `priv` keyword.
351 Priv,
352 /// The `try` keyword.
353 Try,
354 /// The `typeof` keyword.
355 Typeof,
356 /// The `unsized` keyword.
357 Unsized,
358 /// The `virtual` keyword.
359 Virtual,
360 /// The `yield` keyword.
361 Yield,
362
363 // Literals
364 /// A literal (`42`, `"foo"`).
365 ///
366 /// We use textual representation of literals because we don't want to deal
367 /// with the parsing of literals.
368 // TODO: it may be appropriate to actually parse these literals :thinking:.
369 Literal(String),
370
371 // Punctuates
372 /// A plus (`+`).
373 Plus,
374 /// A minus (`-`).
375 Minus,
376 /// A star (`*`).
377 Star,
378 /// A slash (`/`).
379 Slash,
380 /// A percent (`%`).
381 Percent,
382 /// A caret (`^`).
383 Caret,
384 /// A not (`!`).
385 Not,
386 /// An and (`&`).
387 And,
388 /// An or (`|`).
389 Or,
390 /// Lazy boolean and (`&&`).
391 AndAnd,
392 /// Lazy boolean or (`||`).
393 OrOr,
394 /// A shift left (`<<`).
395 Shl,
396 /// A shift right (`>>`).
397 Shr,
398 /// A plus-equals (`+=`).
399 PlusEquals,
400 /// A minus-equals (`-=`).
401 MinusEquals,
402 /// A star-equals (`*=`).
403 StarEquals,
404 /// A slash-equals (`/=`).
405 SlashEquals,
406 /// A percent-equals (`%=`).
407 PercentEquals,
408 /// A caret-equal (`^=`).
409 CaretEquals,
410 /// An and-equals (`&=`).
411 AndEquals,
412 /// An or-equals (`|=`).
413 OrEquals,
414 /// A shift-left-equals (`<<=`).
415 ShlEquals,
416 /// A shift-right-equals (`>>=`).
417 ShrEquals,
418 /// An equal (`=`).
419 Equals,
420 /// An equals equals (`==`).
421 EqualsEquals,
422 /// A not-equal (`!=`).
423 NotEquals,
424 /// A greater than (`>`).
425 GreaterThan,
426 /// A less than (`<`).
427 LessThan,
428 /// A greater than equals (`>=`).
429 GreaterThanEquals,
430 /// A less than equals (`<=`).
431 LessThanEquals,
432 /// An at (`@`).
433 At,
434 /// An underscore (`_`).
435 Underscore,
436 /// A dot (`.`).
437 Dot,
438 /// A dot dot (`..`).
439 DotDot,
440 /// A dot dot dot (`...`).
441 DotDotDot,
442 /// A dot dot equals (`..=`).
443 DotDotEquals,
444 /// A comma (`,`).
445 Comma,
446 /// A semicolon (`;`).
447 Semicolon,
448 /// A colon (`:`).
449 Colon,
450 /// A colon colon (`::`).
451 ColonColon,
452 /// A right arrow (`->`).
453 RightArrow,
454 /// A fat arrow (`=>`).
455 FatArrow,
456 /// A pound (`#`).
457 Pound,
458 /// A dollar sign (`$`).
459 Dollar,
460 /// A question mark (`?`).
461 QuestionMark,
462}
463
464impl_spannable!(Terminal => TokenTree);
465
466impl<Span> From<Terminal> for TokenTreeKind<Span> {
467 fn from(value: Terminal) -> TokenTreeKind<Span> {
468 TokenTreeKind::Terminal(value)
469 }
470}
471
472/// The contexts in which a macro can be called.
473///
474/// All macros can't be called in all contexts. For instance, a macro that
475/// expands to a pattern may not be called where an expression is expected.
476/// This type allows the [`check_macro`] function to know the context the macro
477/// will be invoked in.
478#[derive(Clone, Copy, Debug, PartialEq)]
479pub enum InvocationContext {
480 /// The macro expands to an expression.
481 Expr,
482 /// The macro expands to any number of item.
483 Item,
484 /// The macro expands to a pattern.
485 Pat,
486 /// The macro expands to any number of statement.
487 Stmt,
488 /// The macro expands to a type.
489 Ty,
490}
491
492impl InvocationContext {
493 fn to_state<T>(self) -> DynamicState<T>
494 where
495 T: Copy,
496 {
497 match self {
498 InvocationContext::Expr => DynamicState::expr(),
499 InvocationContext::Item => DynamicState::item(),
500 InvocationContext::Pat => DynamicState::pat(),
501 InvocationContext::Stmt => DynamicState::stmt(),
502 InvocationContext::Ty => DynamicState::ty(),
503 }
504 }
505}
506
507/// A specific kind of fragment.
508#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
509pub enum FragmentKind {
510 /// A block (`block`).
511 Block,
512 /// An expression (`expr`).
513 Expr,
514 /// An identifier or a raw identifier (`ident`).
515 Ident,
516 /// An item (`item`).
517 Item,
518 /// A lifetime (`lifetime`).
519 Lifetime,
520 /// An attribute content (`meta`)
521 Meta,
522 /// A pattern (including alternative) (`pat`).
523 Pat,
524 /// A path (`path`).
525 Path,
526 /// A pattern (excluding alternative) (`pat_param`).
527 PatParam,
528 /// A statement (`stmt`).
529 Stmt,
530 /// A token tree (`tt`).
531 Tt,
532 /// A type (`ty`).
533 Ty,
534 /// A visibility (`vis`).
535 Vis,
536}
537
538impl FromStr for FragmentKind {
539 type Err = ();
540
541 fn from_str(s: &str) -> Result<FragmentKind, ()> {
542 Ok(match s {
543 "block" => FragmentKind::Block,
544 "expr" => FragmentKind::Expr,
545 "ident" => FragmentKind::Ident,
546 "item" => FragmentKind::Item,
547 "lifetime" => FragmentKind::Lifetime,
548 "meta" => FragmentKind::Meta,
549 "pat" => FragmentKind::Pat,
550 "path" => FragmentKind::Path,
551 "pat_param" => FragmentKind::PatParam,
552 "stmt" => FragmentKind::Stmt,
553 "tt" => FragmentKind::Tt,
554 "ty" => FragmentKind::Ty,
555 "vis" => FragmentKind::Vis,
556
557 _ => return Err(()),
558 })
559 }
560}
561
562impl FromStr for InvocationContext {
563 type Err = ();
564
565 fn from_str(s: &str) -> Result<InvocationContext, ()> {
566 Ok(match s {
567 "item" => InvocationContext::Item,
568 "expr" => InvocationContext::Expr,
569
570 _ => return Err(()),
571 })
572 }
573}
574
575#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
576pub(crate) struct RepetitionQuantifier<Span> {
577 kind: RepetitionQuantifierKind,
578 span: Span,
579}
580
581#[cfg(test)]
582#[allow(non_snake_case, unused)]
583impl RepetitionQuantifier<()> {
584 fn ZeroOrOne() -> RepetitionQuantifier<()> {
585 RepetitionQuantifier {
586 kind: RepetitionQuantifierKind::ZeroOrOne,
587 span: (),
588 }
589 }
590
591 fn ZeroOrMore() -> RepetitionQuantifier<()> {
592 RepetitionQuantifier {
593 kind: RepetitionQuantifierKind::ZeroOrMore,
594 span: (),
595 }
596 }
597
598 fn OneOrMore() -> RepetitionQuantifier<()> {
599 RepetitionQuantifier {
600 kind: RepetitionQuantifierKind::OneOrMore,
601 span: (),
602 }
603 }
604}
605
606/// Denotes how much times a repetition shall be repeated.
607#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
608pub enum RepetitionQuantifierKind {
609 /// Zero or one repetitions (`?` operator).
610 ZeroOrOne,
611 /// Zero or more repetitions (`*` operator).
612 ZeroOrMore,
613 /// One or more repetitions (`+` operator).
614 OneOrMore,
615}
616
617impl_spannable!(RepetitionQuantifierKind => RepetitionQuantifier);
618
619#[cfg(test)]
620mod tests {
621 use super::*;
622
623 macro_rules! check_macro_test {
624 (
625 $( #[ $meta:meta ] )*
626 $test_name:ident {
627 #[$kind:ident]
628 {
629 $( $tt:tt )*
630 }
631 }
632 ) => {
633 $( #[ $meta ] )*
634 #[test]
635 fn $test_name() {
636 let tokens = quote! { $( $tt )* };
637 let ctxt = stringify!($kind).parse::<InvocationContext>().expect("Failed to parse `InvocationContext`");
638
639 check_macro(ctxt, tokens).expect("Macro check returned error");
640 }
641 };
642 }
643
644 check_macro_test! {
645 single_arm {
646 #[expr]
647 {
648 () => { a }
649 }
650 }
651 }
652
653 check_macro_test! {
654 accepts_final_semi {
655 #[expr]
656 {
657 () => { a };
658 }
659 }
660 }
661
662 check_macro_test! {
663 multiple_arms {
664 #[expr]
665 {
666 () => { a };
667 (()) => { b };
668 }
669 }
670 }
671
672 check_macro_test! {
673 #[should_panic = "Macro check returned error: InvalidProducedAst { span: 2, expected: [Return, Break, Ident, Self_, SelfUpper, Super, Crate, Fragment(Ident), ColonColon, LessThan, Literal, Fragment(Expr), If, LParen, LBracket, LBrace, Loop, While, For, Match], counter_example: [] }"]
674 empty_expr_is_not_an_expr {
675 #[expr]
676 {
677 () => {}
678 }
679 }
680 }
681
682 check_macro_test! {
683 just_a_simple_if {
684 #[expr]
685 {
686 () => { if a { a } }
687 }
688 }
689 }
690
691 check_macro_test! {
692 if_with_else {
693 #[expr]
694 {
695 () => { if a { a } else { a } }
696 }
697 }
698 }
699
700 check_macro_test! {
701 fn_call_1 {
702 #[expr]
703 {
704 () => { a(b) };
705 }
706 }
707 }
708
709 check_macro_test! {
710 fn_call_2 {
711 #[expr]
712 {
713 () => { a(b, c) };
714 }
715 }
716 }
717
718 check_macro_test! {
719 fn_call_3 {
720 #[expr]
721 {
722 () => { a(b, c, d) };
723 }
724 }
725 }
726
727 check_macro_test! {
728 fn_call_4 {
729 #[expr]
730 {
731 () => {
732 a(
733 b,
734 c,
735 d,
736 )
737 };
738 }
739 }
740 }
741
742 check_macro_test! {
743 fn_call_5 {
744 #[expr]
745 {
746 () => {
747 a(
748 b + c,
749 if d { e },
750 if f { g } else { h }
751 )
752 };
753 }
754 }
755 }
756
757 check_macro_test! {
758 fn_call_6 {
759 #[expr]
760 {
761 () => {
762 a(
763 b + c,
764 if d { e },
765 if f { g } else { h },
766 )
767 };
768 }
769 }
770 }
771
772 check_macro_test! {
773 array_0 {
774 #[expr]
775 {
776 () => { [] };
777 }
778 }
779 }
780
781 check_macro_test! {
782 array_1 {
783 #[expr]
784 {
785 () => { [a] };
786 }
787 }
788 }
789
790 check_macro_test! {
791 array_2 {
792 #[expr]
793 {
794 () => { [a, b] };
795 }
796 }
797 }
798
799 check_macro_test! {
800 array_2_ {
801 #[expr]
802 {
803 () => { [a, b,] };
804 }
805 }
806 }
807
808 check_macro_test! {
809 array_with_fragments {
810 #[expr]
811 {
812 (#a:expr, #b:expr, #c:ident, #d:ident) => { [#a, #b, #c, #d] };
813 }
814 }
815 }
816
817 check_macro_test! {
818 array_repeat {
819 #[expr]
820 {
821 ( #( #a:expr )* ) => {
822 [ #( #a, )* ]
823 };
824 }
825 }
826 }
827
828 check_macro_test! {
829 path_repeat {
830 #[expr]
831 {
832 () => {
833 #( :: a )+
834 }
835 }
836 }
837 }
838
839 check_macro_test! {
840 path_repeat_from_fragment {
841 #[expr]
842 {
843 ( #( #id:ident )+ ) => {
844 #( :: #id )+
845 }
846 }
847 }
848 }
849}