diff options
Diffstat (limited to 'third_party/rust/uniffi_macros/src/enum_.rs')
-rw-r--r-- | third_party/rust/uniffi_macros/src/enum_.rs | 160 |
1 files changed, 160 insertions, 0 deletions
diff --git a/third_party/rust/uniffi_macros/src/enum_.rs b/third_party/rust/uniffi_macros/src/enum_.rs new file mode 100644 index 0000000000..c4e49beb8b --- /dev/null +++ b/third_party/rust/uniffi_macros/src/enum_.rs @@ -0,0 +1,160 @@ +use proc_macro2::{Ident, Span, TokenStream}; +use quote::quote; +use syn::{punctuated::Punctuated, Data, DeriveInput, Field, Index, Token, Variant}; +use uniffi_meta::{EnumMetadata, FieldMetadata, VariantMetadata}; + +use crate::{ + export::metadata::convert::convert_type, + util::{assert_type_eq, create_metadata_static_var, try_read_field}, +}; + +pub fn expand_enum(input: DeriveInput, module_path: Vec<String>) -> TokenStream { + let variants = match input.data { + Data::Enum(e) => Some(e.variants), + _ => None, + }; + + let ident = &input.ident; + + let ffi_converter_impl = enum_ffi_converter_impl(variants.as_ref(), ident); + + let meta_static_var = if let Some(variants) = variants { + match enum_metadata(ident, variants, module_path) { + Ok(metadata) => create_metadata_static_var(ident, metadata.into()), + Err(e) => e.into_compile_error(), + } + } else { + syn::Error::new(Span::call_site(), "This derive must only be used on enums") + .into_compile_error() + }; + + let type_assertion = assert_type_eq(ident, quote! { crate::uniffi_types::#ident }); + + quote! { + #ffi_converter_impl + #meta_static_var + #type_assertion + } +} + +pub(crate) fn enum_ffi_converter_impl( + variants: Option<&Punctuated<Variant, Token![,]>>, + ident: &Ident, +) -> TokenStream { + let (write_impl, try_read_impl) = match variants { + Some(variants) => { + let write_match_arms = variants.iter().enumerate().map(|(i, v)| { + let v_ident = &v.ident; + let fields = v.fields.iter().map(|f| &f.ident); + let idx = Index::from(i + 1); + let write_fields = v.fields.iter().map(write_field); + + quote! { + Self::#v_ident { #(#fields),* } => { + ::uniffi::deps::bytes::BufMut::put_i32(buf, #idx); + #(#write_fields)* + } + } + }); + let write_impl = quote! { + match obj { #(#write_match_arms)* } + }; + + let try_read_match_arms = variants.iter().enumerate().map(|(i, v)| { + let idx = Index::from(i + 1); + let v_ident = &v.ident; + let try_read_fields = v.fields.iter().map(try_read_field); + + quote! { + #idx => Self::#v_ident { #(#try_read_fields)* }, + } + }); + let error_format_string = format!("Invalid {ident} enum value: {{}}"); + let try_read_impl = quote! { + ::uniffi::check_remaining(buf, 4)?; + + Ok(match ::uniffi::deps::bytes::Buf::get_i32(buf) { + #(#try_read_match_arms)* + v => ::uniffi::deps::anyhow::bail!(#error_format_string, v), + }) + }; + + (write_impl, try_read_impl) + } + None => { + let unimplemented = quote! { ::std::unimplemented!() }; + (unimplemented.clone(), unimplemented) + } + }; + + quote! { + #[automatically_derived] + impl ::uniffi::RustBufferFfiConverter for #ident { + type RustType = Self; + + fn write(obj: Self, buf: &mut ::std::vec::Vec<u8>) { + #write_impl + } + + fn try_read(buf: &mut &[::std::primitive::u8]) -> ::uniffi::deps::anyhow::Result<Self> { + #try_read_impl + } + } + } +} + +fn enum_metadata( + ident: &Ident, + variants: Punctuated<Variant, Token![,]>, + module_path: Vec<String>, +) -> syn::Result<EnumMetadata> { + let name = ident.to_string(); + let variants = variants + .iter() + .map(variant_metadata) + .collect::<syn::Result<_>>()?; + + Ok(EnumMetadata { + module_path, + name, + variants, + }) +} + +pub(crate) fn variant_metadata(v: &Variant) -> syn::Result<VariantMetadata> { + let name = v.ident.to_string(); + let fields = v + .fields + .iter() + .map(|f| field_metadata(f, v)) + .collect::<syn::Result<_>>()?; + + Ok(VariantMetadata { name, fields }) +} + +fn field_metadata(f: &Field, v: &Variant) -> syn::Result<FieldMetadata> { + let name = f + .ident + .as_ref() + .ok_or_else(|| { + syn::Error::new_spanned( + v, + "UniFFI only supports enum variants with named fields (or no fields at all)", + ) + })? + .to_string(); + + Ok(FieldMetadata { + name, + ty: convert_type(&f.ty)?, + }) +} + +fn write_field(f: &Field) -> TokenStream { + let ident = &f.ident; + let ty = &f.ty; + + quote! { + <#ty as ::uniffi::FfiConverter>::write(#ident, buf); + } +} |