xabi-macros 0.1.2

Procedural macros for xabi
Documentation
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{Ident, Type};

use super::{MethodRet, MethodSpec, generated_trait_type_path};

impl MethodSpec {
    pub(crate) fn export_thunk(&self, trait_ident: &Ident) -> syn::Result<TokenStream2> {
        if self.asyncness {
            return self.async_export_thunk(trait_ident);
        }

        let name = &self.name;
        let impl_ref = self.impl_ref_expr();
        if self.args.is_empty() {
            match self.ret {
                MethodRet::String => {
                    return Ok(quote! {
                        unsafe extern "C" fn #name<P: #trait_ident>(
                            instance: *mut std::ffi::c_void,
                        ) -> ::xabi::XabiOwnedBytes {
                            ::xabi::catch_unwind_owned(|| {
                                let Some(plugin) = #impl_ref else {
                                    return ::xabi::XabiOwnedBytes::empty();
                                };
                                ::xabi::XabiOwnedBytes::from_string(plugin.#name())
                            })
                        }
                    });
                }
                MethodRet::U32 => {
                    return Ok(quote! {
                        unsafe extern "C" fn #name<P: #trait_ident>(
                            instance: *mut std::ffi::c_void,
                        ) -> u32 {
                            ::xabi::catch_unwind_or(0, || {
                                let Some(plugin) = #impl_ref else {
                                    return 0;
                                };
                                plugin.#name()
                            })
                        }
                    });
                }
                MethodRet::Bool => {
                    return Ok(quote! {
                        unsafe extern "C" fn #name<P: #trait_ident>(
                            instance: *mut std::ffi::c_void,
                        ) -> u8 {
                            ::xabi::catch_unwind_or(0, || {
                                let Some(plugin) = #impl_ref else {
                                    return 0;
                                };
                                plugin.#name() as u8
                            })
                        }
                    });
                }
                MethodRet::Value(_) => {
                    return Ok(quote! {
                        unsafe extern "C" fn #name<P: #trait_ident>(
                            instance: *mut std::ffi::c_void,
                        ) -> ::xabi::XabiOwnedBytes {
                            ::xabi::catch_unwind_owned(|| {
                                let Some(plugin) = #impl_ref else {
                                    return ::xabi::XabiOwnedBytes::empty();
                                };
                                ::xabi::XabiType::into_payload(plugin.#name())
                            })
                        }
                    });
                }
                _ => {}
            }
        }

        let ffi_args = self.ffi_arg_defs();
        let (decoders, call_args) = self.export_arg_decoding(false);
        let ok = self.sync_ok_payload();
        Ok(quote! {
            unsafe extern "C" fn #name<P: #trait_ident>(
                instance: *mut std::ffi::c_void,
                #(#ffi_args)*
                out: *mut ::xabi::XabiOwnedBytes,
            ) -> i32 {
                ::xabi::catch_unwind_code(|| {
                    let Some(plugin) = #impl_ref else {
                        return ::xabi::ERR_INVALID_ARGUMENT;
                    };
                    let Some(out) = (unsafe { out.as_mut() }) else {
                        return ::xabi::ERR_INVALID_ARGUMENT;
                    };
                    #(#decoders)*
                    match plugin.#name(#(#call_args)*) {
                        Ok(value) => {
                            #ok
                            ::xabi::OK
                        }
                        Err(err) => {
                            *out = ::xabi::XabiType::into_payload(err);
                            ::xabi::ERR_EXPORT
                        }
                    }
                })
            }
        })
    }

    fn async_export_thunk(&self, trait_ident: &Ident) -> syn::Result<TokenStream2> {
        let name = &self.name;
        let impl_ref = self.impl_ref_expr();
        let ffi_args = self.ffi_arg_defs();
        let (decoders, call_args) = self.export_arg_decoding(true);
        let future = self.async_future_assignment(name, &call_args);

        Ok(quote! {
            unsafe extern "C" fn #name<P: #trait_ident>(
                instance: *mut std::ffi::c_void,
                #(#ffi_args)*
                out: *mut ::xabi::XabiFuture,
            ) -> i32 {
                ::xabi::catch_unwind_code(|| {
                    let Some(plugin) = #impl_ref else {
                        return ::xabi::ERR_INVALID_ARGUMENT;
                    };
                    let Some(out) = (unsafe { out.as_mut() }) else {
                        return ::xabi::ERR_INVALID_ARGUMENT;
                    };
                    #(#decoders)*
                    #future
                    ::xabi::OK
                })
            }
        })
    }

    fn sync_ok_payload(&self) -> TokenStream2 {
        match &self.ret {
            MethodRet::ResultUnit(_) => quote! {
                *out = ::xabi::XabiOwnedBytes::empty();
            },
            MethodRet::ResultBytes(_) => quote! {
                *out = ::xabi::XabiOwnedBytes::from_vec(value);
            },
            MethodRet::ResultString(_) => quote! {
                *out = ::xabi::XabiOwnedBytes::from_string(value);
            },
            MethodRet::ResultValue { .. } => quote! {
                *out = ::xabi::XabiType::into_payload(value);
            },
            MethodRet::ResultObject { trait_path, .. } => {
                let payload = object_payload_expr(trait_path);
                quote! {
                    *out = ::xabi::XabiOwnedBytes::from_vec({
                        #payload
                    });
                }
            }
            MethodRet::ResultObjectPair { ok, trait_path, .. } => {
                let payload = object_pair_payload_expr(ok, trait_path);
                quote! {
                    let (value, object) = value;
                    *out = ::xabi::XabiOwnedBytes::from_vec({
                        #payload
                    });
                }
            }
            _ => quote! {},
        }
    }

    fn async_future_assignment(&self, name: &Ident, call_args: &[TokenStream2]) -> TokenStream2 {
        match &self.ret {
            MethodRet::ResultObject { trait_path, .. } => {
                let payload = object_payload_expr(trait_path);
                quote! {
                    *out = ::xabi::XabiFuture::from_result_bytes(async move {
                        plugin.#name(#(#call_args)*).await.map(|value| {
                            #payload
                        })
                    });
                }
            }
            MethodRet::ResultObjectPair { ok, trait_path, .. } => {
                let payload = object_pair_payload_expr(ok, trait_path);
                quote! {
                    *out = ::xabi::XabiFuture::from_result_bytes(async move {
                        plugin.#name(#(#call_args)*).await.map(|(value, object)| {
                            #payload
                        })
                    });
                }
            }
            _ => {
                let future = self.async_future_expr();
                quote! {
                        let future = async move {
                            plugin.#name(#(#call_args)*).await
                        };
                        *out = #future;
                }
            }
        }
    }

    fn impl_ref_expr(&self) -> TokenStream2 {
        if self.receiver_mut {
            quote!(Self::__xabi_impl_mut::<P>(instance))
        } else {
            quote!(Self::__xabi_impl_ref::<P>(instance))
        }
    }

    fn async_future_expr(&self) -> TokenStream2 {
        match &self.ret {
            MethodRet::ResultUnit(_) => quote! {
                ::xabi::XabiFuture::from_result_bytes(async move {
                    future.await.map(|()| Vec::new())
                })
            },
            MethodRet::ResultBytes(_) => quote! {
                ::xabi::XabiFuture::from_result_bytes(future)
            },
            MethodRet::ResultString(_) => quote! {
                ::xabi::XabiFuture::from_result_bytes(async move {
                    future.await.map(String::into_bytes)
                })
            },
            MethodRet::ResultValue { .. } => quote! {
                ::xabi::XabiFuture::from_result_value(future)
            },
            MethodRet::ResultObjectPair { .. } => quote! {
                ::xabi::XabiFuture::empty()
            },
            _ => quote! {
                ::xabi::XabiFuture::empty()
            },
        }
    }
}

fn object_payload_expr(trait_path: &syn::Path) -> TokenStream2 {
    let abi_ident = generated_trait_type_path(trait_path, "XabiV1AbiTrait");
    let ret_ident = generated_trait_type_path(trait_path, "XabiV1OwnedRefTrait");
    quote! {
        let raw = #abi_ident::xabi_export(value);
        let wire = #ret_ident {
            size: std::mem::size_of::<#ret_ident>(),
            abi_version: #ret_ident::ABI_VERSION,
            vtable: raw,
        };
        let bytes = unsafe {
            std::slice::from_raw_parts(
                std::ptr::addr_of!(wire).cast::<u8>(),
                std::mem::size_of::<#ret_ident>(),
            )
        };
        bytes.to_vec()
    }
}

fn object_pair_payload_expr(ok: &Type, trait_path: &syn::Path) -> TokenStream2 {
    let abi_ident = generated_trait_type_path(trait_path, "XabiV1AbiTrait");
    let ret_ident = generated_trait_type_path(trait_path, "XabiV1OwnedRefTrait");
    quote! {
        let raw = #abi_ident::xabi_export(object);
        let __xabi_object_wire = #ret_ident {
            size: std::mem::size_of::<#ret_ident>(),
            abi_version: #ret_ident::ABI_VERSION,
            vtable: raw,
        };
        let __xabi_ok_wire = <#ok as ::xabi::XabiType>::into_wire(value);
        #[repr(C)]
        #[derive(Clone, Copy)]
        struct __XabiResultObjectPair<OkWire: Copy + 'static, ObjectWire: Copy + 'static> {
            size: usize,
            abi_version: u32,
            ok: OkWire,
            object: ObjectWire,
        }
        let __xabi_pair_size = std::mem::size_of::<
            __XabiResultObjectPair<
                <#ok as ::xabi::XabiType>::Wire,
                #ret_ident,
            >
        >();
        let mut __xabi_wire = std::mem::MaybeUninit::<
            __XabiResultObjectPair<
                <#ok as ::xabi::XabiType>::Wire,
                #ret_ident,
            >
        >::zeroed();
        unsafe {
            let __xabi_wire_ptr = __xabi_wire.as_mut_ptr();
            std::ptr::addr_of_mut!((*__xabi_wire_ptr).size).write(__xabi_pair_size);
            std::ptr::addr_of_mut!((*__xabi_wire_ptr).abi_version).write(::xabi::ABI_VERSION);
            std::ptr::addr_of_mut!((*__xabi_wire_ptr).ok)
                .write(__xabi_ok_wire);
            std::ptr::addr_of_mut!((*__xabi_wire_ptr).object)
                .write(__xabi_object_wire);
            let __xabi_wire = __xabi_wire.assume_init();
            let bytes = std::slice::from_raw_parts(
                std::ptr::addr_of!(__xabi_wire).cast::<u8>(),
                std::mem::size_of_val(&__xabi_wire),
            );
            bytes.to_vec()
        }
    }
}