summaryrefslogtreecommitdiffstats
path: root/third_party/rust/uniffi_macros/src/export
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--third_party/rust/uniffi_macros/src/export.rs211
-rw-r--r--third_party/rust/uniffi_macros/src/export/attributes.rs306
-rw-r--r--third_party/rust/uniffi_macros/src/export/callback_interface.rs204
-rw-r--r--third_party/rust/uniffi_macros/src/export/item.rs88
-rw-r--r--third_party/rust/uniffi_macros/src/export/scaffolding.rs123
-rw-r--r--third_party/rust/uniffi_macros/src/export/trait_interface.rs183
-rw-r--r--third_party/rust/uniffi_macros/src/export/utrait.rs23
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", &quote! { dyn #trait_ident }, udl_mode);
- let lift_ref_impl_spec = tagged_impl_header("LiftRef", &quote! { 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", &quote! { dyn #trait_ident }, udl_mode);
+ let lift_ref_impl_spec = tagged_impl_header("LiftRef", &quote! { 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))
}