use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; use syn::{parse::ParseStream, Data, DataStruct, DeriveInput, Field, LitStr, Token}; use crate::{ default::{default_value_metadata_calls, DefaultValue}, ffiops, util::{ create_metadata_items, either_attribute_arg, extract_docstring, ident_to_string, kw, try_metadata_value_from_usize, try_read_field, AttributeSliceExt, UniffiAttributeArgs, }, DeriveOptions, }; /// Handle #[uniffi(...)] attributes for records #[derive(Clone, Default)] pub struct RecordAttr { pub name: Option, } impl UniffiAttributeArgs for RecordAttr { fn parse_one(input: ParseStream<'_>) -> syn::Result { let lookahead = input.lookahead1(); if lookahead.peek(kw::name) { let _: kw::name = input.parse()?; let _: Token![=] = input.parse()?; let name = Some(input.parse::()?.value()); Ok(Self { name }) } else { Err(syn::Error::new( input.span(), format!("attribute `{input}` is not supported here."), )) } } fn merge(self, other: Self) -> syn::Result { Ok(Self { name: either_attribute_arg(self.name, other.name)?, }) } } /// Stores parsed data from the Derive Input for the struct. struct RecordItem { ident: Ident, record: DataStruct, docstring: String, attr: RecordAttr, } impl RecordItem { fn new(input: DeriveInput) -> syn::Result { let record = match input.data { Data::Struct(s) => s, _ => { return Err(syn::Error::new( Span::call_site(), "This derive must only be used on structs", )); } }; let attr = input.attrs.parse_uniffi_attr_args::()?; Ok(Self { ident: input.ident, record, docstring: extract_docstring(&input.attrs)?, attr, }) } fn ident(&self) -> &Ident { &self.ident } fn foreign_name(&self) -> String { match &self.attr.name { Some(name) => name.clone(), None => ident_to_string(&self.ident), } } fn struct_(&self) -> &DataStruct { &self.record } fn docstring(&self) -> &str { self.docstring.as_str() } } pub fn expand_record(input: DeriveInput, options: DeriveOptions) -> syn::Result { let record = RecordItem::new(input)?; let ffi_converter = record_ffi_converter_impl(&record, &options).unwrap_or_else(syn::Error::into_compile_error); let meta_static_var = options .generate_metadata .then(|| record_meta_static_var(&record).unwrap_or_else(syn::Error::into_compile_error)); Ok(quote! { #ffi_converter #meta_static_var }) } fn record_ffi_converter_impl( record: &RecordItem, options: &DeriveOptions, ) -> syn::Result { let ident = record.ident(); let impl_spec = options.ffi_impl_header("FfiConverter", ident); let derive_ffi_traits = options.derive_all_ffi_traits(ident); let name = &record.foreign_name(); let write_impl: TokenStream = record.struct_().fields.iter().map(write_field).collect(); let try_read_fields: TokenStream = record.struct_().fields.iter().map(try_read_field).collect(); Ok(quote! { #[automatically_derived] unsafe #impl_spec { ::uniffi::ffi_converter_rust_buffer_lift_and_lower!(crate::UniFfiTag); fn write(obj: Self, buf: &mut ::std::vec::Vec) { #write_impl } fn try_read(buf: &mut &[::std::primitive::u8]) -> ::uniffi::deps::anyhow::Result { ::std::result::Result::Ok(Self { #try_read_fields }) } const TYPE_ID_META: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::TYPE_RECORD) .concat_str(module_path!()) .concat_str(#name); } #derive_ffi_traits }) } fn write_field(f: &Field) -> TokenStream { let ident = &f.ident; let write = ffiops::write(&f.ty); quote! { #write(obj.#ident, buf); } } #[derive(Default)] pub struct FieldAttributeArguments { pub(crate) default: Option, } impl UniffiAttributeArgs for FieldAttributeArguments { fn parse_one(input: ParseStream<'_>) -> syn::Result { let _: kw::default = input.parse()?; let lookahead = input.lookahead1(); let default = if lookahead.peek(Token![=]) { let _: Token![=] = input.parse()?; input.parse()? } else { DefaultValue::Default }; Ok(Self { default: Some(default), }) } fn merge(self, other: Self) -> syn::Result { Ok(Self { default: either_attribute_arg(self.default, other.default)?, }) } } fn record_meta_static_var(record: &RecordItem) -> syn::Result { let name = &record.foreign_name(); let docstring = record.docstring(); let fields_len = try_metadata_value_from_usize( record.struct_().fields.len(), "UniFFI limits structs to 256 fields", )?; let concat_fields: TokenStream = record .struct_() .fields .iter() .map(|f| { let attrs = f .attrs .parse_uniffi_attr_args::()?; let name = ident_to_string(f.ident.as_ref().unwrap()); let docstring = extract_docstring(&f.attrs)?; let default = default_value_metadata_calls(&attrs.default)?; let type_id_meta = ffiops::type_id_meta(&f.ty); // Note: fields need to implement both `Lower` and `Lift` to be used in a record. The // TYPE_ID_META should be the same for both traits. Ok(quote! { .concat_str(#name) .concat(#type_id_meta) #default .concat_long_str(#docstring) }) }) .collect::>()?; Ok(create_metadata_items( "record", name, quote! { ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::RECORD) .concat_str(module_path!()) .concat_str(#name) .concat_value(#fields_len) #concat_fields .concat_long_str(#docstring) }, None, )) }