use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; use syn::DeriveInput; use crate::util::{ create_metadata_items, extract_docstring, ident_to_string, mod_path, tagged_impl_header, }; use uniffi_meta::ObjectImpl; pub fn expand_object(input: DeriveInput, udl_mode: bool) -> syn::Result { let module_path = mod_path()?; let ident = &input.ident; let docstring = extract_docstring(&input.attrs)?; let name = ident_to_string(ident); let clone_fn_ident = Ident::new( &uniffi_meta::clone_fn_symbol_name(&module_path, &name), Span::call_site(), ); let free_fn_ident = Ident::new( &uniffi_meta::free_fn_symbol_name(&module_path, &name), Span::call_site(), ); let meta_static_var = (!udl_mode).then(|| { interface_meta_static_var(ident, ObjectImpl::Struct, &module_path, docstring) .unwrap_or_else(syn::Error::into_compile_error) }); let interface_impl = interface_impl(ident, udl_mode); Ok(quote! { #[doc(hidden)] #[no_mangle] pub unsafe extern "C" fn #clone_fn_ident( ptr: *const ::std::ffi::c_void, call_status: &mut ::uniffi::RustCallStatus ) -> *const ::std::ffi::c_void { uniffi::rust_call(call_status, || { unsafe { ::std::sync::Arc::increment_strong_count(ptr) }; Ok(ptr) }) } #[doc(hidden)] #[no_mangle] pub unsafe extern "C" fn #free_fn_ident( ptr: *const ::std::ffi::c_void, call_status: &mut ::uniffi::RustCallStatus ) { uniffi::rust_call(call_status, || { assert!(!ptr.is_null()); let ptr = ptr.cast::<#ident>(); unsafe { ::std::sync::Arc::decrement_strong_count(ptr); } Ok(()) }); } #interface_impl #meta_static_var }) } pub(crate) fn interface_impl(ident: &Ident, udl_mode: bool) -> TokenStream { let name = ident_to_string(ident); let impl_spec = tagged_impl_header("FfiConverterArc", ident, udl_mode); let lower_return_impl_spec = tagged_impl_header("LowerReturn", ident, udl_mode); let lift_ref_impl_spec = tagged_impl_header("LiftRef", ident, udl_mode); let mod_path = match mod_path() { Ok(p) => p, Err(e) => return e.into_compile_error(), }; quote! { // All Object structs must be `Sync + Send`. The generated scaffolding will fail to compile // if they are not, but unfortunately it fails with an unactionably obscure error message. // By asserting the requirement explicitly, we help Rust produce a more scrutable error message // and thus help the user debug why the requirement isn't being met. uniffi::deps::static_assertions::assert_impl_all!(#ident: ::core::marker::Sync, ::core::marker::Send); #[doc(hidden)] #[automatically_derived] /// Support for passing reference-counted shared objects via the FFI. /// /// To avoid dealing with complex lifetime semantics over the FFI, any data passed /// by reference must be encapsulated in an `Arc`, and must be safe to share /// across threads. unsafe #impl_spec { // Don't use a pointer to as that requires a `pub ` type FfiType = *const ::std::os::raw::c_void; /// When lowering, we have an owned `Arc` and we transfer that ownership /// to the foreign-language code, "leaking" it out of Rust's ownership system /// as a raw pointer. This works safely because we have unique ownership of `self`. /// The foreign-language code is responsible for freeing this by calling the /// `ffi_object_free` FFI function provided by the corresponding UniFFI type. /// /// Safety: when freeing the resulting pointer, the foreign-language code must /// call the destructor function specific to the type `T`. Calling the destructor /// function for other types may lead to undefined behaviour. fn lower(obj: ::std::sync::Arc) -> Self::FfiType { ::std::sync::Arc::into_raw(obj) as Self::FfiType } /// When lifting, we receive an owned `Arc` that the foreign language code cloned. fn try_lift(v: Self::FfiType) -> ::uniffi::Result<::std::sync::Arc> { let v = v as *const #ident; Ok(unsafe { ::std::sync::Arc::::from_raw(v) }) } /// When writing as a field of a complex structure, make a clone and transfer ownership /// of it to the foreign-language code by writing its pointer into the buffer. /// The foreign-language code is responsible for freeing this by calling the /// `ffi_object_free` FFI function provided by the corresponding UniFFI type. /// /// Safety: when freeing the resulting pointer, the foreign-language code must /// call the destructor function specific to the type `T`. Calling the destructor /// function for other types may lead to undefined behaviour. fn write(obj: ::std::sync::Arc, buf: &mut Vec) { ::uniffi::deps::static_assertions::const_assert!(::std::mem::size_of::<*const ::std::ffi::c_void>() <= 8); ::uniffi::deps::bytes::BufMut::put_u64(buf, >::lower(obj) as u64); } /// When reading as a field of a complex structure, we receive a "borrow" of the `Arc` /// that is owned by the foreign-language code, and make a clone for our own use. /// /// Safety: the buffer must contain a pointer previously obtained by calling /// the `lower()` or `write()` method of this impl. fn try_read(buf: &mut &[u8]) -> ::uniffi::Result<::std::sync::Arc> { ::uniffi::deps::static_assertions::const_assert!(::std::mem::size_of::<*const ::std::ffi::c_void>() <= 8); ::uniffi::check_remaining(buf, 8)?; >::try_lift(::uniffi::deps::bytes::Buf::get_u64(buf) as Self::FfiType) } const TYPE_ID_META: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::TYPE_INTERFACE) .concat_str(#mod_path) .concat_str(#name); } unsafe #lower_return_impl_spec { type ReturnType = >::FfiType; fn lower_return(obj: Self) -> ::std::result::Result { Ok(>::lower(::std::sync::Arc::new(obj))) } const TYPE_ID_META: ::uniffi::MetadataBuffer = >::TYPE_ID_META; } unsafe #lift_ref_impl_spec { type LiftType = ::std::sync::Arc; } } } pub(crate) fn interface_meta_static_var( ident: &Ident, imp: ObjectImpl, module_path: &str, docstring: String, ) -> syn::Result { let name = ident_to_string(ident); let code = match imp { ObjectImpl::Struct => quote! { ::uniffi::metadata::codes::INTERFACE }, ObjectImpl::Trait => quote! { ::uniffi::metadata::codes::TRAIT_INTERFACE }, ObjectImpl::CallbackTrait => quote! { ::uniffi::metadata::codes::CALLBACK_TRAIT_INTERFACE }, }; Ok(create_metadata_items( "interface", &name, quote! { ::uniffi::MetadataBuffer::from_code(#code) .concat_str(#module_path) .concat_str(#name) .concat_long_str(#docstring) }, None, )) }