dyn_struct_derive2 0.1.0

Derive macros for the `dyn_struct` crate. Supports arbitrary unsized types, not just slices
Documentation
use proc_macro2::TokenStream;
use quote::quote;

#[proc_macro_derive(DynStruct)]
pub fn derive_dyn_struct(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let input = syn::parse_macro_input!(input as syn::DeriveInput);

    let output = match expand(input) {
        Ok(output) => output,
        Err(e) => e.to_compile_error(),
    };

    output.into()
}

macro_rules! err {
    ($span:expr, $($fmt:tt)+) => {
        syn::Error::new_spanned($span, format_args!($($fmt)+))
    }
}

fn expand(input: syn::DeriveInput) -> syn::Result<TokenStream> {
    match &input.data {
        syn::Data::Struct(struc) => {
            check_repr(&input)?;

            let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();

            let (sized_fields, dynamic_field) = collect_fields(struc)?;

            let single = syn::Ident::new(
                &format!("{}_DynStruct_Single", input.ident),
                input.ident.span(),
            );

            let phantom_field;
            let phantom_init;
            if input.generics.lt_token.is_some() {
                let variables = input.generics.params.iter().map(|param| match param {
                    syn::GenericParam::Type(ty) => {
                        let ident = &ty.ident;
                        quote! { #ident }
                    }
                    syn::GenericParam::Lifetime(life) => {
                        let lifetime = &life.lifetime;
                        quote! { &#lifetime () }
                    }
                    syn::GenericParam::Const(constant) => {
                        let ident = &constant.ident;
                        quote! { [(); #ident] }
                    }
                });

                phantom_field = quote! {
                    __DynStruct_phantom: std::marker::PhantomData<(#(#variables,)*)>
                };
                phantom_init = quote! { __DynStruct_phantom: std::marker::PhantomData };
            } else {
                phantom_field = quote! {};
                phantom_init = quote! {};
            };

            let single_definition;
            let single_init;
            let single_idents: Vec<syn::Ident>;
            if matches!(struc.fields, syn::Fields::Named(_)) {
                single_definition = quote! {
                    #[repr(C)]
                    #[derive(Copy, Clone)]
                    struct #single #impl_generics #where_clause {
                        #(#sized_fields,)*
                        #phantom_field
                    }
                };
                single_idents = sized_fields
                    .iter()
                    .map(|field| field.ident.clone().unwrap())
                    .collect();
                single_init = quote! { #single { #(#single_idents,)* #phantom_init } };
            } else {
                single_definition = quote! {
                    #[repr(C)]
                    #[derive(Copy, Clone)]
                    struct #single #impl_generics ( #(#sized_fields,)* #phantom_init ) #where_clause;
                };
                single_idents = sized_fields
                    .iter()
                    .enumerate()
                    .map(|(i, field)| syn::Ident::new(&format!("_{}", i), span(field)))
                    .collect();
                single_init = quote! { #single ( #(#single_idents,)*, #phantom_init ) };
            };

            let sized_parameters = sized_fields.iter().enumerate().map(|(i, field)| {
                let name = &single_idents[i];
                let ty = &field.ty;
                quote! { #name: #ty }
            });

            let dynamic_type = &dynamic_field.ty;
            let dynamic_name = dynamic_field
                .ident
                .clone()
                .unwrap_or_else(|| syn::Ident::new("tail", span(dynamic_type)));

            let struct_ident = &input.ident;
            Ok(quote! {
                impl #impl_generics #struct_ident #type_generics #where_clause {
                    pub fn new(#(#sized_parameters,)* #dynamic_name: dyn_struct2::DynArg<#dynamic_type>) -> Box<Self>{
                        #single_definition

                        let header: #single #type_generics = #single_init;

                        let dyn_struct = dyn_struct2::DynStruct::new(header, #dynamic_name);
                        unsafe { dyn_struct.transmute() }
                    }
                }
            })
        }
        _ => Err(err!(
            &input.ident,
            "`DynStruct` can only be derived for structs"
        )),
    }
}

fn span<T: syn::spanned::Spanned>(value: &T) -> proc_macro2::Span {
    value.span()
}

fn collect_fields(struc: &syn::DataStruct) -> syn::Result<(Vec<syn::Field>, syn::Field)> {
    let mut fields = struc.fields.clone();

    for field in fields.iter_mut() {
        field.attrs.clear();
        field.vis = syn::Visibility::Inherited;
    }

    let dynamic = match &mut fields {
        syn::Fields::Named(fields) => fields.named.pop(),
        syn::Fields::Unnamed(fields) => fields.unnamed.pop(),
        syn::Fields::Unit => None,
    };

    let dynamic =
        dynamic.ok_or_else(|| err!(&struc.fields, "cannot derive `DynStruct` for empty struct"))?;

    Ok((fields.into_iter().collect(), dynamic.into_value()))
}

fn check_repr(input: &syn::DeriveInput) -> syn::Result<()> {
    if input.attrs.iter().any(|attr| is_repr_c(attr)) {
        Ok(())
    } else {
        Err(err!(
            &input.ident,
            "`DynStruct` can only be derived for structs with `#[repr(C)]`"
        ))
    }
}

fn is_repr_c(attr: &syn::Attribute) -> bool {
    match attr.path.get_ident() {
        Some(ident) if ident == "repr" => {}
        _ => return false,
    }

    match find_ident(attr.tokens.clone()) {
        Some(ident) if ident == "C" => true,
        _ => false,
    }
}

fn find_ident(tokens: TokenStream) -> Option<syn::Ident> {
    tokens.into_iter().find_map(|tree| match tree {
        quote::__private::TokenTree::Group(group) => find_ident(group.stream()),
        quote::__private::TokenTree::Ident(ident) => Some(ident.clone()),
        _ => None,
    })
}