mry_macros 0.14.0

Macro crate for mry, a simple but powerful mocking library that supports struct, trait, and function.
Documentation
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{parse_quote, ItemFn};

pub struct LockPaths(Vec<syn::Type>);

impl syn::parse::Parse for LockPaths {
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
        Ok(Self(
            input
                .parse_terminated(syn::Type::parse, syn::Token![,])?
                .into_iter()
                .collect(),
        ))
    }
}

pub(crate) fn transform(args: LockPaths, mut input: ItemFn) -> TokenStream {
    if let Some((attr, paths)) = input
        .attrs
        .iter_mut()
        .find(|attr| {
            let path = attr.path();
            if path.segments.len() == 1 && path.segments[0].ident == "lock" {
                return true;
            }
            if path.segments.len() == 2
                && path.segments[0].ident == "mry"
                && path.segments[1].ident == "lock"
            {
                return true;
            }
            false
        })
        .and_then(|attr| {
            attr.parse_args::<LockPaths>()
                .ok()
                .map(|paths| (attr, paths))
        })
    {
        let paths = args.0.iter().chain(paths.0.iter());
        *attr = parse_quote!(#[mry::lock(#(#paths),*)]);
        return input.into_token_stream();
    }
    let args = args.0.into_iter().map(|arg| {
        let name = arg
            .to_token_stream()
            .to_string()
            .replace(" :: ", "::")
            .replace("< ", "<")
            .replace(" >", ">");
        quote![(std::any::Any::type_id(&#arg), #name.to_string())]
    });
    let block = input.block.clone();
    input.block.stmts.clear();
    let mutexes = quote![mry::__mutexes(vec![#(#args,)*])];
    input.block.stmts.insert(
        0,
        syn::Stmt::Expr(
            if input.sig.asyncness.is_some() {
                parse_quote! {
                    mry::__async_lock_and_run(#mutexes, move || Box::pin(async #block)).await
                }
            } else {
                parse_quote! {
                    mry::__lock_and_run(#mutexes, move || #block)
                }
            },
            None,
        ),
    );
    input.into_token_stream()
}

#[cfg(test)]
mod test {
    use pretty_assertions::assert_eq;
    use syn::{parse2, parse_str};

    use super::*;

    #[test]
    fn lock() {
        let args = LockPaths(vec![
            parse_str("<A as B>::a").unwrap(),
            parse_str("a::a").unwrap(),
            parse_str("b::b").unwrap(),
        ]);
        let input: ItemFn = parse2(quote! {
            #[test]
            fn test_meow() {
                assert!(true);
            }
        })
        .unwrap();

        assert_eq!(
            transform(args, input).to_string(),
            quote! {
                #[test]
                fn test_meow() {
                    mry::__lock_and_run(mry::__mutexes(vec![
                        (std::any::Any::type_id(&<A as B> :: a), "<A as B>::a".to_string()),
                        (std::any::Any::type_id(&a :: a), "a::a".to_string()),
                        (std::any::Any::type_id(&b :: b), "b::b".to_string()),
                    ]), move | | {
                        assert!(true);
                    })
                }
            }
            .to_string()
        );
    }

    #[test]
    fn concats_multiple_locks() {
        let args = LockPaths(vec![parse_str("a::a").unwrap(), parse_str("b::b").unwrap()]);
        let input: ItemFn = parse2(quote! {
            #[mry::lock(c::c)]
            #[test]
            fn test_meow() {
                assert!(true);
            }
        })
        .unwrap();

        assert_eq!(
            transform(args, input).to_string(),
            quote! {
                #[mry::lock(a::a, b::b, c::c)]
                #[test]
                fn test_meow() {
                    assert!(true);
                }
            }
            .to_string()
        );
    }
}