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}