diff options
Diffstat (limited to '')
-rw-r--r-- | third_party/rust/uniffi_macros/src/export.rs | 211 | ||||
-rw-r--r-- | third_party/rust/uniffi_macros/src/export/attributes.rs | 306 | ||||
-rw-r--r-- | third_party/rust/uniffi_macros/src/export/callback_interface.rs | 204 | ||||
-rw-r--r-- | third_party/rust/uniffi_macros/src/export/item.rs | 88 | ||||
-rw-r--r-- | third_party/rust/uniffi_macros/src/export/scaffolding.rs | 123 | ||||
-rw-r--r-- | third_party/rust/uniffi_macros/src/export/trait_interface.rs | 183 | ||||
-rw-r--r-- | third_party/rust/uniffi_macros/src/export/utrait.rs | 23 |
7 files changed, 802 insertions, 336 deletions
diff --git a/third_party/rust/uniffi_macros/src/export.rs b/third_party/rust/uniffi_macros/src/export.rs index bbb16acf90..41657a639e 100644 --- a/third_party/rust/uniffi_macros/src/export.rs +++ b/third_party/rust/uniffi_macros/src/export.rs @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use proc_macro2::{Ident, Span, TokenStream}; +use proc_macro2::TokenStream; use quote::{quote, quote_spanned}; use syn::{visit_mut::VisitMut, Item, Type}; @@ -10,6 +10,7 @@ mod attributes; mod callback_interface; mod item; mod scaffolding; +mod trait_interface; mod utrait; use self::{ @@ -18,20 +19,16 @@ use self::{ gen_constructor_scaffolding, gen_ffi_function, gen_fn_scaffolding, gen_method_scaffolding, }, }; -use crate::{ - object::interface_meta_static_var, - util::{ident_to_string, mod_path, tagged_impl_header}, -}; -pub use attributes::ExportAttributeArguments; +use crate::util::{ident_to_string, mod_path}; +pub use attributes::{DefaultMap, ExportFnArgs, ExportedImplFnArgs}; pub use callback_interface::ffi_converter_callback_interface_impl; -use uniffi_meta::free_fn_symbol_name; // TODO(jplatte): Ensure no generics, … // TODO(jplatte): Aggregate errors instead of short-circuiting, wherever possible pub(crate) fn expand_export( mut item: Item, - args: ExportAttributeArguments, + all_args: proc_macro::TokenStream, udl_mode: bool, ) -> syn::Result<TokenStream> { let mod_path = mod_path()?; @@ -41,11 +38,17 @@ pub(crate) fn expand_export( // new functions outside of the `impl`). rewrite_self_type(&mut item); - let metadata = ExportItem::new(item, &args)?; + let metadata = ExportItem::new(item, all_args)?; match metadata { - ExportItem::Function { sig } => gen_fn_scaffolding(sig, &args, udl_mode), - ExportItem::Impl { items, self_ident } => { + ExportItem::Function { sig, args } => { + gen_fn_scaffolding(sig, &args.async_runtime, udl_mode) + } + ExportItem::Impl { + items, + self_ident, + args, + } => { if let Some(rt) = &args.async_runtime { if items .iter() @@ -61,8 +64,12 @@ pub(crate) fn expand_export( let item_tokens: TokenStream = items .into_iter() .map(|item| match item { - ImplItem::Constructor(sig) => gen_constructor_scaffolding(sig, &args, udl_mode), - ImplItem::Method(sig) => gen_method_scaffolding(sig, &args, udl_mode), + ImplItem::Constructor(sig) => { + gen_constructor_scaffolding(sig, &args.async_runtime, udl_mode) + } + ImplItem::Method(sig) => { + gen_method_scaffolding(sig, &args.async_runtime, udl_mode) + } }) .collect::<syn::Result<_>>()?; Ok(quote_spanned! { self_ident.span() => #item_tokens }) @@ -70,111 +77,51 @@ pub(crate) fn expand_export( ExportItem::Trait { items, self_ident, - callback_interface: false, - } => { - if let Some(rt) = args.async_runtime { - return Err(syn::Error::new_spanned(rt, "not supported for traits")); - } - - let name = ident_to_string(&self_ident); - let free_fn_ident = - Ident::new(&free_fn_symbol_name(&mod_path, &name), Span::call_site()); - - let free_tokens = quote! { - #[doc(hidden)] - #[no_mangle] - pub 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()); - drop(unsafe { ::std::boxed::Box::from_raw(ptr as *mut std::sync::Arc<dyn #self_ident>) }); - Ok(()) - }); - } - }; - - let impl_tokens: TokenStream = items - .into_iter() - .map(|item| match item { - ImplItem::Method(sig) => { - if sig.is_async { - return Err(syn::Error::new( - sig.span, - "async trait methods are not supported", - )); - } - gen_method_scaffolding(sig, &args, udl_mode) - } - _ => unreachable!("traits have no constructors"), - }) - .collect::<syn::Result<_>>()?; - - let meta_static_var = (!udl_mode).then(|| { - interface_meta_static_var(&self_ident, true, &mod_path) - .unwrap_or_else(syn::Error::into_compile_error) - }); - let ffi_converter_tokens = ffi_converter_trait_impl(&self_ident, false); - - Ok(quote_spanned! { self_ident.span() => - #meta_static_var - #free_tokens - #ffi_converter_tokens - #impl_tokens - }) - } + with_foreign, + callback_interface_only: false, + docstring, + args, + } => trait_interface::gen_trait_scaffolding( + &mod_path, + args, + self_ident, + items, + udl_mode, + with_foreign, + docstring, + ), ExportItem::Trait { items, self_ident, - callback_interface: true, + callback_interface_only: true, + docstring, + .. } => { let trait_name = ident_to_string(&self_ident); - let trait_impl_ident = Ident::new( - &format!("UniFFICallbackHandler{trait_name}"), - Span::call_site(), - ); - let internals_ident = Ident::new( - &format!( - "UNIFFI_FOREIGN_CALLBACK_INTERNALS_{}", - trait_name.to_ascii_uppercase() - ), - Span::call_site(), - ); - - let trait_impl = callback_interface::trait_impl( - &trait_impl_ident, - &self_ident, - &internals_ident, - &items, - ) - .unwrap_or_else(|e| e.into_compile_error()); - let metadata_items = callback_interface::metadata_items(&self_ident, &items, &mod_path) - .unwrap_or_else(|e| vec![e.into_compile_error()]); - - let init_ident = Ident::new( - &uniffi_meta::init_callback_fn_symbol_name(&mod_path, &trait_name), - Span::call_site(), - ); + let trait_impl_ident = callback_interface::trait_impl_ident(&trait_name); + let trait_impl = callback_interface::trait_impl(&mod_path, &self_ident, &items) + .unwrap_or_else(|e| e.into_compile_error()); + let metadata_items = (!udl_mode).then(|| { + let items = + callback_interface::metadata_items(&self_ident, &items, &mod_path, docstring) + .unwrap_or_else(|e| vec![e.into_compile_error()]); + quote! { #(#items)* } + }); + let ffi_converter_tokens = + ffi_converter_callback_interface_impl(&self_ident, &trait_impl_ident, udl_mode); Ok(quote! { - #[doc(hidden)] - static #internals_ident: ::uniffi::ForeignCallbackInternals = ::uniffi::ForeignCallbackInternals::new(); - - #[doc(hidden)] - #[no_mangle] - pub extern "C" fn #init_ident(callback: ::uniffi::ForeignCallback, _: &mut ::uniffi::RustCallStatus) { - #internals_ident.set_callback(callback); - } - #trait_impl - #(#metadata_items)* + #ffi_converter_tokens + + #metadata_items }) } ExportItem::Struct { self_ident, uniffi_traits, + .. } => { assert!(!udl_mode); utrait::expand_uniffi_trait_export(self_ident, uniffi_traits) @@ -182,62 +129,6 @@ pub(crate) fn expand_export( } } -pub(crate) fn ffi_converter_trait_impl(trait_ident: &Ident, udl_mode: bool) -> TokenStream { - let impl_spec = tagged_impl_header("FfiConverterArc", "e! { dyn #trait_ident }, udl_mode); - let lift_ref_impl_spec = tagged_impl_header("LiftRef", "e! { dyn #trait_ident }, udl_mode); - let name = ident_to_string(trait_ident); - let mod_path = match mod_path() { - Ok(p) => p, - Err(e) => return e.into_compile_error(), - }; - - quote! { - // All traits 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!(dyn #trait_ident: Sync, Send); - - unsafe #impl_spec { - type FfiType = *const ::std::os::raw::c_void; - - fn lower(obj: ::std::sync::Arc<Self>) -> Self::FfiType { - ::std::boxed::Box::into_raw(::std::boxed::Box::new(obj)) as *const ::std::os::raw::c_void - } - - fn try_lift(v: Self::FfiType) -> ::uniffi::Result<::std::sync::Arc<Self>> { - let foreign_arc = ::std::boxed::Box::leak(unsafe { Box::from_raw(v as *mut ::std::sync::Arc<Self>) }); - // Take a clone for our own use. - Ok(::std::sync::Arc::clone(foreign_arc)) - } - - fn write(obj: ::std::sync::Arc<Self>, buf: &mut Vec<u8>) { - ::uniffi::deps::static_assertions::const_assert!(::std::mem::size_of::<*const ::std::ffi::c_void>() <= 8); - ::uniffi::deps::bytes::BufMut::put_u64( - buf, - <Self as ::uniffi::FfiConverterArc<crate::UniFfiTag>>::lower(obj) as u64, - ); - } - - fn try_read(buf: &mut &[u8]) -> ::uniffi::Result<::std::sync::Arc<Self>> { - ::uniffi::deps::static_assertions::const_assert!(::std::mem::size_of::<*const ::std::ffi::c_void>() <= 8); - ::uniffi::check_remaining(buf, 8)?; - <Self as ::uniffi::FfiConverterArc<crate::UniFfiTag>>::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) - .concat_bool(true); - } - - unsafe #lift_ref_impl_spec { - type LiftType = ::std::sync::Arc<dyn #trait_ident>; - } - } -} - /// Rewrite Self type alias usage in an impl block to the type itself. /// /// For example, diff --git a/third_party/rust/uniffi_macros/src/export/attributes.rs b/third_party/rust/uniffi_macros/src/export/attributes.rs index c3edcd5920..be7e8902e4 100644 --- a/third_party/rust/uniffi_macros/src/export/attributes.rs +++ b/third_party/rust/uniffi_macros/src/export/attributes.rs @@ -1,31 +1,33 @@ -use crate::util::{either_attribute_arg, kw, parse_comma_separated, UniffiAttributeArgs}; +use std::collections::{HashMap, HashSet}; + +use crate::{ + default::DefaultValue, + util::{either_attribute_arg, kw, parse_comma_separated, UniffiAttributeArgs}, +}; use proc_macro2::TokenStream; use quote::ToTokens; use syn::{ + parenthesized, parse::{Parse, ParseStream}, - Attribute, LitStr, Meta, PathArguments, PathSegment, Token, + Attribute, Ident, LitStr, Meta, PathArguments, PathSegment, Token, }; +use uniffi_meta::UniffiTraitDiscriminants; #[derive(Default)] -pub struct ExportAttributeArguments { +pub struct ExportTraitArgs { pub(crate) async_runtime: Option<AsyncRuntime>, pub(crate) callback_interface: Option<kw::callback_interface>, - pub(crate) constructor: Option<kw::constructor>, - // tried to make this a vec but that got messy quickly... - pub(crate) trait_debug: Option<kw::Debug>, - pub(crate) trait_display: Option<kw::Display>, - pub(crate) trait_hash: Option<kw::Hash>, - pub(crate) trait_eq: Option<kw::Eq>, + pub(crate) with_foreign: Option<kw::with_foreign>, } -impl Parse for ExportAttributeArguments { +impl Parse for ExportTraitArgs { fn parse(input: ParseStream<'_>) -> syn::Result<Self> { parse_comma_separated(input) } } -impl UniffiAttributeArgs for ExportAttributeArguments { +impl UniffiAttributeArgs for ExportTraitArgs { fn parse_one(input: ParseStream<'_>) -> syn::Result<Self> { let lookahead = input.lookahead1(); if lookahead.peek(kw::async_runtime) { @@ -40,52 +42,175 @@ impl UniffiAttributeArgs for ExportAttributeArguments { callback_interface: input.parse()?, ..Self::default() }) - } else if lookahead.peek(kw::constructor) { + } else if lookahead.peek(kw::with_foreign) { Ok(Self { - constructor: input.parse()?, + with_foreign: input.parse()?, ..Self::default() }) - } else if lookahead.peek(kw::Debug) { + } else { + Ok(Self::default()) + } + } + + fn merge(self, other: Self) -> syn::Result<Self> { + let merged = Self { + async_runtime: either_attribute_arg(self.async_runtime, other.async_runtime)?, + callback_interface: either_attribute_arg( + self.callback_interface, + other.callback_interface, + )?, + with_foreign: either_attribute_arg(self.with_foreign, other.with_foreign)?, + }; + if merged.callback_interface.is_some() && merged.with_foreign.is_some() { + return Err(syn::Error::new( + merged.callback_interface.unwrap().span, + "`callback_interface` and `with_foreign` are mutually exclusive", + )); + } + Ok(merged) + } +} + +#[derive(Clone, Default)] +pub struct ExportFnArgs { + pub(crate) async_runtime: Option<AsyncRuntime>, + pub(crate) name: Option<String>, + pub(crate) defaults: DefaultMap, +} + +impl Parse for ExportFnArgs { + fn parse(input: ParseStream<'_>) -> syn::Result<Self> { + parse_comma_separated(input) + } +} + +impl UniffiAttributeArgs for ExportFnArgs { + fn parse_one(input: ParseStream<'_>) -> syn::Result<Self> { + let lookahead = input.lookahead1(); + if lookahead.peek(kw::async_runtime) { + let _: kw::async_runtime = input.parse()?; + let _: Token![=] = input.parse()?; Ok(Self { - trait_debug: input.parse()?, + async_runtime: Some(input.parse()?), ..Self::default() }) - } else if lookahead.peek(kw::Display) { + } else if lookahead.peek(kw::name) { + let _: kw::name = input.parse()?; + let _: Token![=] = input.parse()?; + let name = Some(input.parse::<LitStr>()?.value()); Ok(Self { - trait_display: input.parse()?, + name, ..Self::default() }) - } else if lookahead.peek(kw::Hash) { + } else if lookahead.peek(kw::default) { Ok(Self { - trait_hash: input.parse()?, + defaults: DefaultMap::parse(input)?, ..Self::default() }) - } else if lookahead.peek(kw::Eq) { + } else { + Err(syn::Error::new( + input.span(), + format!("uniffi::export attribute `{input}` is not supported here."), + )) + } + } + + fn merge(self, other: Self) -> syn::Result<Self> { + Ok(Self { + async_runtime: either_attribute_arg(self.async_runtime, other.async_runtime)?, + name: either_attribute_arg(self.name, other.name)?, + defaults: self.defaults.merge(other.defaults), + }) + } +} + +#[derive(Default)] +pub struct ExportImplArgs { + pub(crate) async_runtime: Option<AsyncRuntime>, +} + +impl Parse for ExportImplArgs { + fn parse(input: ParseStream<'_>) -> syn::Result<Self> { + parse_comma_separated(input) + } +} + +impl UniffiAttributeArgs for ExportImplArgs { + fn parse_one(input: ParseStream<'_>) -> syn::Result<Self> { + let lookahead = input.lookahead1(); + if lookahead.peek(kw::async_runtime) { + let _: kw::async_runtime = input.parse()?; + let _: Token![=] = input.parse()?; Ok(Self { - trait_eq: input.parse()?, - ..Self::default() + async_runtime: Some(input.parse()?), }) } else { - Ok(Self::default()) + Err(syn::Error::new( + input.span(), + format!("uniffi::export attribute `{input}` is not supported here."), + )) } } fn merge(self, other: Self) -> syn::Result<Self> { Ok(Self { async_runtime: either_attribute_arg(self.async_runtime, other.async_runtime)?, - callback_interface: either_attribute_arg( - self.callback_interface, - other.callback_interface, - )?, - constructor: either_attribute_arg(self.constructor, other.constructor)?, - trait_debug: either_attribute_arg(self.trait_debug, other.trait_debug)?, - trait_display: either_attribute_arg(self.trait_display, other.trait_display)?, - trait_hash: either_attribute_arg(self.trait_hash, other.trait_hash)?, - trait_eq: either_attribute_arg(self.trait_eq, other.trait_eq)?, }) } } +#[derive(Default)] +pub struct ExportStructArgs { + pub(crate) traits: HashSet<UniffiTraitDiscriminants>, +} + +impl Parse for ExportStructArgs { + fn parse(input: ParseStream<'_>) -> syn::Result<Self> { + parse_comma_separated(input) + } +} + +impl UniffiAttributeArgs for ExportStructArgs { + fn parse_one(input: ParseStream<'_>) -> syn::Result<Self> { + let lookahead = input.lookahead1(); + if lookahead.peek(kw::Debug) { + input.parse::<Option<kw::Debug>>()?; + Ok(Self { + traits: HashSet::from([UniffiTraitDiscriminants::Debug]), + }) + } else if lookahead.peek(kw::Display) { + input.parse::<Option<kw::Display>>()?; + Ok(Self { + traits: HashSet::from([UniffiTraitDiscriminants::Display]), + }) + } else if lookahead.peek(kw::Hash) { + input.parse::<Option<kw::Hash>>()?; + Ok(Self { + traits: HashSet::from([UniffiTraitDiscriminants::Hash]), + }) + } else if lookahead.peek(kw::Eq) { + input.parse::<Option<kw::Eq>>()?; + Ok(Self { + traits: HashSet::from([UniffiTraitDiscriminants::Eq]), + }) + } else { + Err(syn::Error::new( + input.span(), + format!( + "uniffi::export struct attributes must be builtin trait names; `{input}` is invalid" + ), + )) + } + } + + fn merge(self, other: Self) -> syn::Result<Self> { + let mut traits = self.traits; + traits.extend(other.traits); + Ok(Self { traits }) + } +} + +#[derive(Clone)] pub(crate) enum AsyncRuntime { Tokio(LitStr), } @@ -111,9 +236,57 @@ impl ToTokens for AsyncRuntime { } } +/// Arguments for function inside an impl block +/// +/// This stores the parsed arguments for `uniffi::constructor` and `uniffi::method` +#[derive(Clone, Default)] +pub struct ExportedImplFnArgs { + pub(crate) name: Option<String>, + pub(crate) defaults: DefaultMap, +} + +impl Parse for ExportedImplFnArgs { + fn parse(input: ParseStream<'_>) -> syn::Result<Self> { + parse_comma_separated(input) + } +} + +impl UniffiAttributeArgs for ExportedImplFnArgs { + fn parse_one(input: ParseStream<'_>) -> syn::Result<Self> { + let lookahead = input.lookahead1(); + if lookahead.peek(kw::name) { + let _: kw::name = input.parse()?; + let _: Token![=] = input.parse()?; + let name = Some(input.parse::<LitStr>()?.value()); + Ok(Self { + name, + ..Self::default() + }) + } else if lookahead.peek(kw::default) { + Ok(Self { + defaults: DefaultMap::parse(input)?, + ..Self::default() + }) + } else { + Err(syn::Error::new( + input.span(), + format!("uniffi::constructor/method attribute `{input}` is not supported here."), + )) + } + } + + fn merge(self, other: Self) -> syn::Result<Self> { + Ok(Self { + name: either_attribute_arg(self.name, other.name)?, + defaults: self.defaults.merge(other.defaults), + }) + } +} + #[derive(Default)] pub(super) struct ExportedImplFnAttributes { pub constructor: bool, + pub args: ExportedImplFnArgs, } impl ExportedImplFnAttributes { @@ -130,12 +303,11 @@ impl ExportedImplFnAttributes { } ensure_no_path_args(fst)?; - if let Meta::List(_) | Meta::NameValue(_) = &attr.meta { - return Err(syn::Error::new_spanned( - &attr.meta, - "attribute arguments are not currently recognized in this position", - )); - } + let args = match &attr.meta { + Meta::List(_) => attr.parse_args::<ExportedImplFnArgs>()?, + _ => Default::default(), + }; + this.args = args; if segs.len() != 2 { return Err(syn::Error::new_spanned( @@ -156,6 +328,14 @@ impl ExportedImplFnAttributes { } this.constructor = true; } + "method" => { + if this.constructor { + return Err(syn::Error::new_spanned( + attr, + "confused constructor/method attributes", + )); + } + } _ => return Err(syn::Error::new_spanned(snd, "unknown uniffi attribute")), } } @@ -171,3 +351,53 @@ fn ensure_no_path_args(seg: &PathSegment) -> syn::Result<()> { Err(syn::Error::new_spanned(&seg.arguments, "unexpected syntax")) } } + +/// Maps arguments to defaults for functions +#[derive(Clone, Default)] +pub struct DefaultMap { + map: HashMap<Ident, DefaultValue>, +} + +impl DefaultMap { + pub fn merge(self, other: Self) -> Self { + let mut map = self.map; + map.extend(other.map); + Self { map } + } + + pub fn remove(&mut self, ident: &Ident) -> Option<DefaultValue> { + self.map.remove(ident) + } + + pub fn idents(&self) -> Vec<&Ident> { + self.map.keys().collect() + } +} + +impl Parse for DefaultMap { + fn parse(input: ParseStream<'_>) -> syn::Result<Self> { + let _: kw::default = input.parse()?; + let content; + let _ = parenthesized!(content in input); + let pairs = content.parse_terminated(DefaultPair::parse, Token![,])?; + Ok(Self { + map: pairs.into_iter().map(|p| (p.name, p.value)).collect(), + }) + } +} + +pub struct DefaultPair { + pub name: Ident, + pub eq_token: Token![=], + pub value: DefaultValue, +} + +impl Parse for DefaultPair { + fn parse(input: ParseStream<'_>) -> syn::Result<Self> { + Ok(Self { + name: input.parse()?, + eq_token: input.parse()?, + value: input.parse()?, + }) + } +} diff --git a/third_party/rust/uniffi_macros/src/export/callback_interface.rs b/third_party/rust/uniffi_macros/src/export/callback_interface.rs index 2f2561bbc2..fe145384ec 100644 --- a/third_party/rust/uniffi_macros/src/export/callback_interface.rs +++ b/third_party/rust/uniffi_macros/src/export/callback_interface.rs @@ -5,69 +5,137 @@ use crate::{ export::ImplItem, fnsig::{FnKind, FnSignature, ReceiverArg}, - util::{create_metadata_items, ident_to_string, mod_path, tagged_impl_header}, + util::{ + create_metadata_items, derive_ffi_traits, ident_to_string, mod_path, tagged_impl_header, + }, }; use proc_macro2::{Span, TokenStream}; -use quote::quote; +use quote::{format_ident, quote}; use std::iter; use syn::Ident; +/// Generate a trait impl that calls foreign callbacks +/// +/// This generates: +/// * A `repr(C)` VTable struct where each field is the FFI function for the trait method. +/// * A FFI function for foreign code to set their VTable for the interface +/// * An implementation of the trait using that VTable pub(super) fn trait_impl( - ident: &Ident, + mod_path: &str, trait_ident: &Ident, - internals_ident: &Ident, items: &[ImplItem], ) -> syn::Result<TokenStream> { - let trait_impl_methods = items + let trait_name = ident_to_string(trait_ident); + let trait_impl_ident = trait_impl_ident(&trait_name); + let vtable_type = format_ident!("UniFfiTraitVtable{trait_name}"); + let vtable_cell = format_ident!("UNIFFI_TRAIT_CELL_{}", trait_name.to_uppercase()); + let init_ident = Ident::new( + &uniffi_meta::init_callback_vtable_fn_symbol_name(mod_path, &trait_name), + Span::call_site(), + ); + let methods = items .iter() .map(|item| match item { - ImplItem::Method(sig) => gen_method_impl(sig, internals_ident), - _ => unreachable!("traits have no constructors"), + ImplItem::Constructor(sig) => Err(syn::Error::new( + sig.span, + "Constructors not allowed in trait interfaces", + )), + ImplItem::Method(sig) => Ok(sig), }) - .collect::<syn::Result<TokenStream>>()?; - let ffi_converter_tokens = ffi_converter_callback_interface_impl(trait_ident, ident, false); + .collect::<syn::Result<Vec<_>>>()?; + + let vtable_fields = methods.iter() + .map(|sig| { + let ident = &sig.ident; + let param_names = sig.scaffolding_param_names(); + let param_types = sig.scaffolding_param_types(); + let lift_return = sig.lift_return_impl(); + if !sig.is_async { + quote! { + #ident: extern "C" fn( + uniffi_handle: u64, + #(#param_names: #param_types,)* + uniffi_out_return: &mut #lift_return::ReturnType, + uniffi_out_call_status: &mut ::uniffi::RustCallStatus, + ), + } + } else { + quote! { + #ident: extern "C" fn( + uniffi_handle: u64, + #(#param_names: #param_types,)* + uniffi_future_callback: ::uniffi::ForeignFutureCallback<#lift_return::ReturnType>, + uniffi_callback_data: u64, + uniffi_out_return: &mut ::uniffi::ForeignFuture, + ), + } + } + }); + + let trait_impl_methods = methods + .iter() + .map(|sig| gen_method_impl(sig, &vtable_cell)) + .collect::<syn::Result<Vec<_>>>()?; + let has_async_method = methods.iter().any(|m| m.is_async); + let impl_attributes = has_async_method.then(|| quote! { #[::async_trait::async_trait] }); Ok(quote! { - #[doc(hidden)] + struct #vtable_type { + #(#vtable_fields)* + uniffi_free: extern "C" fn(handle: u64), + } + + static #vtable_cell: ::uniffi::UniffiForeignPointerCell::<#vtable_type> = ::uniffi::UniffiForeignPointerCell::<#vtable_type>::new(); + + #[no_mangle] + extern "C" fn #init_ident(vtable: ::std::ptr::NonNull<#vtable_type>) { + #vtable_cell.set(vtable); + } + #[derive(Debug)] - struct #ident { + struct #trait_impl_ident { handle: u64, } - impl #ident { + impl #trait_impl_ident { fn new(handle: u64) -> Self { Self { handle } } } - impl ::std::ops::Drop for #ident { - fn drop(&mut self) { - #internals_ident.invoke_callback::<(), crate::UniFfiTag>( - self.handle, uniffi::IDX_CALLBACK_FREE, Default::default() - ) - } - } - - ::uniffi::deps::static_assertions::assert_impl_all!(#ident: Send); + ::uniffi::deps::static_assertions::assert_impl_all!(#trait_impl_ident: ::core::marker::Send); - impl #trait_ident for #ident { - #trait_impl_methods + #impl_attributes + impl #trait_ident for #trait_impl_ident { + #(#trait_impl_methods)* } - #ffi_converter_tokens + impl ::std::ops::Drop for #trait_impl_ident { + fn drop(&mut self) { + let vtable = #vtable_cell.get(); + (vtable.uniffi_free)(self.handle); + } + } }) } +pub fn trait_impl_ident(trait_name: &str) -> Ident { + Ident::new( + &format!("UniFFICallbackHandler{trait_name}"), + Span::call_site(), + ) +} + pub fn ffi_converter_callback_interface_impl( trait_ident: &Ident, trait_impl_ident: &Ident, udl_mode: bool, ) -> TokenStream { - let name = ident_to_string(trait_ident); + let trait_name = ident_to_string(trait_ident); let dyn_trait = quote! { dyn #trait_ident }; let box_dyn_trait = quote! { ::std::boxed::Box<#dyn_trait> }; let lift_impl_spec = tagged_impl_header("Lift", &box_dyn_trait, udl_mode); - let lift_ref_impl_spec = tagged_impl_header("LiftRef", &dyn_trait, udl_mode); + let derive_ffi_traits = derive_ffi_traits(&box_dyn_trait, udl_mode, &["LiftRef", "LiftReturn"]); let mod_path = match mod_path() { Ok(p) => p, Err(e) => return e.into_compile_error(), @@ -93,66 +161,85 @@ pub fn ffi_converter_callback_interface_impl( ::uniffi::metadata::codes::TYPE_CALLBACK_INTERFACE, ) .concat_str(#mod_path) - .concat_str(#name); + .concat_str(#trait_name); } - unsafe #lift_ref_impl_spec { - type LiftType = #box_dyn_trait; - } + #derive_ffi_traits } } -fn gen_method_impl(sig: &FnSignature, internals_ident: &Ident) -> syn::Result<TokenStream> { +/// Generate a single method for [trait_impl]. This implements a trait method by invoking a +/// foreign-supplied callback. +fn gen_method_impl(sig: &FnSignature, vtable_cell: &Ident) -> syn::Result<TokenStream> { let FnSignature { ident, + is_async, return_ty, kind, receiver, + name, + span, .. } = sig; - let index = match kind { - // Note: the callback index is 1-based, since 0 is reserved for the free function - FnKind::TraitMethod { index, .. } => index + 1, - k => { - return Err(syn::Error::new( - sig.span, - format!( - "Internal UniFFI error: Unexpected function kind for callback interface {k:?}" - ), - )); - } - }; + + if !matches!(kind, FnKind::TraitMethod { .. }) { + return Err(syn::Error::new( + *span, + format!( + "Internal UniFFI error: Unexpected function kind for callback interface {name}: {kind:?}", + ), + )); + } let self_param = match receiver { + Some(ReceiverArg::Ref) => quote! { &self }, + Some(ReceiverArg::Arc) => quote! { self: Arc<Self> }, None => { return Err(syn::Error::new( - sig.span, + *span, "callback interface methods must take &self as their first argument", )); } - Some(ReceiverArg::Ref) => quote! { &self }, - Some(ReceiverArg::Arc) => quote! { self: Arc<Self> }, }; + let params = sig.params(); - let buf_ident = Ident::new("uniffi_args_buf", Span::call_site()); - let write_exprs = sig.write_exprs(&buf_ident); + let lower_exprs = sig.args.iter().map(|a| { + let lower_impl = a.lower_impl(); + let ident = &a.ident; + quote! { #lower_impl::lower(#ident) } + }); - Ok(quote! { - fn #ident(#self_param, #(#params),*) -> #return_ty { - #[allow(unused_mut)] - let mut #buf_ident = ::std::vec::Vec::new(); - #(#write_exprs;)* - let uniffi_args_rbuf = uniffi::RustBuffer::from_vec(#buf_ident); + let lift_return = sig.lift_return_impl(); - #internals_ident.invoke_callback::<#return_ty, crate::UniFfiTag>(self.handle, #index, uniffi_args_rbuf) - } - }) + if !is_async { + Ok(quote! { + fn #ident(#self_param, #(#params),*) -> #return_ty { + let vtable = #vtable_cell.get(); + let mut uniffi_call_status = ::uniffi::RustCallStatus::new(); + let mut uniffi_return_value: #lift_return::ReturnType = ::uniffi::FfiDefault::ffi_default(); + (vtable.#ident)(self.handle, #(#lower_exprs,)* &mut uniffi_return_value, &mut uniffi_call_status); + #lift_return::lift_foreign_return(uniffi_return_value, uniffi_call_status) + } + }) + } else { + Ok(quote! { + async fn #ident(#self_param, #(#params),*) -> #return_ty { + let vtable = #vtable_cell.get(); + ::uniffi::foreign_async_call::<_, #return_ty, crate::UniFfiTag>(move |uniffi_future_callback, uniffi_future_callback_data| { + let mut uniffi_foreign_future: ::uniffi::ForeignFuture = ::uniffi::FfiDefault::ffi_default(); + (vtable.#ident)(self.handle, #(#lower_exprs,)* uniffi_future_callback, uniffi_future_callback_data, &mut uniffi_foreign_future); + uniffi_foreign_future + }).await + } + }) + } } pub(super) fn metadata_items( self_ident: &Ident, items: &[ImplItem], module_path: &str, + docstring: String, ) -> syn::Result<Vec<TokenStream>> { let trait_name = ident_to_string(self_ident); let callback_interface_items = create_metadata_items( @@ -162,13 +249,14 @@ pub(super) fn metadata_items( ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::CALLBACK_INTERFACE) .concat_str(#module_path) .concat_str(#trait_name) + .concat_long_str(#docstring) }, None, ); iter::once(Ok(callback_interface_items)) .chain(items.iter().map(|item| match item { - ImplItem::Method(sig) => sig.metadata_items(), + ImplItem::Method(sig) => sig.metadata_items_for_callback_interface(), _ => unreachable!("traits have no constructors"), })) .collect() diff --git a/third_party/rust/uniffi_macros/src/export/item.rs b/third_party/rust/uniffi_macros/src/export/item.rs index 98c7d0ebe2..da3c9455c8 100644 --- a/third_party/rust/uniffi_macros/src/export/item.rs +++ b/third_party/rust/uniffi_macros/src/export/item.rs @@ -3,24 +3,34 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use crate::fnsig::FnSignature; +use proc_macro::TokenStream; use proc_macro2::{Ident, Span}; use quote::ToTokens; -use super::attributes::{ExportAttributeArguments, ExportedImplFnAttributes}; +use super::attributes::{ + ExportFnArgs, ExportImplArgs, ExportStructArgs, ExportTraitArgs, ExportedImplFnArgs, + ExportedImplFnAttributes, +}; +use crate::util::extract_docstring; use uniffi_meta::UniffiTraitDiscriminants; pub(super) enum ExportItem { Function { sig: FnSignature, + args: ExportFnArgs, }, Impl { self_ident: Ident, items: Vec<ImplItem>, + args: ExportImplArgs, }, Trait { self_ident: Ident, items: Vec<ImplItem>, - callback_interface: bool, + with_foreign: bool, + callback_interface_only: bool, + docstring: String, + args: ExportTraitArgs, }, Struct { self_ident: Ident, @@ -29,15 +39,17 @@ pub(super) enum ExportItem { } impl ExportItem { - pub fn new(item: syn::Item, args: &ExportAttributeArguments) -> syn::Result<Self> { + pub fn new(item: syn::Item, attr_args: TokenStream) -> syn::Result<Self> { match item { syn::Item::Fn(item) => { - let sig = FnSignature::new_function(item.sig)?; - Ok(Self::Function { sig }) + let args: ExportFnArgs = syn::parse(attr_args)?; + let docstring = extract_docstring(&item.attrs)?; + let sig = FnSignature::new_function(item.sig, args.clone(), docstring)?; + Ok(Self::Function { sig, args }) } - syn::Item::Impl(item) => Self::from_impl(item, args.constructor.is_some()), - syn::Item::Trait(item) => Self::from_trait(item, args.callback_interface.is_some()), - syn::Item::Struct(item) => Self::from_struct(item, args), + syn::Item::Impl(item) => Self::from_impl(item, attr_args), + syn::Item::Trait(item) => Self::from_trait(item, attr_args), + syn::Item::Struct(item) => Self::from_struct(item, attr_args), // FIXME: Support const / static? _ => Err(syn::Error::new( Span::call_site(), @@ -47,7 +59,8 @@ impl ExportItem { } } - pub fn from_impl(item: syn::ItemImpl, force_constructor: bool) -> syn::Result<Self> { + pub fn from_impl(item: syn::ItemImpl, attr_args: TokenStream) -> syn::Result<Self> { + let args: ExportImplArgs = syn::parse(attr_args)?; if !item.generics.params.is_empty() || item.generics.where_clause.is_some() { return Err(syn::Error::new_spanned( &item.generics, @@ -88,14 +101,22 @@ impl ExportItem { } }; + let docstring = extract_docstring(&impl_fn.attrs)?; let attrs = ExportedImplFnAttributes::new(&impl_fn.attrs)?; - let item = if force_constructor || attrs.constructor { + let item = if attrs.constructor { ImplItem::Constructor(FnSignature::new_constructor( self_ident.clone(), impl_fn.sig, + attrs.args, + docstring, )?) } else { - ImplItem::Method(FnSignature::new_method(self_ident.clone(), impl_fn.sig)?) + ImplItem::Method(FnSignature::new_method( + self_ident.clone(), + impl_fn.sig, + attrs.args, + docstring, + )?) }; Ok(item) @@ -105,10 +126,15 @@ impl ExportItem { Ok(Self::Impl { items, self_ident: self_ident.to_owned(), + args, }) } - fn from_trait(item: syn::ItemTrait, callback_interface: bool) -> syn::Result<Self> { + fn from_trait(item: syn::ItemTrait, attr_args: TokenStream) -> syn::Result<Self> { + let args: ExportTraitArgs = syn::parse(attr_args)?; + let with_foreign = args.callback_interface.is_some() || args.with_foreign.is_some(); + let callback_interface_only = args.callback_interface.is_some(); + if !item.generics.params.is_empty() || item.generics.where_clause.is_some() { return Err(syn::Error::new_spanned( &item.generics, @@ -117,6 +143,7 @@ impl ExportItem { } let self_ident = item.ident.to_owned(); + let docstring = extract_docstring(&item.attrs)?; let items = item .items .into_iter() @@ -132,6 +159,7 @@ impl ExportItem { } }; + let docstring = extract_docstring(&tim.attrs)?; let attrs = ExportedImplFnAttributes::new(&tim.attrs)?; let item = if attrs.constructor { return Err(syn::Error::new_spanned( @@ -142,7 +170,9 @@ impl ExportItem { ImplItem::Method(FnSignature::new_trait_method( self_ident.clone(), tim.sig, + ExportedImplFnArgs::default(), i as u32, + docstring, )?) }; @@ -153,28 +183,26 @@ impl ExportItem { Ok(Self::Trait { items, self_ident, - callback_interface, + with_foreign, + callback_interface_only, + docstring, + args, }) } - fn from_struct(item: syn::ItemStruct, args: &ExportAttributeArguments) -> syn::Result<Self> { - let mut uniffi_traits = Vec::new(); - if args.trait_debug.is_some() { - uniffi_traits.push(UniffiTraitDiscriminants::Debug); - } - if args.trait_display.is_some() { - uniffi_traits.push(UniffiTraitDiscriminants::Display); - } - if args.trait_hash.is_some() { - uniffi_traits.push(UniffiTraitDiscriminants::Hash); - } - if args.trait_eq.is_some() { - uniffi_traits.push(UniffiTraitDiscriminants::Eq); + fn from_struct(item: syn::ItemStruct, attr_args: TokenStream) -> syn::Result<Self> { + let args: ExportStructArgs = syn::parse(attr_args)?; + let uniffi_traits: Vec<UniffiTraitDiscriminants> = args.traits.into_iter().collect(); + if uniffi_traits.is_empty() { + Err(syn::Error::new(Span::call_site(), + "uniffi::export on a struct must supply a builtin trait name. Did you mean `#[derive(uniffi::Object)]`?" + )) + } else { + Ok(Self::Struct { + self_ident: item.ident, + uniffi_traits, + }) } - Ok(Self::Struct { - self_ident: item.ident, - uniffi_traits, - }) } } diff --git a/third_party/rust/uniffi_macros/src/export/scaffolding.rs b/third_party/rust/uniffi_macros/src/export/scaffolding.rs index f120ccc880..fa7b61deca 100644 --- a/third_party/rust/uniffi_macros/src/export/scaffolding.rs +++ b/third_party/rust/uniffi_macros/src/export/scaffolding.rs @@ -6,12 +6,12 @@ use proc_macro2::{Ident, TokenStream}; use quote::quote; use std::iter; -use super::attributes::{AsyncRuntime, ExportAttributeArguments}; -use crate::fnsig::{FnKind, FnSignature, NamedArg}; +use super::attributes::AsyncRuntime; +use crate::fnsig::{FnKind, FnSignature}; pub(super) fn gen_fn_scaffolding( sig: FnSignature, - arguments: &ExportAttributeArguments, + ar: &Option<AsyncRuntime>, udl_mode: bool, ) -> syn::Result<TokenStream> { if sig.receiver.is_some() { @@ -21,7 +21,7 @@ pub(super) fn gen_fn_scaffolding( )); } if !sig.is_async { - if let Some(async_runtime) = &arguments.async_runtime { + if let Some(async_runtime) = ar { return Err(syn::Error::new_spanned( async_runtime, "this attribute is only allowed on async functions", @@ -32,7 +32,7 @@ pub(super) fn gen_fn_scaffolding( sig.metadata_items() .unwrap_or_else(syn::Error::into_compile_error) }); - let scaffolding_func = gen_ffi_function(&sig, arguments, udl_mode)?; + let scaffolding_func = gen_ffi_function(&sig, ar, udl_mode)?; Ok(quote! { #scaffolding_func #metadata_items @@ -41,7 +41,7 @@ pub(super) fn gen_fn_scaffolding( pub(super) fn gen_constructor_scaffolding( sig: FnSignature, - arguments: &ExportAttributeArguments, + ar: &Option<AsyncRuntime>, udl_mode: bool, ) -> syn::Result<TokenStream> { if sig.receiver.is_some() { @@ -50,14 +50,11 @@ pub(super) fn gen_constructor_scaffolding( "constructors must not have a self parameter", )); } - if sig.is_async { - return Err(syn::Error::new(sig.span, "constructors can't be async")); - } let metadata_items = (!udl_mode).then(|| { sig.metadata_items() .unwrap_or_else(syn::Error::into_compile_error) }); - let scaffolding_func = gen_ffi_function(&sig, arguments, udl_mode)?; + let scaffolding_func = gen_ffi_function(&sig, ar, udl_mode)?; Ok(quote! { #scaffolding_func #metadata_items @@ -66,7 +63,7 @@ pub(super) fn gen_constructor_scaffolding( pub(super) fn gen_method_scaffolding( sig: FnSignature, - arguments: &ExportAttributeArguments, + ar: &Option<AsyncRuntime>, udl_mode: bool, ) -> syn::Result<TokenStream> { let scaffolding_func = if sig.receiver.is_none() { @@ -75,7 +72,7 @@ pub(super) fn gen_method_scaffolding( "associated functions are not currently supported", )); } else { - gen_ffi_function(&sig, arguments, udl_mode)? + gen_ffi_function(&sig, ar, udl_mode)? }; let metadata_items = (!udl_mode).then(|| { @@ -90,31 +87,37 @@ pub(super) fn gen_method_scaffolding( // Pieces of code for the scaffolding function struct ScaffoldingBits { - /// Parameters for the scaffolding function - params: Vec<TokenStream>, + /// Parameter names for the scaffolding function + param_names: Vec<TokenStream>, + /// Parameter types for the scaffolding function + param_types: Vec<TokenStream>, /// Lift closure. See `FnSignature::lift_closure` for an explanation of this. lift_closure: TokenStream, /// Expression to call the Rust function after a successful lift. rust_fn_call: TokenStream, + /// Convert the result of `rust_fn_call`, stored in a variable named `uniffi_result` into its final value. + /// This is used to do things like error conversion / Arc wrapping + convert_result: TokenStream, } impl ScaffoldingBits { fn new_for_function(sig: &FnSignature, udl_mode: bool) -> Self { let ident = &sig.ident; - let params: Vec<_> = sig.args.iter().map(NamedArg::scaffolding_param).collect(); let call_params = sig.rust_call_params(false); let rust_fn_call = quote! { #ident(#call_params) }; // UDL mode adds an extra conversion (#1749) - let rust_fn_call = if udl_mode && sig.looks_like_result { - quote! { #rust_fn_call.map_err(::std::convert::Into::into) } + let convert_result = if udl_mode && sig.looks_like_result { + quote! { uniffi_result.map_err(::std::convert::Into::into) } } else { - rust_fn_call + quote! { uniffi_result } }; Self { - params, + param_names: sig.scaffolding_param_names().collect(), + param_types: sig.scaffolding_param_types().collect(), lift_closure: sig.lift_closure(None), rust_fn_call, + convert_result, } } @@ -125,20 +128,32 @@ impl ScaffoldingBits { udl_mode: bool, ) -> Self { let ident = &sig.ident; - let ffi_converter = if is_trait { + let lift_impl = if is_trait { quote! { - <::std::sync::Arc<dyn #self_ident> as ::uniffi::FfiConverter<crate::UniFfiTag>> + <::std::sync::Arc<dyn #self_ident> as ::uniffi::Lift<crate::UniFfiTag>> } } else { quote! { - <::std::sync::Arc<#self_ident> as ::uniffi::FfiConverter<crate::UniFfiTag>> + <::std::sync::Arc<#self_ident> as ::uniffi::Lift<crate::UniFfiTag>> } }; - let params: Vec<_> = iter::once(quote! { uniffi_self_lowered: #ffi_converter::FfiType }) - .chain(sig.scaffolding_params()) - .collect(); + let try_lift_self = if is_trait { + // For trait interfaces we need to special case this. Trait interfaces normally lift + // foreign trait impl pointers. However, for a method call, we want to lift a Rust + // pointer. + quote! { + { + let boxed_foreign_arc = unsafe { Box::from_raw(uniffi_self_lowered as *mut ::std::sync::Arc<dyn #self_ident>) }; + // Take a clone for our own use. + Ok(*boxed_foreign_arc) + } + } + } else { + quote! { #lift_impl::try_lift(uniffi_self_lowered) } + }; + let lift_closure = sig.lift_closure(Some(quote! { - match #ffi_converter::try_lift(uniffi_self_lowered) { + match #try_lift_self { Ok(v) => v, Err(e) => return Err(("self", e)) } @@ -146,38 +161,45 @@ impl ScaffoldingBits { let call_params = sig.rust_call_params(true); let rust_fn_call = quote! { uniffi_args.0.#ident(#call_params) }; // UDL mode adds an extra conversion (#1749) - let rust_fn_call = if udl_mode && sig.looks_like_result { - quote! { #rust_fn_call.map_err(::std::convert::Into::into) } + let convert_result = if udl_mode && sig.looks_like_result { + quote! { uniffi_result .map_err(::std::convert::Into::into) } } else { - rust_fn_call + quote! { uniffi_result } }; Self { - params, + param_names: iter::once(quote! { uniffi_self_lowered }) + .chain(sig.scaffolding_param_names()) + .collect(), + param_types: iter::once(quote! { #lift_impl::FfiType }) + .chain(sig.scaffolding_param_types()) + .collect(), lift_closure, rust_fn_call, + convert_result, } } fn new_for_constructor(sig: &FnSignature, self_ident: &Ident, udl_mode: bool) -> Self { let ident = &sig.ident; - let params: Vec<_> = sig.args.iter().map(NamedArg::scaffolding_param).collect(); let call_params = sig.rust_call_params(false); let rust_fn_call = quote! { #self_ident::#ident(#call_params) }; // UDL mode adds extra conversions (#1749) - let rust_fn_call = match (udl_mode, sig.looks_like_result) { + let convert_result = match (udl_mode, sig.looks_like_result) { // For UDL - (true, false) => quote! { ::std::sync::Arc::new(#rust_fn_call) }, + (true, false) => quote! { ::std::sync::Arc::new(uniffi_result) }, (true, true) => { - quote! { #rust_fn_call.map(::std::sync::Arc::new).map_err(::std::convert::Into::into) } + quote! { uniffi_result.map(::std::sync::Arc::new).map_err(::std::convert::Into::into) } } - (false, _) => rust_fn_call, + (false, _) => quote! { uniffi_result }, }; Self { - params, + param_names: sig.scaffolding_param_names().collect(), + param_types: sig.scaffolding_param_types().collect(), lift_closure: sig.lift_closure(None), rust_fn_call, + convert_result, } } } @@ -188,13 +210,15 @@ impl ScaffoldingBits { /// `rust_fn` is the Rust function to call. pub(super) fn gen_ffi_function( sig: &FnSignature, - arguments: &ExportAttributeArguments, + ar: &Option<AsyncRuntime>, udl_mode: bool, ) -> syn::Result<TokenStream> { let ScaffoldingBits { - params, + param_names, + param_types, lift_closure, rust_fn_call, + convert_result, } = match &sig.kind { FnKind::Function => ScaffoldingBits::new_for_function(sig, udl_mode), FnKind::Method { self_ident } => { @@ -216,14 +240,15 @@ pub(super) fn gen_ffi_function( let ffi_ident = sig.scaffolding_fn_ident()?; let name = &sig.name; - let return_impl = &sig.return_impl(); + let return_ty = &sig.return_ty; + let return_impl = &sig.lower_return_impl(); Ok(if !sig.is_async { quote! { #[doc(hidden)] #[no_mangle] #vis extern "C" fn #ffi_ident( - #(#params,)* + #(#param_names: #param_types,)* call_status: &mut ::uniffi::RustCallStatus, ) -> #return_impl::ReturnType { ::uniffi::deps::log::debug!(#name); @@ -231,7 +256,10 @@ pub(super) fn gen_ffi_function( ::uniffi::rust_call(call_status, || { #return_impl::lower_return( match uniffi_lift_args() { - Ok(uniffi_args) => #rust_fn_call, + Ok(uniffi_args) => { + let uniffi_result = #rust_fn_call; + #convert_result + } Err((arg_name, anyhow_error)) => { #return_impl::handle_failed_lift(arg_name, anyhow_error) }, @@ -242,25 +270,28 @@ pub(super) fn gen_ffi_function( } } else { let mut future_expr = rust_fn_call; - if matches!(arguments.async_runtime, Some(AsyncRuntime::Tokio(_))) { + if matches!(ar, Some(AsyncRuntime::Tokio(_))) { future_expr = quote! { ::uniffi::deps::async_compat::Compat::new(#future_expr) } } quote! { #[doc(hidden)] #[no_mangle] - pub extern "C" fn #ffi_ident(#(#params,)*) -> ::uniffi::RustFutureHandle { + pub extern "C" fn #ffi_ident(#(#param_names: #param_types,)*) -> ::uniffi::Handle { ::uniffi::deps::log::debug!(#name); let uniffi_lift_args = #lift_closure; match uniffi_lift_args() { Ok(uniffi_args) => { - ::uniffi::rust_future_new( - async move { #future_expr.await }, + ::uniffi::rust_future_new::<_, #return_ty, _>( + async move { + let uniffi_result = #future_expr.await; + #convert_result + }, crate::UniFfiTag ) }, Err((arg_name, anyhow_error)) => { - ::uniffi::rust_future_new( + ::uniffi::rust_future_new::<_, #return_ty, _>( async move { #return_impl::handle_failed_lift(arg_name, anyhow_error) }, diff --git a/third_party/rust/uniffi_macros/src/export/trait_interface.rs b/third_party/rust/uniffi_macros/src/export/trait_interface.rs new file mode 100644 index 0000000000..83587ae386 --- /dev/null +++ b/third_party/rust/uniffi_macros/src/export/trait_interface.rs @@ -0,0 +1,183 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use proc_macro2::{Ident, Span, TokenStream}; +use quote::{quote, quote_spanned}; + +use uniffi_meta::ObjectImpl; + +use crate::{ + export::{ + attributes::ExportTraitArgs, callback_interface, gen_method_scaffolding, item::ImplItem, + }, + object::interface_meta_static_var, + util::{ident_to_string, tagged_impl_header}, +}; + +pub(super) fn gen_trait_scaffolding( + mod_path: &str, + args: ExportTraitArgs, + self_ident: Ident, + items: Vec<ImplItem>, + udl_mode: bool, + with_foreign: bool, + docstring: String, +) -> syn::Result<TokenStream> { + if let Some(rt) = args.async_runtime { + return Err(syn::Error::new_spanned(rt, "not supported for traits")); + } + let trait_name = ident_to_string(&self_ident); + let trait_impl = with_foreign.then(|| { + callback_interface::trait_impl(mod_path, &self_ident, &items) + .unwrap_or_else(|e| e.into_compile_error()) + }); + + let clone_fn_ident = Ident::new( + &uniffi_meta::clone_fn_symbol_name(mod_path, &trait_name), + Span::call_site(), + ); + let free_fn_ident = Ident::new( + &uniffi_meta::free_fn_symbol_name(mod_path, &trait_name), + Span::call_site(), + ); + + let helper_fn_tokens = quote! { + #[doc(hidden)] + #[no_mangle] + /// Clone a pointer to this object type + /// + /// Safety: Only pass pointers returned by a UniFFI call. Do not pass pointers that were + /// passed to the free function. + 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, || { + let ptr = ptr as *mut std::sync::Arc<dyn #self_ident>; + let arc = unsafe { ::std::sync::Arc::clone(&*ptr) }; + Ok(::std::boxed::Box::into_raw(::std::boxed::Box::new(arc)) as *const ::std::ffi::c_void) + }) + } + + #[doc(hidden)] + #[no_mangle] + /// Free a pointer to this object type + /// + /// Safety: Only pass pointers returned by a UniFFI call. Do not pass pointers that were + /// passed to the free function. + /// + /// Note: clippy doesn't complain about this being unsafe, but it definitely is since it + /// calls `Box::from_raw`. + 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()); + drop(unsafe { ::std::boxed::Box::from_raw(ptr as *mut std::sync::Arc<dyn #self_ident>) }); + Ok(()) + }); + } + }; + + let impl_tokens: TokenStream = items + .into_iter() + .map(|item| match item { + ImplItem::Method(sig) => gen_method_scaffolding(sig, &None, udl_mode), + _ => unreachable!("traits have no constructors"), + }) + .collect::<syn::Result<_>>()?; + + let meta_static_var = (!udl_mode).then(|| { + let imp = if with_foreign { + ObjectImpl::CallbackTrait + } else { + ObjectImpl::Trait + }; + interface_meta_static_var(&self_ident, imp, mod_path, docstring) + .unwrap_or_else(syn::Error::into_compile_error) + }); + let ffi_converter_tokens = ffi_converter(mod_path, &self_ident, udl_mode, with_foreign); + + Ok(quote_spanned! { self_ident.span() => + #meta_static_var + #helper_fn_tokens + #trait_impl + #impl_tokens + #ffi_converter_tokens + }) +} + +pub(crate) fn ffi_converter( + mod_path: &str, + trait_ident: &Ident, + udl_mode: bool, + with_foreign: bool, +) -> TokenStream { + let impl_spec = tagged_impl_header("FfiConverterArc", "e! { dyn #trait_ident }, udl_mode); + let lift_ref_impl_spec = tagged_impl_header("LiftRef", "e! { dyn #trait_ident }, udl_mode); + let trait_name = ident_to_string(trait_ident); + let try_lift = if with_foreign { + let trait_impl_ident = callback_interface::trait_impl_ident(&trait_name); + quote! { + fn try_lift(v: Self::FfiType) -> ::uniffi::deps::anyhow::Result<::std::sync::Arc<Self>> { + Ok(::std::sync::Arc::new(<#trait_impl_ident>::new(v as u64))) + } + } + } else { + quote! { + fn try_lift(v: Self::FfiType) -> ::uniffi::deps::anyhow::Result<::std::sync::Arc<Self>> { + unsafe { + Ok(*::std::boxed::Box::from_raw(v as *mut ::std::sync::Arc<Self>)) + } + } + } + }; + let metadata_code = if with_foreign { + quote! { ::uniffi::metadata::codes::TYPE_CALLBACK_TRAIT_INTERFACE } + } else { + quote! { ::uniffi::metadata::codes::TYPE_TRAIT_INTERFACE } + }; + + quote! { + // All traits 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!(dyn #trait_ident: ::core::marker::Sync, ::core::marker::Send); + + unsafe #impl_spec { + type FfiType = *const ::std::os::raw::c_void; + + fn lower(obj: ::std::sync::Arc<Self>) -> Self::FfiType { + ::std::boxed::Box::into_raw(::std::boxed::Box::new(obj)) as *const ::std::os::raw::c_void + } + + #try_lift + + fn write(obj: ::std::sync::Arc<Self>, buf: &mut Vec<u8>) { + ::uniffi::deps::static_assertions::const_assert!(::std::mem::size_of::<*const ::std::ffi::c_void>() <= 8); + ::uniffi::deps::bytes::BufMut::put_u64( + buf, + <Self as ::uniffi::FfiConverterArc<crate::UniFfiTag>>::lower(obj) as u64, + ); + } + + fn try_read(buf: &mut &[u8]) -> ::uniffi::Result<::std::sync::Arc<Self>> { + ::uniffi::deps::static_assertions::const_assert!(::std::mem::size_of::<*const ::std::ffi::c_void>() <= 8); + ::uniffi::check_remaining(buf, 8)?; + <Self as ::uniffi::FfiConverterArc<crate::UniFfiTag>>::try_lift( + ::uniffi::deps::bytes::Buf::get_u64(buf) as Self::FfiType) + } + + const TYPE_ID_META: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(#metadata_code) + .concat_str(#mod_path) + .concat_str(#trait_name); + } + + unsafe #lift_ref_impl_spec { + type LiftType = ::std::sync::Arc<dyn #trait_ident>; + } + } +} diff --git a/third_party/rust/uniffi_macros/src/export/utrait.rs b/third_party/rust/uniffi_macros/src/export/utrait.rs index 3db09ea2b7..9007ae2c56 100644 --- a/third_party/rust/uniffi_macros/src/export/utrait.rs +++ b/third_party/rust/uniffi_macros/src/export/utrait.rs @@ -6,8 +6,10 @@ use proc_macro2::{Ident, TokenStream}; use quote::quote; use syn::ext::IdentExt; -use super::{attributes::ExportAttributeArguments, gen_ffi_function}; +use super::gen_ffi_function; +use crate::export::ExportedImplFnArgs; use crate::fnsig::FnSignature; +use crate::util::extract_docstring; use uniffi_meta::UniffiTraitDiscriminants; pub(crate) fn expand_uniffi_trait_export( @@ -157,12 +159,25 @@ fn process_uniffi_trait_method( unreachable!() }; + let docstring = extract_docstring(&item.attrs)?; + let ffi_func = gen_ffi_function( - &FnSignature::new_method(self_ident.clone(), item.sig.clone())?, - &ExportAttributeArguments::default(), + &FnSignature::new_method( + self_ident.clone(), + item.sig.clone(), + ExportedImplFnArgs::default(), + docstring.clone(), + )?, + &None, udl_mode, )?; // metadata for the method, which will be packed inside metadata for the trait. - let method_meta = FnSignature::new_method(self_ident.clone(), item.sig)?.metadata_expr()?; + let method_meta = FnSignature::new_method( + self_ident.clone(), + item.sig, + ExportedImplFnArgs::default(), + docstring, + )? + .metadata_expr()?; Ok((ffi_func, method_meta)) } |