mkutils-macros 0.1.186

Utility methods, traits, and types.
Documentation
use crate::error::Error;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use syn::{
    Data, DeriveInput, Error as SynError, Field, Fields, FieldsNamed, FieldsUnnamed, Ident, Visibility,
    parse::{Parse, ParseStream},
};

struct ConstructorArgs {
    visibility: Visibility,
    name: Ident,
}

impl ConstructorArgs {
    const DEFAULT_VISIBILITY: Visibility = Visibility::Inherited;
    const DEFAULT_NAME: &str = "new";

    fn default_name() -> Ident {
        quote::format_ident!("{}", Self::DEFAULT_NAME)
    }
}

impl Default for ConstructorArgs {
    fn default() -> Self {
        let visibility = Self::DEFAULT_VISIBILITY;
        let name = Self::default_name();

        Self { visibility, name }
    }
}

impl Parse for ConstructorArgs {
    fn parse(parse_stream: ParseStream) -> Result<Self, SynError> {
        let visibility = parse_stream.parse()?;
        let name = if parse_stream.is_empty() {
            Self::default_name()
        } else {
            parse_stream.parse()?
        };
        let constructor_args = Self { visibility, name };

        Ok(constructor_args)
    }
}

pub struct Constructor;

impl Constructor {
    const ATTRIBUTE_NAME: &str = "constructor";

    fn get_constructor_args(input: &DeriveInput) -> Result<ConstructorArgs, SynError> {
        for attribute in &input.attrs {
            if attribute.path().is_ident(Self::ATTRIBUTE_NAME) {
                return attribute.parse_args();
            }
        }

        Ok(ConstructorArgs::default())
    }

    fn ident_field_pair(field: &Field) -> Result<(&Ident, &Field), SynError> {
        let Some(ident) = &field.ident else {
            return Err(Error::c_struct_field_missing_name(field));
        };
        let ident_field_pair = (ident, field);

        Ok(ident_field_pair)
    }

    fn constructor_method_block_for_c_struct(
        fields_named: &FieldsNamed,
        ConstructorArgs { visibility, name }: &ConstructorArgs,
    ) -> Result<TokenStream2, SynError> {
        let ident_field_pairs = fields_named
            .named
            .iter()
            .map(Self::ident_field_pair)
            .collect::<Result<Vec<(&Ident, &Field)>, SynError>>()?;
        let mut constructor_parameter_idents = Vec::new();
        let mut constructor_parameters = Vec::new();

        for (constructor_parameter_ident, field) in ident_field_pairs {
            let field_type = &field.ty;
            let constructor_parameter = quote::quote! { #constructor_parameter_ident: #field_type };

            constructor_parameter_idents.push(constructor_parameter_ident);
            constructor_parameters.push(constructor_parameter);
        }

        let constructor_method_block = quote::quote! {
            #visibility fn #name(#(#constructor_parameters),*) -> Self {
                Self { #(#constructor_parameter_idents),* }
            }
        };

        Ok(constructor_method_block)
    }

    fn constructor_method_block_for_tuple_struct(
        fields_unnamed: &FieldsUnnamed,
        ConstructorArgs { visibility, name }: &ConstructorArgs,
    ) -> TokenStream2 {
        let mut constructor_parameter_idents = Vec::new();
        let mut constructor_parameters = Vec::new();

        for (index, field) in fields_unnamed.unnamed.iter().enumerate() {
            let field_type = &field.ty;
            let constructor_parameter_ident = quote::format_ident!("field_{index}");
            let constructor_parameter = quote::quote! { #constructor_parameter_ident: #field_type };

            constructor_parameter_idents.push(constructor_parameter_ident);
            constructor_parameters.push(constructor_parameter);
        }

        quote::quote! {
            #visibility fn #name(#(#constructor_parameters),*) -> Self {
                Self(#(#constructor_parameter_idents),*)
            }
        }
    }

    fn constructor_method_block_for_unit_struct(
        ConstructorArgs { visibility, name }: &ConstructorArgs,
    ) -> TokenStream2 {
        quote::quote! {
            #visibility fn #name() -> Self {
                Self
            }
        }
    }

    fn constructor_method_block(
        input: &DeriveInput,
        constructor_args: &ConstructorArgs,
    ) -> Result<TokenStream2, SynError> {
        let Data::Struct(data_struct) = &input.data else {
            return Err(Error::unsupported_item_type(input));
        };
        let constructor_method_block = match &data_struct.fields {
            Fields::Named(fields_named) => Self::constructor_method_block_for_c_struct(fields_named, constructor_args)?,
            Fields::Unnamed(fields_unnamed) => {
                Self::constructor_method_block_for_tuple_struct(fields_unnamed, constructor_args)
            }
            Fields::Unit => Self::constructor_method_block_for_unit_struct(constructor_args),
        };

        Ok(constructor_method_block)
    }

    fn derive_impl(input: &DeriveInput) -> Result<TokenStream2, SynError> {
        let constructor_args = Self::get_constructor_args(input)?;
        let constructor_method_block = Self::constructor_method_block(input, &constructor_args)?;
        let input_ident = &input.ident;
        let (impl_generics, input_generics, input_where_clause) = input.generics.split_for_impl();
        let impl_block_token_stream = quote::quote! {
            impl #impl_generics #input_ident #input_generics #input_where_clause {
                #constructor_method_block
            }
        };

        Ok(impl_block_token_stream)
    }

    pub fn derive(input_token_stream: TokenStream) -> TokenStream {
        let input = syn::parse_macro_input!(input_token_stream);

        Self::derive_impl(&input)
            .unwrap_or_else(SynError::into_compile_error)
            .into()
    }
}