tycho-util-proc 0.3.9

Macros implementations for `tycho-util`.
Documentation
use quote::quote;
use syn::{Data, DeriveInput, Field, Fields};
pub fn impl_transactional(input: DeriveInput) -> Result<proc_macro2::TokenStream, syn::Error> {
    let name = &input.ident;

    let fields = match &input.data {
        Data::Struct(data) => match &data.fields {
            Fields::Named(fields) => &fields.named,
            _ => {
                return Err(syn::Error::new_spanned(
                    &input,
                    "only named structs supported",
                ));
            }
        },
        _ => return Err(syn::Error::new_spanned(&input, "only structs supported")),
    };

    let tx_fields: Vec<_> = fields.iter().filter(|f| !has_skip_attr(f)).collect();

    let tx_idents: Vec<_> = tx_fields.iter().filter_map(|f| f.ident.as_ref()).collect();

    let mut generics = input.generics.clone();
    {
        let where_clause = generics.make_where_clause();
        for field in &tx_fields {
            let ty = &field.ty;
            where_clause
                .predicates
                .push(syn::parse_quote!(#ty: tycho_util::transactional::Transactional));
        }
    }
    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();

    let in_tx_body = if tx_idents.is_empty() {
        quote! { false }
    } else {
        let checks = tx_idents.iter().map(|field| quote! { self.#field.in_tx() });
        quote! { #(#checks)||* }
    };

    Ok(quote! {
        impl #impl_generics tycho_util::transactional::Transactional for #name #ty_generics #where_clause {
            fn begin(&mut self) {
                #( self.#tx_idents.begin(); )*
            }

            fn commit(&mut self) {
                #( self.#tx_idents.commit(); )*
            }

            fn rollback(&mut self) {
                #( self.#tx_idents.rollback(); )*
            }

            fn in_tx(&self) -> bool {
                #in_tx_body
            }
        }
    })
}

fn has_skip_attr(field: &Field) -> bool {
    field.attrs.iter().any(|attr| {
        attr.path().is_ident("tx")
            && attr
                .parse_args::<syn::Ident>()
                .map(|id| id == "skip")
                .unwrap_or(false)
    })
}