anonymous-trait 0.1.3

Anonymous trait implementation with capturing the environment
Documentation
use proc_macro2::TokenStream;
use quote::quote;
use syn::parse_quote;

use crate::{attr_syntax::LetDefault, impl_syntax::AnonymousImpl};

pub(crate) fn generate(attr: &LetDefault, mock: &AnonymousImpl) -> TokenStream {
    let trait_ = &mock.trait_;
    let struct_name = mock.struct_name(attr);
    let lifetime = quote!('__anonymous_trait_state);
    let generics = mock.methods().map(|method| {
        let method_ident = &method.sig.ident;
        let closure_type = crate::closure_type::generate(mock.target(), method);
        quote! {
            #method_ident: #closure_type,
        }
    });
    let struct_generics = mock.methods().map(|method| {
        let method_ident = &method.sig.ident;
        quote! {
            #method_ident
        }
    });
    let methods = mock.methods().map(|method| {
        let mut method = method.clone();
        let method_ident = &method.sig.ident;
        let arg_pats = method
            .sig
            .inputs
            .iter()
            .filter_map(|arg| {
                let syn::FnArg::Typed(pat_type) = arg else {
                    return None;
                };
                Some(&pat_type.pat)
            })
            .collect::<Vec<_>>();
        method.block.stmts = vec![syn::Stmt::Expr(
            parse_quote! {
                self.#method_ident.lock().unwrap()(self.__anonymous_trait_state #(,#arg_pats)*)
            },
            None,
        )];
        method
    });
    quote! {
        #[allow(non_camel_case_types)]
        impl <
            #lifetime,
            #(#generics)*
        > #trait_ for #struct_name<#lifetime #(,#struct_generics)*> {
            #(#methods)*
        }
    }
}

#[cfg(test)]
mod tests {
    use syn::parse_quote;

    use super::*;
    use pretty_assertions::assert_eq;

    #[test]
    fn empty() {
        let attr = parse_quote! {
            let my_mock = Cat
        };
        let input = parse_quote! {
            impl Something for Cat {}
        };
        let actual = generate(&attr, &input);
        let expected = quote! {
            #[allow(non_camel_case_types)]
            impl <
                '__anonymous_trait_state,
            > Something for my_mock__Something<'__anonymous_trait_state> {
            }
        };
        assert_eq!(actual.to_string(), expected.to_string());
    }

    #[test]
    fn with_method() {
        let attr = parse_quote! {
            let my_mock = Cat
        };
        let input = parse_quote! {
            impl Something for Cat {
                fn meow(&self) -> String {
                    "meow".to_string()
                }
            }
        };
        let actual = generate(&attr, &input);
        let expected = quote! {
            #[allow(non_camel_case_types)]
            impl <
                '__anonymous_trait_state,
                meow: FnMut(&Cat) -> String,
            > Something for my_mock__Something<'__anonymous_trait_state, meow> {
                fn meow(&self) -> String {
                    self.meow.lock().unwrap()(self.__anonymous_trait_state)
                }
            }
        };
        assert_eq!(actual.to_string(), expected.to_string());
    }

    #[test]
    fn method_args() {
        let attr = parse_quote! {
            let my_mock = Cat
        };
        let input = parse_quote! {
            impl Something for Cat {
                fn meow(&self, volume: u8, count: usize) -> String {
                    "meow".to_string()
                }
            }
        };
        let actual = generate(&attr, &input);
        let expected = quote! {
            #[allow(non_camel_case_types)]
            impl <
                '__anonymous_trait_state,
                meow: FnMut(&Cat, u8, usize) -> String,
            > Something for my_mock__Something<'__anonymous_trait_state, meow> {
                fn meow(&self, volume: u8, count: usize) -> String {
                    self.meow.lock().unwrap()(self.__anonymous_trait_state, volume, count)
                }
            }
        };
        assert_eq!(actual.to_string(), expected.to_string());
    }

    #[test]
    fn receiver_mut() {
        let attr = parse_quote! {
            let my_mock = Cat
        };
        let input = parse_quote! {
            impl Something for Cat {
                fn meow(&mut self) -> String {
                    "meow".to_string()
                }
            }
        };
        let actual = generate(&attr, &input);
        let expected = quote! {
            #[allow(non_camel_case_types)]
            impl <
                '__anonymous_trait_state,
                meow: FnMut(&mut Cat) -> String,
            > Something for my_mock__Something<'__anonymous_trait_state, meow> {
                fn meow(&mut self) -> String {
                    self.meow.lock().unwrap()(self.__anonymous_trait_state)
                }
            }
        };
        assert_eq!(actual.to_string(), expected.to_string());
    }

    #[test]
    fn no_output() {
        let attr = parse_quote! {
            let my_mock = Cat
        };
        let input = parse_quote! {
            impl Something for Cat {
                fn meow(&self) {
                    "meow".to_string()
                }
            }
        };
        let actual = generate(&attr, &input);
        let expected = quote! {
            #[allow(non_camel_case_types)]
            impl <
                '__anonymous_trait_state,
                meow: FnMut(&Cat),
            > Something for my_mock__Something<'__anonymous_trait_state, meow> {
                fn meow(&self) {
                    self.meow.lock().unwrap()(self.__anonymous_trait_state)
                }
            }
        };
        assert_eq!(actual.to_string(), expected.to_string())
    }

    #[test]
    fn async_fn() {
        let attr = parse_quote! {
            let my_mock = Cat
        };
        let input = parse_quote! {
            impl Something for Cat {
                async fn meow(&self) -> String {
                    "meow".to_string()
                }
            }
        };
        let actual = generate(&attr, &input);
        let expected = quote! {
            #[allow(non_camel_case_types)]
            impl <
                '__anonymous_trait_state,
                meow: FnMut(&Cat) -> String + Send,
            > Something for my_mock__Something<'__anonymous_trait_state, meow> {
                async fn meow(&self) -> String {
                    self.meow.lock().unwrap()(self.__anonymous_trait_state)
                }
            }
        };
        assert_eq!(actual.to_string(), expected.to_string());
    }
}