use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::Ident;
use weaveffi_core::abi::CType;
use weaveffi_core::model::{EnumBinding, FieldBinding, RichEnumBinding, RichVariantBinding};
use super::helpers::{ident, is_copy, ret_arrow, rust_type_ident, sentinel, slot_tokens};
use super::marshal::{field_as_param, lift_param, lower_value};
pub(crate) fn gen_enum(e: &EnumBinding, item: Option<&syn::ItemEnum>) -> syn::Result<TokenStream> {
if let Some(rich) = &e.rich {
let item = item.ok_or_else(|| {
syn::Error::new(
Span::call_site(),
format!("internal error: no source enum for rich enum `{}`", e.name),
)
})?;
return gen_rich_enum(e, rich, item);
}
let ty = rust_type_ident(&e.name);
let arms = e.variants.iter().map(|v| {
let value = v.value;
let vident = ident(&v.name);
quote!(#value => ::std::option::Option::Some(Self::#vident),)
});
Ok(quote! {
#[allow(dead_code)]
impl #ty {
#[doc(hidden)]
pub fn __weaveffi_from_i32(__v: i32) -> ::std::option::Option<Self> {
match __v {
#(#arms)*
_ => ::std::option::Option::None,
}
}
}
})
}
fn variant_has_fields(v: &RichVariantBinding) -> bool {
!v.fields.is_empty()
}
fn gen_rich_enum(
e: &EnumBinding,
rich: &RichEnumBinding,
item: &syn::ItemEnum,
) -> syn::Result<TokenStream> {
let ty = rust_type_ident(&e.name);
for v in &item.variants {
if matches!(v.fields, syn::Fields::Unnamed(_)) {
return Err(syn::Error::new_spanned(
v,
"weaveffi: tuple-style rich-enum variants are not supported; use named fields",
));
}
}
let tag_sym = ident(&rich.tag_symbol);
let tag_arms = rich.variants.iter().map(|v| {
let value = v.value;
let vident = ident(&v.name);
if variant_has_fields(v) {
quote!(#ty::#vident { .. } => #value,)
} else {
quote!(#ty::#vident => #value,)
}
});
let tag = quote! {
#[no_mangle]
#[allow(unsafe_code, clippy::not_unsafe_ptr_arg_deref, clippy::missing_safety_doc)]
pub extern "C" fn #tag_sym(ptr: *const #ty) -> i32 {
assert!(!ptr.is_null());
match unsafe { &*ptr } {
#(#tag_arms)*
}
}
};
let destroy_sym = ident(&rich.destroy_symbol);
let destroy = quote! {
#[no_mangle]
#[allow(unsafe_code, clippy::not_unsafe_ptr_arg_deref)]
pub extern "C" fn #destroy_sym(ptr: *mut #ty) {
if ptr.is_null() {
return;
}
unsafe { drop(::std::boxed::Box::from_raw(ptr)) };
}
};
let mut variants = TokenStream::new();
for v in &rich.variants {
let vident = ident(&v.name);
let create_sym = ident(&v.create.symbol);
let create_params: Vec<TokenStream> = v.create.params.iter().map(slot_tokens).collect();
let create_sentinel = quote!(::std::ptr::null_mut());
let mut create_pre = TokenStream::new();
let mut field_inits: Vec<TokenStream> = Vec::new();
for field in &v.fields {
let pb = field_as_param(field);
let (pre, _) = lift_param(&pb, false, &create_sentinel)?;
create_pre.extend(pre);
let fname = ident(&field.name);
field_inits.push(quote!(#fname: #fname));
}
let ctor_value = if variant_has_fields(v) {
quote!(#ty::#vident { #(#field_inits),* })
} else {
quote!(#ty::#vident)
};
variants.extend(quote! {
#[no_mangle]
#[allow(unsafe_code, clippy::not_unsafe_ptr_arg_deref, clippy::missing_safety_doc)]
pub extern "C" fn #create_sym(#(#create_params),*) -> *mut #ty {
#create_pre
::weaveffi::abi::error_set_ok(out_err);
::std::boxed::Box::into_raw(::std::boxed::Box::new(#ctor_value))
}
});
for field in &v.fields {
variants.extend(gen_variant_getter(&ty, &vident, field)?);
}
}
Ok(quote! {
#tag
#destroy
#variants
})
}
fn gen_variant_getter(
ty: &Ident,
vident: &Ident,
field: &FieldBinding,
) -> syn::Result<TokenStream> {
let sym = ident(&field.getter_symbol);
let fname = ident(&field.name);
let mut params: Vec<TokenStream> = vec![quote!(ptr: *const #ty)];
params.extend(field.getter_out_params.iter().map(slot_tokens));
let arrow = ret_arrow(&field.getter_ret);
let sent = sentinel(&field.getter_ret);
let read = if is_copy(&field.ty) {
quote!(*#fname)
} else {
quote!(#fname.clone())
};
let lowered = lower_value(&field.ty, read)?;
let guard = if matches!(field.getter_ret, CType::Void) {
quote!(return;)
} else {
quote!(return #sent;)
};
let miss = if matches!(field.getter_ret, CType::Void) {
quote!(_ => {})
} else {
quote!(_ => #sent,)
};
Ok(quote! {
#[no_mangle]
#[allow(unsafe_code, clippy::not_unsafe_ptr_arg_deref, clippy::missing_safety_doc)]
pub extern "C" fn #sym(#(#params),*) #arrow {
if ptr.is_null() {
#guard
}
match unsafe { &*ptr } {
#ty::#vident { #fname, .. } => #lowered,
#miss
}
}
})
}