ezffi-macros 0.1.1

Proc-macros backing the ezffi crate
Documentation
use syn::{Generics, Ident, ItemStruct};

use crate::Namer;
use quote::quote;

pub fn is_c_compatible_struct(item: &ItemStruct) -> bool {
    super::FFITypeResolver::is_rust_type_c_compatible(&item.ident.to_string())
}

pub fn expand_struct(item: &ItemStruct) -> syn::Result<proc_macro2::TokenStream> {
    if let Some(lt) = item.generics.lifetimes().next() {
        return Err(syn::Error::new_spanned(
            lt,
            "lifetime parameters are not supported by #[ezffi::export]",
        ));
    }

    if item.generics.gt_token.is_some() {
        Ok(expand_generic_type(&item.ident, &item.generics))
    } else {
        Ok(expand_type(&item.ident))
    }
}

pub fn expand_c_struct(item: &ItemStruct) -> proc_macro2::TokenStream {
    let rust_ty_name = &item.ident;
    let c_ty_name = Namer::name_struct(rust_ty_name);
    let fields = &item.fields;
    let attrs = &item.attrs;

    let has_repr = attrs.iter().any(|a| a.path().is_ident("repr"));
    let repr = if has_repr {
        quote! {}
    } else {
        quote! { #[repr(C)] }
    };

    // Named fields render as `{ ... }` (no semicolon); tuple fields as
    // `( ... )` and need a trailing `;`.
    let semi = match fields {
        syn::Fields::Named(_) => quote! {},
        _ => quote! { ; },
    };

    quote! {
        #[derive(Clone, Copy)]
        #repr
        #(#attrs)*
        pub struct #c_ty_name #fields #semi

        pub type #rust_ty_name = #c_ty_name;

        impl ::ezffi::RustRefIntoC<()> for #c_ty_name {
            type C = #c_ty_name;

            unsafe fn ref_into_c(&self) -> Self::C { *self }
        }

        impl ::ezffi::RustOwnedIntoC<()> for #c_ty_name {
            type C = #c_ty_name;

            unsafe fn owned_into_c(self) -> Self::C { self }
        }

        impl ::ezffi::CRefIntoRust<#c_ty_name> for #c_ty_name {
            unsafe fn into_rust(&self) -> &#c_ty_name { self }
            unsafe fn into_rust_mut(&mut self) -> &mut #c_ty_name { self }
        }

        impl ::ezffi::COwnedIntoRust<#c_ty_name> for #c_ty_name {
            unsafe fn into_rust_owned(self) -> #c_ty_name { self }
        }
    }
}

pub fn expand_type(ty_name: &Ident) -> proc_macro2::TokenStream {
    let c_ty_name = Namer::name_struct(ty_name);
    let free_fn_name = Namer::name_free_fn(ty_name);

    quote! {
        #[doc(hidden)]
        #[derive(Clone, Copy)]
        #[repr(C)]
        pub struct #c_ty_name {
            inner: *mut core::ffi::c_void,
        }

        const _: () = {
            impl ::ezffi::RustRefIntoC<()> for #ty_name {
                type C = #c_ty_name;

                #[inline]
                unsafe fn ref_into_c(&self) -> Self::C {
                    #c_ty_name {
                        inner: self as *const Self as *mut core::ffi::c_void,
                    }
                }
            }

            impl ::ezffi::RustOwnedIntoC<()> for #ty_name {
                type C = #c_ty_name;

                #[inline]
                unsafe fn owned_into_c(self) -> Self::C {
                    #c_ty_name {
                        inner: Box::into_raw(Box::new(self)) as *mut core::ffi::c_void,
                    }
                }
            }
        };

        impl<T> ::ezffi::CRefIntoRust<T> for #c_ty_name {
            #[inline]
            unsafe fn into_rust(&self) -> &T {
                unsafe { &*(self.inner as *mut T) }
            }

            #[inline]
            unsafe fn into_rust_mut(&mut self) -> &mut T {
                unsafe { &mut *(self.inner as *mut T) }
            }
        }

        impl<T> ::ezffi::COwnedIntoRust<T> for #c_ty_name {
            #[inline]
            unsafe fn into_rust_owned(self) -> T {
                unsafe { *Box::from_raw(self.inner as *mut T) }
            }
        }

        #[doc(hidden)]
        #[unsafe(no_mangle)]
        pub unsafe extern "C" fn #free_fn_name(o: *const #c_ty_name) {
            let _ = unsafe { Box::from_raw((*o).inner as *mut #ty_name) };
        }
    }
}

fn expand_generic_type(ty_name: &Ident, generics: &Generics) -> proc_macro2::TokenStream {
    let c_ty_name = Namer::name_struct(ty_name);
    let free_fn_name = Namer::name_free_fn(ty_name);

    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
    let turbofish = ty_generics.as_turbofish();

    quote! {
        #[doc(hidden)]
        #[derive(Clone, Copy)]
        #[repr(C)]
        pub struct #c_ty_name {
            inner: *mut core::ffi::c_void,
            drop_fn: unsafe extern "C" fn(*mut core::ffi::c_void),
        }

        const _: () = {
            // Per-monomorphization drop, captured as a fn pointer at
            // construction time so the free fn deallocates with the right Layout.
            unsafe extern "C" fn __ezffi_drop #impl_generics (
                p: *mut core::ffi::c_void,
            ) #where_clause {
                let _ = unsafe { Box::from_raw(p as *mut #ty_name #ty_generics) };
            }

            impl #impl_generics ::ezffi::RustRefIntoC<()>
                for #ty_name #ty_generics #where_clause
            {
                type C = #c_ty_name;

                #[inline]
                unsafe fn ref_into_c(&self) -> Self::C {
                    #c_ty_name {
                        inner: self as *const Self as *mut core::ffi::c_void,
                        drop_fn: __ezffi_drop #turbofish,
                    }
                }
            }

            impl #impl_generics ::ezffi::RustOwnedIntoC<()>
                for #ty_name #ty_generics #where_clause
            {
                type C = #c_ty_name;

                #[inline]
                unsafe fn owned_into_c(self) -> Self::C {
                    #c_ty_name {
                        inner: Box::into_raw(Box::new(self)) as *mut core::ffi::c_void,
                        drop_fn: __ezffi_drop #turbofish,
                    }
                }
            }
        };

        impl<T> ::ezffi::CRefIntoRust<T> for #c_ty_name {
            #[inline]
            unsafe fn into_rust(&self) -> &T {
                unsafe { &*(self.inner as *mut T) }
            }

            #[inline]
            unsafe fn into_rust_mut(&mut self) -> &mut T {
                unsafe { &mut *(self.inner as *mut T) }
            }
        }

        impl<T> ::ezffi::COwnedIntoRust<T> for #c_ty_name {
            #[inline]
            unsafe fn into_rust_owned(self) -> T {
                unsafe { *Box::from_raw(self.inner as *mut T) }
            }
        }

        #[doc(hidden)]
        #[unsafe(no_mangle)]
        pub unsafe extern "C" fn #free_fn_name(o: *const #c_ty_name) {
            unsafe { ((*o).drop_fn)((*o).inner); }
        }
    }
}