Skip to main content

moku_macros/
lib.rs

1#![allow(unused)]
2
3use convert_case::{Case, Casing};
4use proc_macro::TokenStream;
5use proc_macro2::{Ident, Span};
6use quote::ToTokens;
7use syn::{parse, parse_macro_input, spanned::Spanned, ItemImpl, ItemMod};
8use unpacker::build_metadata;
9
10use crate::util::path_matches;
11
12mod metadata;
13mod unpacker;
14mod util;
15
16/// Append a compile error to a TokenStream.
17///
18/// Important for allowing rust-analyzer completions to work while typing inside of an attribute
19/// macro, which would otherwise erase the attributed item during macro expansion in favor of the
20/// compile error resulting from trying to parse a malformed AST.
21///
22/// See https://github.com/tokio-rs/tokio/pull/4162 for more context.
23fn token_stream_with_error(mut tokens: TokenStream, error: syn::Error) -> TokenStream {
24    tokens.extend(TokenStream::from(error.into_compile_error()));
25    tokens
26}
27
28#[proc_macro_attribute]
29pub fn machine_module(_args: TokenStream, input: TokenStream) -> TokenStream {
30    // validate that this attribute is attached to a module
31    match parse::<ItemMod>(input.clone()) {
32        Ok(_) => input,
33        Err(error) => token_stream_with_error(input, error),
34    }
35}
36
37#[proc_macro_attribute]
38pub fn state_machine(args: TokenStream, input: TokenStream) -> TokenStream {
39    // validate that this attribute is attached to a module
40    let main_mod = match parse::<ItemMod>(input.clone()) {
41        Ok(main_mod) => main_mod,
42        Err(error) => {
43            return token_stream_with_error(input, error);
44        }
45    };
46
47    // Past this point, don't return the input along with the compile error here.
48    //
49    // If we've found an error at this point, the lack of autogen code will cause a ton of
50    // red herring compile errors within the machine module. Better to let the errors be
51    // outside of the module due to its lack of existence in order to make the true error
52    // easier for the user to find within the module.
53    //
54    // This will stop rust-analyzer completions from working while the error persists.
55
56    let name = if args.is_empty() {
57        // derive state machine name from module name by default
58        Ident::new(
59            &main_mod.ident.to_string().to_case(Case::UpperCamel),
60            Span::call_site(),
61        )
62    } else {
63        parse_macro_input!(args as Ident)
64    };
65
66    match generate_state_machine(name, main_mod) {
67        Ok(output) => output.into_token_stream().into(),
68        Err(error) => error.into_compile_error().into(),
69    }
70}
71
72fn generate_state_machine(name: Ident, main_mod: ItemMod) -> Result<ItemMod, syn::Error> {
73    let metadata = build_metadata(name, main_mod)?;
74    Ok(metadata.write_state_machine())
75}