application-toys-macros 0.0.2

Procedural macros for application-toys — makes async trait methods dyn-compatible via #[asynchronous]
Documentation
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use syn::{
    Fields, FieldsUnnamed, ItemEnum, Type, parse_macro_input, punctuated::Punctuated, token::Paren,
};

pub fn responsible_macro(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let mut enum_def = parse_macro_input!(item as ItemEnum);

    for variant in enum_def.variants.iter_mut() {
        let mut found_ty: Option<Type> = None;
        let mut found_idx: Option<usize> = None;

        for (i, attr) in variant.attrs.iter().enumerate() {
            if attr.path().is_ident("responsible") {
                match attr.parse_args::<Type>() {
                    Ok(ty) => {
                        found_ty = Some(ty);
                        found_idx = Some(i);
                    }
                    Err(e) => return e.to_compile_error().into(),
                }
                break;
            }
        }

        if let (Some(ty), Some(idx)) = (found_ty, found_idx) {
            variant.attrs.remove(idx);

            let sender_ty: Type = syn::parse2(quote! {
                ::tokio::sync::oneshot::Sender<#ty>
            })
            .unwrap();

            let sender_field = syn::Field {
                attrs: vec![],
                vis: syn::Visibility::Inherited,
                mutability: syn::FieldMutability::None,
                ident: None,
                colon_token: None,
                ty: sender_ty,
            };

            match &mut variant.fields {
                Fields::Unit => {
                    let mut unnamed = Punctuated::new();
                    unnamed.push(sender_field);
                    variant.fields = Fields::Unnamed(FieldsUnnamed {
                        paren_token: Paren(Span::call_site()),
                        unnamed,
                    });
                }
                Fields::Unnamed(fields) => {
                    fields.unnamed.push(sender_field);
                }
                Fields::Named(_) => {
                    return syn::Error::new_spanned(
                        &variant.ident,
                        "#[responsible] cannot be applied to a named-field variant",
                    )
                    .to_compile_error()
                    .into();
                }
            }
        }
    }

    quote! { #enum_def }.into()
}