tuco-derive 0.1.3

Derive macro for the Tuco crate
Documentation
use proc_macro::TokenStream;
use quote::{quote, ToTokens};
use syn::{parse_macro_input, Data, DeriveInput, Field, Fields, GenericParam, Type};
struct TucoType {
    original: Type,
    tuple: Type,
}

enum FieldTypeInfo {
    Tuco(TucoType),
    Standard(Type),
}

struct StructFieldEntry {
    single: bool,
    index: usize,
    ident: Option<syn::Ident>,
    t: FieldTypeInfo,
}

impl StructFieldEntry {
    fn get_original_type(&self) -> proc_macro2::TokenStream {
        match &self.t {
            FieldTypeInfo::Tuco(tuco) => tuco.original.to_token_stream(),
            FieldTypeInfo::Standard(x) => x.to_token_stream(),
        }
    }

    fn get_tuple_type(&self) -> proc_macro2::TokenStream {
        match &self.t {
            FieldTypeInfo::Tuco(tuco) => tuco.tuple.to_token_stream(),
            FieldTypeInfo::Standard(x) => x.to_token_stream(),
        }
    }

    fn get_convert_to_tuple_row(&self) -> proc_macro2::TokenStream {
        match &self.ident {
            Some(ident) => match &self.t {
                FieldTypeInfo::Tuco(_) => {
                    quote! { self.#ident.into_tuple() }
                }
                FieldTypeInfo::Standard(_) => {
                    quote! { self.#ident }
                }
            },
            None => {
                let index = syn::Index::from(self.index);
                match &self.t {
                    FieldTypeInfo::Tuco(_) => {
                        quote! { self.#index.into_tuple() }
                    }
                    FieldTypeInfo::Standard(_) => {
                        quote! { self.#index }
                    }
                }
            }
        }
    }

    fn get_convert_from_tuple_row(&self) -> proc_macro2::TokenStream {
        let index = syn::Index::from(self.index);
        let type_token = self.get_original_type();
        let input_select = if self.single {
            quote! { tuple }
        } else {
            quote! { tuple.#index }
        };

        match &self.ident {
            Some(ident) => match &self.t {
                FieldTypeInfo::Tuco(_) => {
                    quote! { #ident : <#type_token as Tuco>::from_tuple(#input_select) }
                }
                FieldTypeInfo::Standard(_) => {
                    quote! { #ident : #input_select }
                }
            },
            None => match &self.t {
                FieldTypeInfo::Tuco(_) => {
                    quote! { <#type_token as Tuco>::from_tuple(#input_select) }
                }
                FieldTypeInfo::Standard(_) => {
                    quote! { #input_select }
                }
            },
        }
    }
}

fn has_tuco_meta_tag(field: &Field) -> bool {
    field.attrs.iter().any(|x| x.path().is_ident("tuco"))
}

fn parse_struct_field(
    index: usize,
    field: &Field,
    single: bool,
) -> Result<StructFieldEntry, String> {
    if has_tuco_meta_tag(field) {
        let type_as_string = field.ty.to_token_stream().to_string().replace(" ", "");
        let tuple_type = format!("<{} as Tuco>::Tuple", type_as_string);
        let parsed_type: syn::Type = match syn::parse_str(&tuple_type) {
            Ok(t) => t,
            _ => return Err("Failed to parse type".to_string()),
        };

        Ok(StructFieldEntry {
            single,
            index,
            ident: field.ident.clone(),
            t: FieldTypeInfo::Tuco(TucoType {
                original: field.ty.clone(),
                tuple: parsed_type,
            }),
        })
    } else {
        Ok(StructFieldEntry {
            single,
            index,
            ident: field.ident.clone(),
            t: FieldTypeInfo::Standard(field.ty.clone()),
        })
    }
}

#[proc_macro_attribute]
pub fn tuco(_: TokenStream, _: TokenStream) -> TokenStream {
    TokenStream::new()
}

#[proc_macro_derive(Tuco, attributes(tuco))]
pub fn derive_tuco(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    // Get the struct or enum name
    let struct_name = input.ident;

    // Generate the generic parameters
    let generics = input.generics;
    let where_clause: &Option<syn::WhereClause> = &generics.where_clause;
    let generic_params: Vec<_> = generics
        .params
        .iter()
        .map(|param| match param {
            GenericParam::Type(type_param) => quote! { #type_param },
            GenericParam::Lifetime(lifetime) => quote! { #lifetime },
            GenericParam::Const(const_param) => quote! { #const_param },
        })
        .collect();

    let impl_statement = quote! { impl<#(#generic_params),*> Tuco for #struct_name<#(#generic_params),*> #where_clause };

    let Data::Struct(data_struct) = &input.data else {
        panic!("Tuco can only be used with structs.");
    };
    let single = data_struct.fields.len() == 1;
    let fields: Vec<StructFieldEntry> = match &data_struct.fields {
        Fields::Named(fields_named) => fields_named
            .named
            .iter()
            .enumerate()
            .map(|(i, f)| parse_struct_field(i, f, single).unwrap())
            .collect(),
        Fields::Unnamed(fields) => fields
            .unnamed
            .iter()
            .enumerate()
            .map(|(i, f)| parse_struct_field(i, f, single).unwrap())
            .collect(),
        Fields::Unit => Vec::new(),
    };

    let tuple_types: Vec<_> = fields
        .iter()
        .map(StructFieldEntry::get_tuple_type)
        .collect();

    let from_tuple_rows: Vec<_> = fields
        .iter()
        .map(StructFieldEntry::get_convert_from_tuple_row)
        .collect();

    let from_tuple_impl = match &data_struct.fields {
        Fields::Named(_) => match fields.len() {
            0 => quote! {fn from_tuple(tuple: Self::Tuple) -> Self { #struct_name{} }},
            1 => {
                let first = from_tuple_rows.first().unwrap();
                quote! {fn from_tuple(tuple: Self::Tuple) -> Self { #struct_name{#first} }}
            }
            _ => {
                quote! {fn from_tuple(tuple: Self::Tuple) -> Self { #struct_name{#(#from_tuple_rows),*} }}
            }
        },
        Fields::Unnamed(_) => match fields.len() {
            0 => quote! {fn from_tuple(tuple: Self::Tuple) -> Self { #struct_name() }},
            1 => {
                let first = from_tuple_rows.first().unwrap();
                quote! {fn from_tuple(tuple: Self::Tuple) -> Self { #struct_name(#first) }}
            }
            _ => {
                quote! {fn from_tuple(tuple: Self::Tuple) -> Self { #struct_name(#(#from_tuple_rows),*) }}
            }
        },
        Fields::Unit => {
            quote! {fn from_tuple(tuple: Self::Tuple) -> Self {#struct_name} }
        }
    };

    let into_tuple_rows: Vec<_> = fields
        .iter()
        .map(StructFieldEntry::get_convert_to_tuple_row)
        .collect();

    let into_tuple_impl = match &data_struct.fields {
        Fields::Unnamed(_) | Fields::Named(_) => match fields.len() {
            0 => quote! {fn into_tuple(self) -> Self::Tuple { () }},
            1 => {
                let first = into_tuple_rows.first().unwrap();
                quote! {fn into_tuple(self) -> Self::Tuple { #first }}
            }
            _ => quote! {fn into_tuple(self) -> Self::Tuple { (#(#into_tuple_rows),*,) }},
        },
        Fields::Unit => {
            quote! {fn into_tuple(self) -> Self::Tuple { () }}
        }
    };

    let associated_type_assign = match &data_struct.fields {
        Fields::Unnamed(_) | Fields::Named(_) => match fields.len() {
            0 => quote! {type Tuple = ();},
            1 => {
                let first = tuple_types.first().unwrap();
                quote! {type Tuple = #first;}
            }
            _ => quote! {type Tuple = (#(#tuple_types),*,);},
        },
        Fields::Unit => {
            quote! {type Tuple = ();}
        }
    };

    let decompose_impl = quote! {
        #[automatically_derived]
        #impl_statement {
            #associated_type_assign

            #into_tuple_impl

            #from_tuple_impl

            fn from_tuco<TFromTuco: Tuco<Tuple = Self::Tuple>>(value: TFromTuco) -> Self {
                Self::from_tuple(value.into_tuple())
            }
        }
    };

    decompose_impl.into()
}