diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /third_party/rust/uniffi_macros/src | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/uniffi_macros/src')
-rw-r--r-- | third_party/rust/uniffi_macros/src/custom.rs | 85 | ||||
-rw-r--r-- | third_party/rust/uniffi_macros/src/enum_.rs | 195 | ||||
-rw-r--r-- | third_party/rust/uniffi_macros/src/error.rs | 267 | ||||
-rw-r--r-- | third_party/rust/uniffi_macros/src/export.rs | 291 | ||||
-rw-r--r-- | third_party/rust/uniffi_macros/src/export/attributes.rs | 173 | ||||
-rw-r--r-- | third_party/rust/uniffi_macros/src/export/callback_interface.rs | 175 | ||||
-rw-r--r-- | third_party/rust/uniffi_macros/src/export/item.rs | 200 | ||||
-rw-r--r-- | third_party/rust/uniffi_macros/src/export/scaffolding.rs | 274 | ||||
-rw-r--r-- | third_party/rust/uniffi_macros/src/export/utrait.rs | 168 | ||||
-rw-r--r-- | third_party/rust/uniffi_macros/src/fnsig.rs | 466 | ||||
-rw-r--r-- | third_party/rust/uniffi_macros/src/lib.rs | 410 | ||||
-rw-r--r-- | third_party/rust/uniffi_macros/src/object.rs | 147 | ||||
-rw-r--r-- | third_party/rust/uniffi_macros/src/record.rs | 224 | ||||
-rw-r--r-- | third_party/rust/uniffi_macros/src/setup_scaffolding.rs | 232 | ||||
-rw-r--r-- | third_party/rust/uniffi_macros/src/test.rs | 90 | ||||
-rw-r--r-- | third_party/rust/uniffi_macros/src/util.rs | 278 |
16 files changed, 3675 insertions, 0 deletions
diff --git a/third_party/rust/uniffi_macros/src/custom.rs b/third_party/rust/uniffi_macros/src/custom.rs new file mode 100644 index 0000000000..9d8e5acde6 --- /dev/null +++ b/third_party/rust/uniffi_macros/src/custom.rs @@ -0,0 +1,85 @@ +/* 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 crate::util::{derive_all_ffi_traits, ident_to_string, mod_path, tagged_impl_header}; +use proc_macro2::{Ident, TokenStream}; +use quote::quote; +use syn::Path; + +// Generate an FfiConverter impl based on the UniffiCustomTypeConverter +// implementation that the library supplies +pub(crate) fn expand_ffi_converter_custom_type( + ident: &Ident, + builtin: &Path, + udl_mode: bool, +) -> syn::Result<TokenStream> { + let impl_spec = tagged_impl_header("FfiConverter", ident, udl_mode); + let derive_ffi_traits = derive_all_ffi_traits(ident, udl_mode); + let name = ident_to_string(ident); + let mod_path = mod_path()?; + + Ok(quote! { + #[automatically_derived] + unsafe #impl_spec { + // Note: the builtin type needs to implement both `Lower` and `Lift'. We use the + // `Lower` trait to get the associated type `FfiType` and const `TYPE_ID_META`. These + // can't differ between `Lower` and `Lift`. + type FfiType = <#builtin as ::uniffi::Lower<crate::UniFfiTag>>::FfiType; + fn lower(obj: #ident ) -> Self::FfiType { + <#builtin as ::uniffi::Lower<crate::UniFfiTag>>::lower(<#ident as crate::UniffiCustomTypeConverter>::from_custom(obj)) + } + + fn try_lift(v: Self::FfiType) -> uniffi::Result<#ident> { + <#ident as crate::UniffiCustomTypeConverter>::into_custom(<#builtin as ::uniffi::Lift<crate::UniFfiTag>>::try_lift(v)?) + } + + fn write(obj: #ident, buf: &mut Vec<u8>) { + <#builtin as ::uniffi::Lower<crate::UniFfiTag>>::write(<#ident as crate::UniffiCustomTypeConverter>::from_custom(obj), buf); + } + + fn try_read(buf: &mut &[u8]) -> uniffi::Result<#ident> { + <#ident as crate::UniffiCustomTypeConverter>::into_custom(<#builtin as ::uniffi::Lift<crate::UniFfiTag>>::try_read(buf)?) + } + + const TYPE_ID_META: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::TYPE_CUSTOM) + .concat_str(#mod_path) + .concat_str(#name) + .concat(<#builtin as ::uniffi::Lower<crate::UniFfiTag>>::TYPE_ID_META); + } + + #derive_ffi_traits + }) +} + +// Generate an FfiConverter impl *and* an UniffiCustomTypeConverter. +pub(crate) fn expand_ffi_converter_custom_newtype( + ident: &Ident, + builtin: &Path, + udl_mode: bool, +) -> syn::Result<TokenStream> { + let ffi_converter = expand_ffi_converter_custom_type(ident, builtin, udl_mode)?; + let type_converter = custom_ffi_type_converter(ident, builtin)?; + + Ok(quote! { + #ffi_converter + + #type_converter + }) +} + +fn custom_ffi_type_converter(ident: &Ident, builtin: &Path) -> syn::Result<TokenStream> { + Ok(quote! { + impl crate::UniffiCustomTypeConverter for #ident { + type Builtin = #builtin; + + fn into_custom(val: Self::Builtin) -> uniffi::Result<Self> { + Ok(#ident(val)) + } + + fn from_custom(obj: Self) -> Self::Builtin { + obj.0 + } + } + }) +} diff --git a/third_party/rust/uniffi_macros/src/enum_.rs b/third_party/rust/uniffi_macros/src/enum_.rs new file mode 100644 index 0000000000..32abfa08cc --- /dev/null +++ b/third_party/rust/uniffi_macros/src/enum_.rs @@ -0,0 +1,195 @@ +use proc_macro2::{Ident, Span, TokenStream}; +use quote::quote; +use syn::{Data, DataEnum, DeriveInput, Field, Index}; + +use crate::util::{ + create_metadata_items, derive_all_ffi_traits, ident_to_string, mod_path, tagged_impl_header, + try_metadata_value_from_usize, try_read_field, +}; + +pub fn expand_enum(input: DeriveInput, udl_mode: bool) -> syn::Result<TokenStream> { + let enum_ = match input.data { + Data::Enum(e) => e, + _ => { + return Err(syn::Error::new( + Span::call_site(), + "This derive must only be used on enums", + )) + } + }; + let ident = &input.ident; + let ffi_converter_impl = enum_ffi_converter_impl(ident, &enum_, udl_mode); + + let meta_static_var = (!udl_mode).then(|| { + enum_meta_static_var(ident, &enum_).unwrap_or_else(syn::Error::into_compile_error) + }); + + Ok(quote! { + #ffi_converter_impl + #meta_static_var + }) +} + +pub(crate) fn enum_ffi_converter_impl( + ident: &Ident, + enum_: &DataEnum, + udl_mode: bool, +) -> TokenStream { + enum_or_error_ffi_converter_impl( + ident, + enum_, + udl_mode, + quote! { ::uniffi::metadata::codes::TYPE_ENUM }, + ) +} + +pub(crate) fn rich_error_ffi_converter_impl( + ident: &Ident, + enum_: &DataEnum, + udl_mode: bool, +) -> TokenStream { + enum_or_error_ffi_converter_impl( + ident, + enum_, + udl_mode, + quote! { ::uniffi::metadata::codes::TYPE_ENUM }, + ) +} + +fn enum_or_error_ffi_converter_impl( + ident: &Ident, + enum_: &DataEnum, + udl_mode: bool, + metadata_type_code: TokenStream, +) -> TokenStream { + let name = ident_to_string(ident); + let impl_spec = tagged_impl_header("FfiConverter", ident, udl_mode); + let derive_ffi_traits = derive_all_ffi_traits(ident, udl_mode); + let mod_path = match mod_path() { + Ok(p) => p, + Err(e) => return e.into_compile_error(), + }; + let write_match_arms = enum_.variants.iter().enumerate().map(|(i, v)| { + let v_ident = &v.ident; + let fields = v.fields.iter().map(|f| &f.ident); + let idx = Index::from(i + 1); + let write_fields = v.fields.iter().map(write_field); + + quote! { + Self::#v_ident { #(#fields),* } => { + ::uniffi::deps::bytes::BufMut::put_i32(buf, #idx); + #(#write_fields)* + } + } + }); + let write_impl = quote! { + match obj { #(#write_match_arms)* } + }; + + let try_read_match_arms = enum_.variants.iter().enumerate().map(|(i, v)| { + let idx = Index::from(i + 1); + let v_ident = &v.ident; + let try_read_fields = v.fields.iter().map(try_read_field); + + quote! { + #idx => Self::#v_ident { #(#try_read_fields)* }, + } + }); + let error_format_string = format!("Invalid {ident} enum value: {{}}"); + let try_read_impl = quote! { + ::uniffi::check_remaining(buf, 4)?; + + Ok(match ::uniffi::deps::bytes::Buf::get_i32(buf) { + #(#try_read_match_arms)* + v => ::uniffi::deps::anyhow::bail!(#error_format_string, v), + }) + }; + + quote! { + #[automatically_derived] + unsafe #impl_spec { + ::uniffi::ffi_converter_rust_buffer_lift_and_lower!(crate::UniFfiTag); + + fn write(obj: Self, buf: &mut ::std::vec::Vec<u8>) { + #write_impl + } + + fn try_read(buf: &mut &[::std::primitive::u8]) -> ::uniffi::deps::anyhow::Result<Self> { + #try_read_impl + } + + const TYPE_ID_META: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(#metadata_type_code) + .concat_str(#mod_path) + .concat_str(#name); + } + + #derive_ffi_traits + } +} + +fn write_field(f: &Field) -> TokenStream { + let ident = &f.ident; + let ty = &f.ty; + + quote! { + <#ty as ::uniffi::Lower<crate::UniFfiTag>>::write(#ident, buf); + } +} + +pub(crate) fn enum_meta_static_var(ident: &Ident, enum_: &DataEnum) -> syn::Result<TokenStream> { + let name = ident_to_string(ident); + let module_path = mod_path()?; + + let mut metadata_expr = quote! { + ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::ENUM) + .concat_str(#module_path) + .concat_str(#name) + }; + metadata_expr.extend(variant_metadata(enum_)?); + Ok(create_metadata_items("enum", &name, metadata_expr, None)) +} + +pub fn variant_metadata(enum_: &DataEnum) -> syn::Result<Vec<TokenStream>> { + let variants_len = + try_metadata_value_from_usize(enum_.variants.len(), "UniFFI limits enums to 256 variants")?; + std::iter::once(Ok(quote! { .concat_value(#variants_len) })) + .chain( + enum_.variants + .iter() + .map(|v| { + let fields_len = try_metadata_value_from_usize( + v.fields.len(), + "UniFFI limits enum variants to 256 fields", + )?; + + let field_names = v.fields + .iter() + .map(|f| { + f.ident + .as_ref() + .ok_or_else(|| + syn::Error::new_spanned( + v, + "UniFFI only supports enum variants with named fields (or no fields at all)", + ) + ) + .map(ident_to_string) + }) + .collect::<syn::Result<Vec<_>>>()?; + + let name = ident_to_string(&v.ident); + let field_types = v.fields.iter().map(|f| &f.ty); + Ok(quote! { + .concat_str(#name) + .concat_value(#fields_len) + #( + .concat_str(#field_names) + .concat(<#field_types as ::uniffi::Lower<crate::UniFfiTag>>::TYPE_ID_META) + // field defaults not yet supported for enums + .concat_bool(false) + )* + }) + }) + ) + .collect() +} diff --git a/third_party/rust/uniffi_macros/src/error.rs b/third_party/rust/uniffi_macros/src/error.rs new file mode 100644 index 0000000000..a2ee7cf603 --- /dev/null +++ b/third_party/rust/uniffi_macros/src/error.rs @@ -0,0 +1,267 @@ +use proc_macro2::{Ident, Span, TokenStream}; +use quote::quote; +use syn::{ + parse::{Parse, ParseStream}, + Data, DataEnum, DeriveInput, Index, +}; + +use crate::{ + enum_::{rich_error_ffi_converter_impl, variant_metadata}, + util::{ + chain, create_metadata_items, derive_ffi_traits, either_attribute_arg, ident_to_string, kw, + mod_path, parse_comma_separated, tagged_impl_header, try_metadata_value_from_usize, + AttributeSliceExt, UniffiAttributeArgs, + }, +}; + +pub fn expand_error( + input: DeriveInput, + // Attributes from #[derive_error_for_udl()], if we are in udl mode + attr_from_udl_mode: Option<ErrorAttr>, + udl_mode: bool, +) -> syn::Result<TokenStream> { + let enum_ = match input.data { + Data::Enum(e) => e, + _ => { + return Err(syn::Error::new( + Span::call_site(), + "This derive currently only supports enums", + )); + } + }; + let ident = &input.ident; + let mut attr: ErrorAttr = input.attrs.parse_uniffi_attr_args()?; + if let Some(attr_from_udl_mode) = attr_from_udl_mode { + attr = attr.merge(attr_from_udl_mode)?; + } + let ffi_converter_impl = error_ffi_converter_impl(ident, &enum_, &attr, udl_mode); + let meta_static_var = (!udl_mode).then(|| { + error_meta_static_var(ident, &enum_, attr.flat.is_some()) + .unwrap_or_else(syn::Error::into_compile_error) + }); + + let variant_errors: TokenStream = enum_ + .variants + .iter() + .flat_map(|variant| { + chain( + variant.attrs.uniffi_attr_args_not_allowed_here(), + variant + .fields + .iter() + .flat_map(|field| field.attrs.uniffi_attr_args_not_allowed_here()), + ) + }) + .map(syn::Error::into_compile_error) + .collect(); + + Ok(quote! { + #ffi_converter_impl + #meta_static_var + #variant_errors + }) +} + +fn error_ffi_converter_impl( + ident: &Ident, + enum_: &DataEnum, + attr: &ErrorAttr, + udl_mode: bool, +) -> TokenStream { + if attr.flat.is_some() { + flat_error_ffi_converter_impl(ident, enum_, udl_mode, attr.with_try_read.is_some()) + } else { + rich_error_ffi_converter_impl(ident, enum_, udl_mode) + } +} + +// FfiConverters for "flat errors" +// +// These are errors where we only lower the to_string() value, rather than any assocated data. +// We lower the to_string() value unconditionally, whether the enum has associated data or not. +fn flat_error_ffi_converter_impl( + ident: &Ident, + enum_: &DataEnum, + udl_mode: bool, + implement_lift: bool, +) -> TokenStream { + let name = ident_to_string(ident); + let lower_impl_spec = tagged_impl_header("Lower", ident, udl_mode); + let lift_impl_spec = tagged_impl_header("Lift", ident, udl_mode); + let derive_ffi_traits = derive_ffi_traits(ident, udl_mode, &["ConvertError"]); + let mod_path = match mod_path() { + Ok(p) => p, + Err(e) => return e.into_compile_error(), + }; + + let lower_impl = { + let match_arms = enum_.variants.iter().enumerate().map(|(i, v)| { + let v_ident = &v.ident; + let idx = Index::from(i + 1); + + quote! { + Self::#v_ident { .. } => { + ::uniffi::deps::bytes::BufMut::put_i32(buf, #idx); + <::std::string::String as ::uniffi::Lower<crate::UniFfiTag>>::write(error_msg, buf); + } + } + }); + + quote! { + #[automatically_derived] + unsafe #lower_impl_spec { + type FfiType = ::uniffi::RustBuffer; + + fn write(obj: Self, buf: &mut ::std::vec::Vec<u8>) { + let error_msg = ::std::string::ToString::to_string(&obj); + match obj { #(#match_arms)* } + } + + fn lower(obj: Self) -> ::uniffi::RustBuffer { + <Self as ::uniffi::Lower<crate::UniFfiTag>>::lower_into_rust_buffer(obj) + } + + const TYPE_ID_META: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::TYPE_ENUM) + .concat_str(#mod_path) + .concat_str(#name); + } + } + }; + + let lift_impl = if implement_lift { + let match_arms = enum_.variants.iter().enumerate().map(|(i, v)| { + let v_ident = &v.ident; + let idx = Index::from(i + 1); + + quote! { + #idx => Self::#v_ident, + } + }); + quote! { + #[automatically_derived] + unsafe #lift_impl_spec { + type FfiType = ::uniffi::RustBuffer; + + fn try_read(buf: &mut &[::std::primitive::u8]) -> ::uniffi::deps::anyhow::Result<Self> { + Ok(match ::uniffi::deps::bytes::Buf::get_i32(buf) { + #(#match_arms)* + v => ::uniffi::deps::anyhow::bail!("Invalid #ident enum value: {}", v), + }) + } + + fn try_lift(v: ::uniffi::RustBuffer) -> ::uniffi::deps::anyhow::Result<Self> { + <Self as ::uniffi::Lift<crate::UniFfiTag>>::try_lift_from_rust_buffer(v) + } + + const TYPE_ID_META: ::uniffi::MetadataBuffer = <Self as ::uniffi::Lower<crate::UniFfiTag>>::TYPE_ID_META; + } + + } + } else { + quote! { + // Lifting flat errors is not currently supported, but we still define the trait so + // that dicts containing flat errors don't cause compile errors (see ReturnOnlyDict in + // coverall.rs). + // + // Note: it would be better to not derive `Lift` for dictionaries containing flat + // errors, but getting the trait bounds and derived impls there would be much harder. + // For now, we just fail at runtime. + #[automatically_derived] + unsafe #lift_impl_spec { + type FfiType = ::uniffi::RustBuffer; + + fn try_read(buf: &mut &[::std::primitive::u8]) -> ::uniffi::deps::anyhow::Result<Self> { + panic!("Can't lift flat errors") + } + + fn try_lift(v: ::uniffi::RustBuffer) -> ::uniffi::deps::anyhow::Result<Self> { + panic!("Can't lift flat errors") + } + + const TYPE_ID_META: ::uniffi::MetadataBuffer = <Self as ::uniffi::Lower<crate::UniFfiTag>>::TYPE_ID_META; + } + } + }; + + quote! { + #lower_impl + #lift_impl + #derive_ffi_traits + } +} + +pub(crate) fn error_meta_static_var( + ident: &Ident, + enum_: &DataEnum, + flat: bool, +) -> syn::Result<TokenStream> { + let name = ident_to_string(ident); + let module_path = mod_path()?; + let mut metadata_expr = quote! { + ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::ERROR) + // first our is-flat flag + .concat_bool(#flat) + // followed by an enum + .concat_str(#module_path) + .concat_str(#name) + }; + if flat { + metadata_expr.extend(flat_error_variant_metadata(enum_)?) + } else { + metadata_expr.extend(variant_metadata(enum_)?); + } + Ok(create_metadata_items("error", &name, metadata_expr, None)) +} + +pub fn flat_error_variant_metadata(enum_: &DataEnum) -> syn::Result<Vec<TokenStream>> { + let variants_len = + try_metadata_value_from_usize(enum_.variants.len(), "UniFFI limits enums to 256 variants")?; + Ok(std::iter::once(quote! { .concat_value(#variants_len) }) + .chain(enum_.variants.iter().map(|v| { + let name = ident_to_string(&v.ident); + quote! { .concat_str(#name) } + })) + .collect()) +} + +#[derive(Default)] +pub struct ErrorAttr { + flat: Option<kw::flat_error>, + with_try_read: Option<kw::with_try_read>, +} + +impl UniffiAttributeArgs for ErrorAttr { + fn parse_one(input: ParseStream<'_>) -> syn::Result<Self> { + let lookahead = input.lookahead1(); + if lookahead.peek(kw::flat_error) { + Ok(Self { + flat: input.parse()?, + ..Self::default() + }) + } else if lookahead.peek(kw::with_try_read) { + Ok(Self { + with_try_read: input.parse()?, + ..Self::default() + }) + } else if lookahead.peek(kw::handle_unknown_callback_error) { + // Not used anymore, but still lallowed + Ok(Self::default()) + } else { + Err(lookahead.error()) + } + } + + fn merge(self, other: Self) -> syn::Result<Self> { + Ok(Self { + flat: either_attribute_arg(self.flat, other.flat)?, + with_try_read: either_attribute_arg(self.with_try_read, other.with_try_read)?, + }) + } +} + +// So ErrorAttr can be used with `parse_macro_input!` +impl Parse for ErrorAttr { + fn parse(input: ParseStream<'_>) -> syn::Result<Self> { + parse_comma_separated(input) + } +} diff --git a/third_party/rust/uniffi_macros/src/export.rs b/third_party/rust/uniffi_macros/src/export.rs new file mode 100644 index 0000000000..bbb16acf90 --- /dev/null +++ b/third_party/rust/uniffi_macros/src/export.rs @@ -0,0 +1,291 @@ +/* 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 syn::{visit_mut::VisitMut, Item, Type}; + +mod attributes; +mod callback_interface; +mod item; +mod scaffolding; +mod utrait; + +use self::{ + item::{ExportItem, ImplItem}, + scaffolding::{ + 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; +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, + udl_mode: bool, +) -> syn::Result<TokenStream> { + let mod_path = mod_path()?; + // If the input is an `impl` block, rewrite any uses of the `Self` type + // alias to the actual type, so we don't have to special-case it in the + // metadata collection or scaffolding code generation (which generates + // new functions outside of the `impl`). + rewrite_self_type(&mut item); + + let metadata = ExportItem::new(item, &args)?; + + match metadata { + ExportItem::Function { sig } => gen_fn_scaffolding(sig, &args, udl_mode), + ExportItem::Impl { items, self_ident } => { + if let Some(rt) = &args.async_runtime { + if items + .iter() + .all(|item| !matches!(item, ImplItem::Method(sig) if sig.is_async)) + { + return Err(syn::Error::new_spanned( + rt, + "no async methods in this impl block", + )); + } + } + + 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), + }) + .collect::<syn::Result<_>>()?; + Ok(quote_spanned! { self_ident.span() => #item_tokens }) + } + 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 + }) + } + ExportItem::Trait { + items, + self_ident, + callback_interface: true, + } => { + 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(), + ); + + 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)* + }) + } + ExportItem::Struct { + self_ident, + uniffi_traits, + } => { + assert!(!udl_mode); + utrait::expand_uniffi_trait_export(self_ident, uniffi_traits) + } + } +} + +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, +/// +/// ```ignore +/// impl some::module::Foo { +/// fn method( +/// self: Arc<Self>, +/// arg: Option<Bar<(), Self>>, +/// ) -> Result<Self, Error> { +/// todo!() +/// } +/// } +/// ``` +/// +/// will be rewritten to +/// +/// ```ignore +/// impl some::module::Foo { +/// fn method( +/// self: Arc<some::module::Foo>, +/// arg: Option<Bar<(), some::module::Foo>>, +/// ) -> Result<some::module::Foo, Error> { +/// todo!() +/// } +/// } +/// ``` +pub fn rewrite_self_type(item: &mut Item) { + let item = match item { + Item::Impl(i) => i, + _ => return, + }; + + struct RewriteSelfVisitor<'a>(&'a Type); + + impl<'a> VisitMut for RewriteSelfVisitor<'a> { + fn visit_type_mut(&mut self, i: &mut Type) { + match i { + Type::Path(p) if p.qself.is_none() && p.path.is_ident("Self") => { + *i = self.0.clone(); + } + _ => syn::visit_mut::visit_type_mut(self, i), + } + } + } + + let mut visitor = RewriteSelfVisitor(&item.self_ty); + for item in &mut item.items { + visitor.visit_impl_item_mut(item); + } +} diff --git a/third_party/rust/uniffi_macros/src/export/attributes.rs b/third_party/rust/uniffi_macros/src/export/attributes.rs new file mode 100644 index 0000000000..c3edcd5920 --- /dev/null +++ b/third_party/rust/uniffi_macros/src/export/attributes.rs @@ -0,0 +1,173 @@ +use crate::util::{either_attribute_arg, kw, parse_comma_separated, UniffiAttributeArgs}; + +use proc_macro2::TokenStream; +use quote::ToTokens; +use syn::{ + parse::{Parse, ParseStream}, + Attribute, LitStr, Meta, PathArguments, PathSegment, Token, +}; + +#[derive(Default)] +pub struct ExportAttributeArguments { + 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>, +} + +impl Parse for ExportAttributeArguments { + fn parse(input: ParseStream<'_>) -> syn::Result<Self> { + parse_comma_separated(input) + } +} + +impl UniffiAttributeArgs for ExportAttributeArguments { + 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 { + async_runtime: Some(input.parse()?), + ..Self::default() + }) + } else if lookahead.peek(kw::callback_interface) { + Ok(Self { + callback_interface: input.parse()?, + ..Self::default() + }) + } else if lookahead.peek(kw::constructor) { + Ok(Self { + constructor: input.parse()?, + ..Self::default() + }) + } else if lookahead.peek(kw::Debug) { + Ok(Self { + trait_debug: input.parse()?, + ..Self::default() + }) + } else if lookahead.peek(kw::Display) { + Ok(Self { + trait_display: input.parse()?, + ..Self::default() + }) + } else if lookahead.peek(kw::Hash) { + Ok(Self { + trait_hash: input.parse()?, + ..Self::default() + }) + } else if lookahead.peek(kw::Eq) { + Ok(Self { + trait_eq: input.parse()?, + ..Self::default() + }) + } else { + Ok(Self::default()) + } + } + + 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)?, + }) + } +} + +pub(crate) enum AsyncRuntime { + Tokio(LitStr), +} + +impl Parse for AsyncRuntime { + fn parse(input: ParseStream<'_>) -> syn::Result<Self> { + let lit: LitStr = input.parse()?; + match lit.value().as_str() { + "tokio" => Ok(Self::Tokio(lit)), + _ => Err(syn::Error::new_spanned( + lit, + "unknown async runtime, currently only `tokio` is supported", + )), + } + } +} + +impl ToTokens for AsyncRuntime { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + AsyncRuntime::Tokio(lit) => lit.to_tokens(tokens), + } + } +} + +#[derive(Default)] +pub(super) struct ExportedImplFnAttributes { + pub constructor: bool, +} + +impl ExportedImplFnAttributes { + pub fn new(attrs: &[Attribute]) -> syn::Result<Self> { + let mut this = Self::default(); + for attr in attrs { + let segs = &attr.path().segments; + + let fst = segs + .first() + .expect("attributes have at least one path segment"); + if fst.ident != "uniffi" { + continue; + } + 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", + )); + } + + if segs.len() != 2 { + return Err(syn::Error::new_spanned( + segs, + "unsupported uniffi attribute", + )); + } + let snd = &segs[1]; + ensure_no_path_args(snd)?; + + match snd.ident.to_string().as_str() { + "constructor" => { + if this.constructor { + return Err(syn::Error::new_spanned( + attr, + "duplicate constructor attribute", + )); + } + this.constructor = true; + } + _ => return Err(syn::Error::new_spanned(snd, "unknown uniffi attribute")), + } + } + + Ok(this) + } +} + +fn ensure_no_path_args(seg: &PathSegment) -> syn::Result<()> { + if matches!(seg.arguments, PathArguments::None) { + Ok(()) + } else { + Err(syn::Error::new_spanned(&seg.arguments, "unexpected syntax")) + } +} diff --git a/third_party/rust/uniffi_macros/src/export/callback_interface.rs b/third_party/rust/uniffi_macros/src/export/callback_interface.rs new file mode 100644 index 0000000000..2f2561bbc2 --- /dev/null +++ b/third_party/rust/uniffi_macros/src/export/callback_interface.rs @@ -0,0 +1,175 @@ +/* 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 crate::{ + export::ImplItem, + fnsig::{FnKind, FnSignature, ReceiverArg}, + util::{create_metadata_items, ident_to_string, mod_path, tagged_impl_header}, +}; +use proc_macro2::{Span, TokenStream}; +use quote::quote; +use std::iter; +use syn::Ident; + +pub(super) fn trait_impl( + ident: &Ident, + trait_ident: &Ident, + internals_ident: &Ident, + items: &[ImplItem], +) -> syn::Result<TokenStream> { + let trait_impl_methods = items + .iter() + .map(|item| match item { + ImplItem::Method(sig) => gen_method_impl(sig, internals_ident), + _ => unreachable!("traits have no constructors"), + }) + .collect::<syn::Result<TokenStream>>()?; + let ffi_converter_tokens = ffi_converter_callback_interface_impl(trait_ident, ident, false); + + Ok(quote! { + #[doc(hidden)] + #[derive(Debug)] + struct #ident { + handle: u64, + } + + 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); + + impl #trait_ident for #ident { + #trait_impl_methods + } + + #ffi_converter_tokens + }) +} + +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 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 mod_path = match mod_path() { + Ok(p) => p, + Err(e) => return e.into_compile_error(), + }; + + quote! { + #[doc(hidden)] + #[automatically_derived] + unsafe #lift_impl_spec { + type FfiType = u64; + + fn try_lift(v: Self::FfiType) -> ::uniffi::deps::anyhow::Result<Self> { + Ok(::std::boxed::Box::new(<#trait_impl_ident>::new(v))) + } + + fn try_read(buf: &mut &[u8]) -> ::uniffi::deps::anyhow::Result<Self> { + use uniffi::deps::bytes::Buf; + ::uniffi::check_remaining(buf, 8)?; + <Self as ::uniffi::Lift<crate::UniFfiTag>>::try_lift(buf.get_u64()) + } + + const TYPE_ID_META: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code( + ::uniffi::metadata::codes::TYPE_CALLBACK_INTERFACE, + ) + .concat_str(#mod_path) + .concat_str(#name); + } + + unsafe #lift_ref_impl_spec { + type LiftType = #box_dyn_trait; + } + } +} + +fn gen_method_impl(sig: &FnSignature, internals_ident: &Ident) -> syn::Result<TokenStream> { + let FnSignature { + ident, + return_ty, + kind, + receiver, + .. + } = 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:?}" + ), + )); + } + }; + + let self_param = match receiver { + None => { + return Err(syn::Error::new( + sig.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); + + 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); + + #internals_ident.invoke_callback::<#return_ty, crate::UniFfiTag>(self.handle, #index, uniffi_args_rbuf) + } + }) +} + +pub(super) fn metadata_items( + self_ident: &Ident, + items: &[ImplItem], + module_path: &str, +) -> syn::Result<Vec<TokenStream>> { + let trait_name = ident_to_string(self_ident); + let callback_interface_items = create_metadata_items( + "callback_interface", + &trait_name, + quote! { + ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::CALLBACK_INTERFACE) + .concat_str(#module_path) + .concat_str(#trait_name) + }, + None, + ); + + iter::once(Ok(callback_interface_items)) + .chain(items.iter().map(|item| match item { + ImplItem::Method(sig) => sig.metadata_items(), + _ => 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 new file mode 100644 index 0000000000..98c7d0ebe2 --- /dev/null +++ b/third_party/rust/uniffi_macros/src/export/item.rs @@ -0,0 +1,200 @@ +/* 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 crate::fnsig::FnSignature; +use proc_macro2::{Ident, Span}; +use quote::ToTokens; + +use super::attributes::{ExportAttributeArguments, ExportedImplFnAttributes}; +use uniffi_meta::UniffiTraitDiscriminants; + +pub(super) enum ExportItem { + Function { + sig: FnSignature, + }, + Impl { + self_ident: Ident, + items: Vec<ImplItem>, + }, + Trait { + self_ident: Ident, + items: Vec<ImplItem>, + callback_interface: bool, + }, + Struct { + self_ident: Ident, + uniffi_traits: Vec<UniffiTraitDiscriminants>, + }, +} + +impl ExportItem { + pub fn new(item: syn::Item, args: &ExportAttributeArguments) -> syn::Result<Self> { + match item { + syn::Item::Fn(item) => { + let sig = FnSignature::new_function(item.sig)?; + Ok(Self::Function { sig }) + } + 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), + // FIXME: Support const / static? + _ => Err(syn::Error::new( + Span::call_site(), + "unsupported item: only functions and impl \ + blocks may be annotated with this attribute", + )), + } + } + + pub fn from_impl(item: syn::ItemImpl, force_constructor: bool) -> syn::Result<Self> { + if !item.generics.params.is_empty() || item.generics.where_clause.is_some() { + return Err(syn::Error::new_spanned( + &item.generics, + "generic impls are not currently supported by uniffi::export", + )); + } + + let type_path = type_as_type_path(&item.self_ty)?; + + if type_path.qself.is_some() { + return Err(syn::Error::new_spanned( + type_path, + "qualified self types are not currently supported by uniffi::export", + )); + } + + let self_ident = match type_path.path.get_ident() { + Some(id) => id, + None => { + return Err(syn::Error::new_spanned( + type_path, + "qualified paths in self-types are not currently supported by uniffi::export", + )); + } + }; + + let items = item + .items + .into_iter() + .map(|item| { + let impl_fn = match item { + syn::ImplItem::Fn(m) => m, + _ => { + return Err(syn::Error::new_spanned( + item, + "only fn's are supported in impl blocks annotated with uniffi::export", + )); + } + }; + + let attrs = ExportedImplFnAttributes::new(&impl_fn.attrs)?; + let item = if force_constructor || attrs.constructor { + ImplItem::Constructor(FnSignature::new_constructor( + self_ident.clone(), + impl_fn.sig, + )?) + } else { + ImplItem::Method(FnSignature::new_method(self_ident.clone(), impl_fn.sig)?) + }; + + Ok(item) + }) + .collect::<syn::Result<_>>()?; + + Ok(Self::Impl { + items, + self_ident: self_ident.to_owned(), + }) + } + + fn from_trait(item: syn::ItemTrait, callback_interface: bool) -> syn::Result<Self> { + if !item.generics.params.is_empty() || item.generics.where_clause.is_some() { + return Err(syn::Error::new_spanned( + &item.generics, + "generic impls are not currently supported by uniffi::export", + )); + } + + let self_ident = item.ident.to_owned(); + let items = item + .items + .into_iter() + .enumerate() + .map(|(i, item)| { + let tim = match item { + syn::TraitItem::Fn(tim) => tim, + _ => { + return Err(syn::Error::new_spanned( + item, + "only fn's are supported in traits annotated with uniffi::export", + )); + } + }; + + let attrs = ExportedImplFnAttributes::new(&tim.attrs)?; + let item = if attrs.constructor { + return Err(syn::Error::new_spanned( + tim, + "exported traits can not have constructors", + )); + } else { + ImplItem::Method(FnSignature::new_trait_method( + self_ident.clone(), + tim.sig, + i as u32, + )?) + }; + + Ok(item) + }) + .collect::<syn::Result<_>>()?; + + Ok(Self::Trait { + items, + self_ident, + callback_interface, + }) + } + + 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); + } + Ok(Self::Struct { + self_ident: item.ident, + uniffi_traits, + }) + } +} + +pub(super) enum ImplItem { + Constructor(FnSignature), + Method(FnSignature), +} + +fn type_as_type_path(ty: &syn::Type) -> syn::Result<&syn::TypePath> { + match ty { + syn::Type::Group(g) => type_as_type_path(&g.elem), + syn::Type::Paren(p) => type_as_type_path(&p.elem), + syn::Type::Path(p) => Ok(p), + _ => Err(type_not_supported(ty)), + } +} + +fn type_not_supported(ty: &impl ToTokens) -> syn::Error { + syn::Error::new_spanned( + ty, + "this type is not currently supported by uniffi::export in this position", + ) +} diff --git a/third_party/rust/uniffi_macros/src/export/scaffolding.rs b/third_party/rust/uniffi_macros/src/export/scaffolding.rs new file mode 100644 index 0000000000..f120ccc880 --- /dev/null +++ b/third_party/rust/uniffi_macros/src/export/scaffolding.rs @@ -0,0 +1,274 @@ +/* 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, TokenStream}; +use quote::quote; +use std::iter; + +use super::attributes::{AsyncRuntime, ExportAttributeArguments}; +use crate::fnsig::{FnKind, FnSignature, NamedArg}; + +pub(super) fn gen_fn_scaffolding( + sig: FnSignature, + arguments: &ExportAttributeArguments, + udl_mode: bool, +) -> syn::Result<TokenStream> { + if sig.receiver.is_some() { + return Err(syn::Error::new( + sig.span, + "Unexpected self param (Note: uniffi::export must be used on the impl block, not its containing fn's)" + )); + } + if !sig.is_async { + if let Some(async_runtime) = &arguments.async_runtime { + return Err(syn::Error::new_spanned( + async_runtime, + "this attribute is only allowed on async functions", + )); + } + } + 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)?; + Ok(quote! { + #scaffolding_func + #metadata_items + }) +} + +pub(super) fn gen_constructor_scaffolding( + sig: FnSignature, + arguments: &ExportAttributeArguments, + udl_mode: bool, +) -> syn::Result<TokenStream> { + if sig.receiver.is_some() { + return Err(syn::Error::new( + sig.span, + "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)?; + Ok(quote! { + #scaffolding_func + #metadata_items + }) +} + +pub(super) fn gen_method_scaffolding( + sig: FnSignature, + arguments: &ExportAttributeArguments, + udl_mode: bool, +) -> syn::Result<TokenStream> { + let scaffolding_func = if sig.receiver.is_none() { + return Err(syn::Error::new( + sig.span, + "associated functions are not currently supported", + )); + } else { + gen_ffi_function(&sig, arguments, udl_mode)? + }; + + let metadata_items = (!udl_mode).then(|| { + sig.metadata_items() + .unwrap_or_else(syn::Error::into_compile_error) + }); + Ok(quote! { + #scaffolding_func + #metadata_items + }) +} + +// Pieces of code for the scaffolding function +struct ScaffoldingBits { + /// Parameters for the scaffolding function + params: 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, +} + +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) } + } else { + rust_fn_call + }; + + Self { + params, + lift_closure: sig.lift_closure(None), + rust_fn_call, + } + } + + fn new_for_method( + sig: &FnSignature, + self_ident: &Ident, + is_trait: bool, + udl_mode: bool, + ) -> Self { + let ident = &sig.ident; + let ffi_converter = if is_trait { + quote! { + <::std::sync::Arc<dyn #self_ident> as ::uniffi::FfiConverter<crate::UniFfiTag>> + } + } else { + quote! { + <::std::sync::Arc<#self_ident> as ::uniffi::FfiConverter<crate::UniFfiTag>> + } + }; + let params: Vec<_> = iter::once(quote! { uniffi_self_lowered: #ffi_converter::FfiType }) + .chain(sig.scaffolding_params()) + .collect(); + let lift_closure = sig.lift_closure(Some(quote! { + match #ffi_converter::try_lift(uniffi_self_lowered) { + Ok(v) => v, + Err(e) => return Err(("self", e)) + } + })); + 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) } + } else { + rust_fn_call + }; + + Self { + params, + lift_closure, + rust_fn_call, + } + } + + 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) { + // For UDL + (true, false) => quote! { ::std::sync::Arc::new(#rust_fn_call) }, + (true, true) => { + quote! { #rust_fn_call.map(::std::sync::Arc::new).map_err(::std::convert::Into::into) } + } + (false, _) => rust_fn_call, + }; + + Self { + params, + lift_closure: sig.lift_closure(None), + rust_fn_call, + } + } +} + +/// Generate a scaffolding function +/// +/// `pre_fn_call` is the statements that we should execute before the rust call +/// `rust_fn` is the Rust function to call. +pub(super) fn gen_ffi_function( + sig: &FnSignature, + arguments: &ExportAttributeArguments, + udl_mode: bool, +) -> syn::Result<TokenStream> { + let ScaffoldingBits { + params, + lift_closure, + rust_fn_call, + } = match &sig.kind { + FnKind::Function => ScaffoldingBits::new_for_function(sig, udl_mode), + FnKind::Method { self_ident } => { + ScaffoldingBits::new_for_method(sig, self_ident, false, udl_mode) + } + FnKind::TraitMethod { self_ident, .. } => { + ScaffoldingBits::new_for_method(sig, self_ident, true, udl_mode) + } + FnKind::Constructor { self_ident } => { + ScaffoldingBits::new_for_constructor(sig, self_ident, udl_mode) + } + }; + // Scaffolding functions are logically `pub`, but we don't use that in UDL mode since UDL has + // historically not required types to be `pub` + let vis = match udl_mode { + false => quote! { pub }, + true => quote! {}, + }; + + let ffi_ident = sig.scaffolding_fn_ident()?; + let name = &sig.name; + let return_impl = &sig.return_impl(); + + Ok(if !sig.is_async { + quote! { + #[doc(hidden)] + #[no_mangle] + #vis extern "C" fn #ffi_ident( + #(#params,)* + call_status: &mut ::uniffi::RustCallStatus, + ) -> #return_impl::ReturnType { + ::uniffi::deps::log::debug!(#name); + let uniffi_lift_args = #lift_closure; + ::uniffi::rust_call(call_status, || { + #return_impl::lower_return( + match uniffi_lift_args() { + Ok(uniffi_args) => #rust_fn_call, + Err((arg_name, anyhow_error)) => { + #return_impl::handle_failed_lift(arg_name, anyhow_error) + }, + } + ) + }) + } + } + } else { + let mut future_expr = rust_fn_call; + if matches!(arguments.async_runtime, 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 { + ::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 }, + crate::UniFfiTag + ) + }, + Err((arg_name, anyhow_error)) => { + ::uniffi::rust_future_new( + async move { + #return_impl::handle_failed_lift(arg_name, anyhow_error) + }, + crate::UniFfiTag, + ) + }, + } + } + } + }) +} diff --git a/third_party/rust/uniffi_macros/src/export/utrait.rs b/third_party/rust/uniffi_macros/src/export/utrait.rs new file mode 100644 index 0000000000..3db09ea2b7 --- /dev/null +++ b/third_party/rust/uniffi_macros/src/export/utrait.rs @@ -0,0 +1,168 @@ +/* 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, TokenStream}; +use quote::quote; +use syn::ext::IdentExt; + +use super::{attributes::ExportAttributeArguments, gen_ffi_function}; +use crate::fnsig::FnSignature; +use uniffi_meta::UniffiTraitDiscriminants; + +pub(crate) fn expand_uniffi_trait_export( + self_ident: Ident, + uniffi_traits: Vec<UniffiTraitDiscriminants>, +) -> syn::Result<TokenStream> { + let udl_mode = false; + let mut impl_items = Vec::new(); + let mut global_items = Vec::new(); + for trait_id in uniffi_traits { + match trait_id { + UniffiTraitDiscriminants::Debug => { + let method = quote! { + fn uniffi_trait_debug(&self) -> String { + ::uniffi::deps::static_assertions::assert_impl_all!(#self_ident: ::std::fmt::Debug); + format!("{:?}", self) + } + }; + let (ffi_func, method_meta) = + process_uniffi_trait_method(&method, &self_ident, udl_mode)?; + // metadata for the trait - which includes metadata for the method. + let discr = UniffiTraitDiscriminants::Debug as u8; + let trait_meta = crate::util::create_metadata_items( + "uniffi_trait", + &format!("{}_Debug", self_ident.unraw()), + quote! { + ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::UNIFFI_TRAIT) + .concat_value(#discr) + .concat(#method_meta) + }, + None, + ); + impl_items.push(method); + global_items.push(ffi_func); + global_items.push(trait_meta); + } + UniffiTraitDiscriminants::Display => { + let method = quote! { + fn uniffi_trait_display(&self) -> String { + ::uniffi::deps::static_assertions::assert_impl_all!(#self_ident: ::std::fmt::Display); + format!("{}", self) + } + }; + let (ffi_func, method_meta) = + process_uniffi_trait_method(&method, &self_ident, udl_mode)?; + // metadata for the trait - which includes metadata for the method. + let discr = UniffiTraitDiscriminants::Display as u8; + let trait_meta = crate::util::create_metadata_items( + "uniffi_trait", + &format!("{}_Display", self_ident.unraw()), + quote! { + ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::UNIFFI_TRAIT) + .concat_value(#discr) + .concat(#method_meta) + }, + None, + ); + impl_items.push(method); + global_items.push(ffi_func); + global_items.push(trait_meta); + } + UniffiTraitDiscriminants::Hash => { + let method = quote! { + fn uniffi_trait_hash(&self) -> u64 { + use ::std::hash::{Hash, Hasher}; + ::uniffi::deps::static_assertions::assert_impl_all!(#self_ident: Hash); + let mut s = ::std::collections::hash_map::DefaultHasher::new(); + Hash::hash(self, &mut s); + s.finish() + } + }; + let (ffi_func, method_meta) = + process_uniffi_trait_method(&method, &self_ident, udl_mode)?; + // metadata for the trait - which includes metadata for the hash method. + let discr = UniffiTraitDiscriminants::Hash as u8; + let trait_meta = crate::util::create_metadata_items( + "uniffi_trait", + &format!("{}_Hash", self_ident.unraw()), + quote! { + ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::UNIFFI_TRAIT) + .concat_value(#discr) + .concat(#method_meta) + }, + None, + ); + impl_items.push(method); + global_items.push(ffi_func); + global_items.push(trait_meta); + } + UniffiTraitDiscriminants::Eq => { + let method_eq = quote! { + fn uniffi_trait_eq_eq(&self, other: &#self_ident) -> bool { + use ::std::cmp::PartialEq; + uniffi::deps::static_assertions::assert_impl_all!(#self_ident: PartialEq); // This object has a trait method which requires `PartialEq` be implemented. + PartialEq::eq(self, other) + } + }; + let method_ne = quote! { + fn uniffi_trait_eq_ne(&self, other: &#self_ident) -> bool { + use ::std::cmp::PartialEq; + uniffi::deps::static_assertions::assert_impl_all!(#self_ident: PartialEq); // This object has a trait method which requires `PartialEq` be implemented. + PartialEq::ne(self, other) + } + }; + let (ffi_func_eq, method_meta_eq) = + process_uniffi_trait_method(&method_eq, &self_ident, udl_mode)?; + let (ffi_func_ne, method_meta_ne) = + process_uniffi_trait_method(&method_ne, &self_ident, udl_mode)?; + // metadata for the trait itself. + let discr = UniffiTraitDiscriminants::Eq as u8; + let trait_meta = crate::util::create_metadata_items( + "uniffi_trait", + &format!("{}_Eq", self_ident.unraw()), + quote! { + ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::UNIFFI_TRAIT) + .concat_value(#discr) + .concat(#method_meta_eq) + .concat(#method_meta_ne) + }, + None, + ); + impl_items.push(method_eq); + impl_items.push(method_ne); + global_items.push(ffi_func_eq); + global_items.push(ffi_func_ne); + global_items.push(trait_meta); + } + } + } + Ok(quote! { + #[doc(hidden)] + impl #self_ident { + #(#impl_items)* + } + #(#global_items)* + }) +} + +fn process_uniffi_trait_method( + method: &TokenStream, + self_ident: &Ident, + udl_mode: bool, +) -> syn::Result<(TokenStream, TokenStream)> { + let item = syn::parse(method.clone().into())?; + + let syn::Item::Fn(item) = item else { + unreachable!() + }; + + let ffi_func = gen_ffi_function( + &FnSignature::new_method(self_ident.clone(), item.sig.clone())?, + &ExportAttributeArguments::default(), + 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()?; + Ok((ffi_func, method_meta)) +} diff --git a/third_party/rust/uniffi_macros/src/fnsig.rs b/third_party/rust/uniffi_macros/src/fnsig.rs new file mode 100644 index 0000000000..69b6529d1e --- /dev/null +++ b/third_party/rust/uniffi_macros/src/fnsig.rs @@ -0,0 +1,466 @@ +/* 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 crate::util::{ + create_metadata_items, ident_to_string, mod_path, try_metadata_value_from_usize, +}; +use proc_macro2::{Span, TokenStream}; +use quote::quote; +use syn::{spanned::Spanned, FnArg, Ident, Pat, Receiver, ReturnType, Type}; + +pub(crate) struct FnSignature { + pub kind: FnKind, + pub span: Span, + pub mod_path: String, + pub ident: Ident, + pub name: String, + pub is_async: bool, + pub receiver: Option<ReceiverArg>, + pub args: Vec<NamedArg>, + pub return_ty: TokenStream, + // Does this the return type look like a result? + // Only use this in UDL mode. + // In general, it's not reliable because it fails for type aliases. + pub looks_like_result: bool, +} + +impl FnSignature { + pub(crate) fn new_function(sig: syn::Signature) -> syn::Result<Self> { + Self::new(FnKind::Function, sig) + } + + pub(crate) fn new_method(self_ident: Ident, sig: syn::Signature) -> syn::Result<Self> { + Self::new(FnKind::Method { self_ident }, sig) + } + + pub(crate) fn new_constructor(self_ident: Ident, sig: syn::Signature) -> syn::Result<Self> { + Self::new(FnKind::Constructor { self_ident }, sig) + } + + pub(crate) fn new_trait_method( + self_ident: Ident, + sig: syn::Signature, + index: u32, + ) -> syn::Result<Self> { + Self::new(FnKind::TraitMethod { self_ident, index }, sig) + } + + pub(crate) fn new(kind: FnKind, sig: syn::Signature) -> syn::Result<Self> { + let span = sig.span(); + let ident = sig.ident; + let looks_like_result = looks_like_result(&sig.output); + let output = match sig.output { + ReturnType::Default => quote! { () }, + ReturnType::Type(_, ty) => quote! { #ty }, + }; + let is_async = sig.asyncness.is_some(); + + if is_async && matches!(kind, FnKind::Constructor { .. }) { + return Err(syn::Error::new( + span, + "Async constructors are not supported", + )); + } + + let mut input_iter = sig.inputs.into_iter().map(Arg::try_from).peekable(); + + let receiver = input_iter + .next_if(|a| matches!(a, Ok(a) if a.is_receiver())) + .map(|a| match a { + Ok(Arg { + kind: ArgKind::Receiver(r), + .. + }) => r, + _ => unreachable!(), + }); + let args = input_iter + .map(|a| { + a.and_then(|a| match a.kind { + ArgKind::Named(named) => Ok(named), + ArgKind::Receiver(_) => { + Err(syn::Error::new(a.span, "Unexpected receiver argument")) + } + }) + }) + .collect::<syn::Result<Vec<_>>>()?; + let mod_path = mod_path()?; + + Ok(Self { + kind, + span, + mod_path, + name: ident_to_string(&ident), + ident, + is_async, + receiver, + args, + return_ty: output, + looks_like_result, + }) + } + + pub fn return_impl(&self) -> TokenStream { + let return_ty = &self.return_ty; + quote! { + <#return_ty as ::uniffi::LowerReturn<crate::UniFfiTag>> + } + } + + /// Generate a closure that tries to lift all arguments into a tuple. + /// + /// The closure moves all scaffolding arguments into itself and returns: + /// - The lifted argument tuple on success + /// - The field name and error on failure (`Err(&'static str, anyhow::Error>`) + pub fn lift_closure(&self, self_lift: Option<TokenStream>) -> TokenStream { + let arg_lifts = self.args.iter().map(|arg| { + let ident = &arg.ident; + let lift_impl = arg.lift_impl(); + let name = &arg.name; + quote! { + match #lift_impl::try_lift(#ident) { + Ok(v) => v, + Err(e) => return Err((#name, e)), + } + } + }); + let all_lifts = self_lift.into_iter().chain(arg_lifts); + quote! { + move || Ok(( + #(#all_lifts,)* + )) + } + } + + /// Call a Rust function from a [Self::lift_closure] success. + /// + /// This takes an Ok value returned by `lift_closure` with the name `uniffi_args` and generates + /// a series of parameters to pass to the Rust function. + pub fn rust_call_params(&self, self_lift: bool) -> TokenStream { + let start_idx = if self_lift { 1 } else { 0 }; + let args = self.args.iter().enumerate().map(|(i, arg)| { + let idx = syn::Index::from(i + start_idx); + let ty = &arg.ty; + match &arg.ref_type { + None => quote! { uniffi_args.#idx }, + Some(ref_type) => quote! { + <#ty as ::std::borrow::Borrow<#ref_type>>::borrow(&uniffi_args.#idx) + }, + } + }); + quote! { #(#args),* } + } + + /// Write expressions for each of our arguments + pub fn write_exprs<'a>( + &'a self, + buf_ident: &'a Ident, + ) -> impl Iterator<Item = TokenStream> + 'a { + self.args.iter().map(|a| a.write_expr(buf_ident)) + } + + /// Parameters expressions for each of our arguments + pub fn params(&self) -> impl Iterator<Item = TokenStream> + '_ { + self.args.iter().map(NamedArg::param) + } + + /// Name of the scaffolding function to generate for this function + pub fn scaffolding_fn_ident(&self) -> syn::Result<Ident> { + let name = &self.name; + let name = match &self.kind { + FnKind::Function => uniffi_meta::fn_symbol_name(&self.mod_path, name), + FnKind::Method { self_ident } | FnKind::TraitMethod { self_ident, .. } => { + uniffi_meta::method_symbol_name(&self.mod_path, &ident_to_string(self_ident), name) + } + FnKind::Constructor { self_ident } => uniffi_meta::constructor_symbol_name( + &self.mod_path, + &ident_to_string(self_ident), + name, + ), + }; + Ok(Ident::new(&name, Span::call_site())) + } + + /// Scaffolding parameters expressions for each of our arguments + pub fn scaffolding_params(&self) -> impl Iterator<Item = TokenStream> + '_ { + self.args.iter().map(NamedArg::scaffolding_param) + } + + /// Generate metadata items for this function + pub(crate) fn metadata_expr(&self) -> syn::Result<TokenStream> { + let Self { + name, + return_ty, + is_async, + mod_path, + .. + } = &self; + let args_len = try_metadata_value_from_usize( + // Use param_lifts to calculate this instead of sig.inputs to avoid counting any self + // params + self.args.len(), + "UniFFI limits functions to 256 arguments", + )?; + let arg_metadata_calls = self.args.iter().map(NamedArg::arg_metadata); + + match &self.kind { + FnKind::Function => Ok(quote! { + ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::FUNC) + .concat_str(#mod_path) + .concat_str(#name) + .concat_bool(#is_async) + .concat_value(#args_len) + #(#arg_metadata_calls)* + .concat(<#return_ty as ::uniffi::LowerReturn<crate::UniFfiTag>>::TYPE_ID_META) + }), + + FnKind::Method { self_ident } => { + let object_name = ident_to_string(self_ident); + Ok(quote! { + ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::METHOD) + .concat_str(#mod_path) + .concat_str(#object_name) + .concat_str(#name) + .concat_bool(#is_async) + .concat_value(#args_len) + #(#arg_metadata_calls)* + .concat(<#return_ty as ::uniffi::LowerReturn<crate::UniFfiTag>>::TYPE_ID_META) + }) + } + + FnKind::TraitMethod { self_ident, index } => { + let object_name = ident_to_string(self_ident); + Ok(quote! { + ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::TRAIT_METHOD) + .concat_str(#mod_path) + .concat_str(#object_name) + .concat_u32(#index) + .concat_str(#name) + .concat_bool(#is_async) + .concat_value(#args_len) + #(#arg_metadata_calls)* + .concat(<#return_ty as ::uniffi::LowerReturn<crate::UniFfiTag>>::TYPE_ID_META) + }) + } + + FnKind::Constructor { self_ident } => { + let object_name = ident_to_string(self_ident); + Ok(quote! { + ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::CONSTRUCTOR) + .concat_str(#mod_path) + .concat_str(#object_name) + .concat_str(#name) + .concat_value(#args_len) + #(#arg_metadata_calls)* + .concat(<#return_ty as ::uniffi::LowerReturn<crate::UniFfiTag>>::TYPE_ID_META) + }) + } + } + } + + pub(crate) fn metadata_items(&self) -> syn::Result<TokenStream> { + let Self { name, .. } = &self; + match &self.kind { + FnKind::Function => Ok(create_metadata_items( + "func", + name, + self.metadata_expr()?, + Some(self.checksum_symbol_name()), + )), + + FnKind::Method { self_ident } => { + let object_name = ident_to_string(self_ident); + Ok(create_metadata_items( + "method", + &format!("{object_name}_{name}"), + self.metadata_expr()?, + Some(self.checksum_symbol_name()), + )) + } + + FnKind::TraitMethod { self_ident, .. } => { + let object_name = ident_to_string(self_ident); + Ok(create_metadata_items( + "method", + &format!("{object_name}_{name}"), + self.metadata_expr()?, + Some(self.checksum_symbol_name()), + )) + } + + FnKind::Constructor { self_ident } => { + let object_name = ident_to_string(self_ident); + Ok(create_metadata_items( + "constructor", + &format!("{object_name}_{name}"), + self.metadata_expr()?, + Some(self.checksum_symbol_name()), + )) + } + } + } + + pub(crate) fn checksum_symbol_name(&self) -> String { + let name = &self.name; + match &self.kind { + FnKind::Function => uniffi_meta::fn_checksum_symbol_name(&self.mod_path, name), + FnKind::Method { self_ident } | FnKind::TraitMethod { self_ident, .. } => { + uniffi_meta::method_checksum_symbol_name( + &self.mod_path, + &ident_to_string(self_ident), + name, + ) + } + FnKind::Constructor { self_ident } => uniffi_meta::constructor_checksum_symbol_name( + &self.mod_path, + &ident_to_string(self_ident), + name, + ), + } + } +} + +pub(crate) struct Arg { + pub(crate) span: Span, + pub(crate) kind: ArgKind, +} + +pub(crate) enum ArgKind { + Receiver(ReceiverArg), + Named(NamedArg), +} + +impl Arg { + pub(crate) fn is_receiver(&self) -> bool { + matches!(self.kind, ArgKind::Receiver(_)) + } +} + +impl TryFrom<FnArg> for Arg { + type Error = syn::Error; + + fn try_from(syn_arg: FnArg) -> syn::Result<Self> { + let span = syn_arg.span(); + let kind = match syn_arg { + FnArg::Typed(p) => match *p.pat { + Pat::Ident(i) => Ok(ArgKind::Named(NamedArg::new(i.ident, &p.ty))), + _ => Err(syn::Error::new_spanned(p, "Argument name missing")), + }, + FnArg::Receiver(receiver) => Ok(ArgKind::Receiver(ReceiverArg::from(receiver))), + }?; + + Ok(Self { span, kind }) + } +} + +pub(crate) enum ReceiverArg { + Ref, + Arc, +} + +impl From<Receiver> for ReceiverArg { + fn from(receiver: Receiver) -> Self { + if let Type::Path(p) = *receiver.ty { + if let Some(segment) = p.path.segments.last() { + // This comparison will fail if a user uses a typedef for Arc. Maybe we could + // implement some system like TYPE_ID_META to figure this out from the type system. + // However, this seems good enough for now. + if segment.ident == "Arc" { + return ReceiverArg::Arc; + } + } + } + Self::Ref + } +} + +pub(crate) struct NamedArg { + pub(crate) ident: Ident, + pub(crate) name: String, + pub(crate) ty: TokenStream, + pub(crate) ref_type: Option<Type>, +} + +impl NamedArg { + pub(crate) fn new(ident: Ident, ty: &Type) -> Self { + match ty { + Type::Reference(r) => { + let inner = &r.elem; + Self { + name: ident_to_string(&ident), + ident, + ty: quote! { <#inner as ::uniffi::LiftRef<crate::UniFfiTag>>::LiftType }, + ref_type: Some(*inner.clone()), + } + } + _ => Self { + name: ident_to_string(&ident), + ident, + ty: quote! { #ty }, + ref_type: None, + }, + } + } + + pub(crate) fn lift_impl(&self) -> TokenStream { + let ty = &self.ty; + quote! { <#ty as ::uniffi::Lift<crate::UniFfiTag>> } + } + + pub(crate) fn lower_impl(&self) -> TokenStream { + let ty = &self.ty; + quote! { <#ty as ::uniffi::Lower<crate::UniFfiTag>> } + } + + /// Generate the parameter for this Arg + pub(crate) fn param(&self) -> TokenStream { + let ident = &self.ident; + let ty = &self.ty; + quote! { #ident: #ty } + } + + /// Generate the scaffolding parameter for this Arg + pub(crate) fn scaffolding_param(&self) -> TokenStream { + let ident = &self.ident; + let lift_impl = self.lift_impl(); + quote! { #ident: #lift_impl::FfiType } + } + + /// Generate the expression to write the scaffolding parameter for this arg + pub(crate) fn write_expr(&self, buf_ident: &Ident) -> TokenStream { + let ident = &self.ident; + let lower_impl = self.lower_impl(); + quote! { #lower_impl::write(#ident, &mut #buf_ident) } + } + + pub(crate) fn arg_metadata(&self) -> TokenStream { + let name = &self.name; + let lift_impl = self.lift_impl(); + quote! { + .concat_str(#name) + .concat(#lift_impl::TYPE_ID_META) + } + } +} + +fn looks_like_result(return_type: &ReturnType) -> bool { + if let ReturnType::Type(_, ty) = return_type { + if let Type::Path(p) = &**ty { + if let Some(seg) = p.path.segments.last() { + if seg.ident == "Result" { + return true; + } + } + } + } + + false +} + +#[derive(Debug)] +pub(crate) enum FnKind { + Function, + Constructor { self_ident: Ident }, + Method { self_ident: Ident }, + TraitMethod { self_ident: Ident, index: u32 }, +} diff --git a/third_party/rust/uniffi_macros/src/lib.rs b/third_party/rust/uniffi_macros/src/lib.rs new file mode 100644 index 0000000000..4cffddfa0e --- /dev/null +++ b/third_party/rust/uniffi_macros/src/lib.rs @@ -0,0 +1,410 @@ +/* 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/. */ +#![cfg_attr(feature = "nightly", feature(proc_macro_expand))] +#![warn(rust_2018_idioms, unused_qualifications)] + +//! Macros for `uniffi`. +//! +//! Currently this is just for easily generating integration tests, but maybe +//! we'll put some other code-annotation helper macros in here at some point. + +use camino::Utf8Path; +use proc_macro::TokenStream; +use quote::quote; +use syn::{ + parse::{Parse, ParseStream}, + parse_macro_input, Ident, LitStr, Path, Token, +}; + +mod custom; +mod enum_; +mod error; +mod export; +mod fnsig; +mod object; +mod record; +mod setup_scaffolding; +mod test; +mod util; + +use self::{ + enum_::expand_enum, error::expand_error, export::expand_export, object::expand_object, + record::expand_record, +}; + +struct IdentPair { + lhs: Ident, + rhs: Ident, +} + +impl Parse for IdentPair { + fn parse(input: ParseStream<'_>) -> syn::Result<Self> { + let lhs = input.parse()?; + input.parse::<Token![,]>()?; + let rhs = input.parse()?; + Ok(Self { lhs, rhs }) + } +} + +struct CustomTypeInfo { + ident: Ident, + builtin: Path, +} + +impl Parse for CustomTypeInfo { + fn parse(input: ParseStream<'_>) -> syn::Result<Self> { + let ident = input.parse()?; + input.parse::<Token![,]>()?; + let builtin = input.parse()?; + Ok(Self { ident, builtin }) + } +} + +/// A macro to build testcases for a component's generated bindings. +/// +/// This macro provides some plumbing to write automated tests for the generated +/// foreign language bindings of a component. As a component author, you can write +/// script files in the target foreign language(s) that exercise you component API, +/// and then call this macro to produce a `cargo test` testcase from each one. +/// The generated code will execute your script file with appropriate configuration and +/// environment to let it load the component bindings, and will pass iff the script +/// exits successfully. +/// +/// To use it, invoke the macro with the name of a fixture/example crate as the first argument, +/// then one or more file paths relative to the crate root directory. It will produce one `#[test]` +/// function per file, in a manner designed to play nicely with `cargo test` and its test filtering +/// options. +#[proc_macro] +pub fn build_foreign_language_testcases(tokens: TokenStream) -> TokenStream { + test::build_foreign_language_testcases(tokens) +} + +/// Top-level initialization macro +/// +/// The optional namespace argument is only used by the scaffolding templates to pass in the +/// CI namespace. +#[proc_macro] +pub fn setup_scaffolding(tokens: TokenStream) -> TokenStream { + let namespace = match syn::parse_macro_input!(tokens as Option<LitStr>) { + Some(lit_str) => lit_str.value(), + None => match util::mod_path() { + Ok(v) => v, + Err(e) => return e.into_compile_error().into(), + }, + }; + setup_scaffolding::setup_scaffolding(namespace) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} + +#[proc_macro_attribute] +pub fn export(attr_args: TokenStream, input: TokenStream) -> TokenStream { + do_export(attr_args, input, false) +} + +fn do_export(attr_args: TokenStream, input: TokenStream, udl_mode: bool) -> TokenStream { + let copied_input = (!udl_mode).then(|| proc_macro2::TokenStream::from(input.clone())); + + let gen_output = || { + let args = syn::parse(attr_args)?; + let item = syn::parse(input)?; + expand_export(item, args, udl_mode) + }; + let output = gen_output().unwrap_or_else(syn::Error::into_compile_error); + + quote! { + #copied_input + #output + } + .into() +} + +#[proc_macro_derive(Record, attributes(uniffi))] +pub fn derive_record(input: TokenStream) -> TokenStream { + expand_record(parse_macro_input!(input), false) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} + +#[proc_macro_derive(Enum)] +pub fn derive_enum(input: TokenStream) -> TokenStream { + expand_enum(parse_macro_input!(input), false) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} + +#[proc_macro_derive(Object)] +pub fn derive_object(input: TokenStream) -> TokenStream { + expand_object(parse_macro_input!(input), false) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} + +#[proc_macro_derive(Error, attributes(uniffi))] +pub fn derive_error(input: TokenStream) -> TokenStream { + expand_error(parse_macro_input!(input), None, false) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} + +/// Generate the `FfiConverter` implementation for a Custom Type - ie, +/// for a `<T>` which implements `UniffiCustomTypeConverter`. +#[proc_macro] +pub fn custom_type(tokens: TokenStream) -> TokenStream { + let input: CustomTypeInfo = syn::parse_macro_input!(tokens); + custom::expand_ffi_converter_custom_type(&input.ident, &input.builtin, true) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} + +/// Generate the `FfiConverter` and the `UniffiCustomTypeConverter` implementations for a +/// Custom Type - ie, for a `<T>` which implements `UniffiCustomTypeConverter` via the +/// newtype idiom. +#[proc_macro] +pub fn custom_newtype(tokens: TokenStream) -> TokenStream { + let input: CustomTypeInfo = syn::parse_macro_input!(tokens); + custom::expand_ffi_converter_custom_newtype(&input.ident, &input.builtin, true) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} + +// == derive_for_udl and export_for_udl == +// +// The Askama templates generate placeholder items wrapped with these attributes. The goal is to +// have all scaffolding generation go through the same code path. +// +// The one difference is that derive-style attributes are not allowed inside attribute macro +// inputs. Instead, we take the attributes from the macro invocation itself. +// +// Instead of: +// +// ``` +// #[derive(Error) +// #[uniffi(flat_error]) +// enum { .. } +// ``` +// +// We have: +// +// ``` +// #[derive_error_for_udl(flat_error)] +// enum { ... } +// ``` +// +// # Differences between UDL-mode and normal mode +// +// ## Metadata symbols / checksum functions +// +// In UDL mode, we don't export the static metadata symbols or generate the checksum +// functions. This could be changed, but there doesn't seem to be much benefit at this point. +// +// ## The FfiConverter<UT> parameter +// +// In UDL-mode, we only implement `FfiConverter` for the local tag (`FfiConverter<crate::UniFfiTag>`) +// +// The reason for this split is remote types, i.e. types defined in remote crates that we +// don't control and therefore can't define a blanket impl on because of the orphan rules. +// +// With UDL, we handle this by only implementing `FfiConverter<crate::UniFfiTag>` for the +// type. This gets around the orphan rules since a local type is in the trait, but requires +// a `uniffi::ffi_converter_forward!` call if the type is used in a second local crate (an +// External typedef). This is natural for UDL-based generation, since you always need to +// define the external type in the UDL file. +// +// With proc-macros this system isn't so natural. Instead, we create a blanket implementation +// for all UT and support for remote types is still TODO. + +#[doc(hidden)] +#[proc_macro_attribute] +pub fn derive_record_for_udl(_attrs: TokenStream, input: TokenStream) -> TokenStream { + expand_record(syn::parse_macro_input!(input), true) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} + +#[doc(hidden)] +#[proc_macro_attribute] +pub fn derive_enum_for_udl(_attrs: TokenStream, input: TokenStream) -> TokenStream { + expand_enum(syn::parse_macro_input!(input), true) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} + +#[doc(hidden)] +#[proc_macro_attribute] +pub fn derive_error_for_udl(attrs: TokenStream, input: TokenStream) -> TokenStream { + expand_error( + syn::parse_macro_input!(input), + Some(syn::parse_macro_input!(attrs)), + true, + ) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} + +#[doc(hidden)] +#[proc_macro_attribute] +pub fn derive_object_for_udl(_attrs: TokenStream, input: TokenStream) -> TokenStream { + expand_object(syn::parse_macro_input!(input), true) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} + +#[doc(hidden)] +#[proc_macro_attribute] +pub fn export_for_udl(attrs: TokenStream, input: TokenStream) -> TokenStream { + do_export(attrs, input, true) +} + +/// Generate various support elements, including the FfiConverter implementation, +/// for a trait interface for the scaffolding code +#[doc(hidden)] +#[proc_macro] +pub fn expand_trait_interface_support(tokens: TokenStream) -> TokenStream { + export::ffi_converter_trait_impl(&syn::parse_macro_input!(tokens), true).into() +} + +/// Generate the FfiConverter implementation for an trait interface for the scaffolding code +#[doc(hidden)] +#[proc_macro] +pub fn scaffolding_ffi_converter_callback_interface(tokens: TokenStream) -> TokenStream { + let input: IdentPair = syn::parse_macro_input!(tokens); + export::ffi_converter_callback_interface_impl(&input.lhs, &input.rhs, true).into() +} + +/// A helper macro to include generated component scaffolding. +/// +/// This is a simple convenience macro to include the UniFFI component +/// scaffolding as built by `uniffi_build::generate_scaffolding`. +/// Use it like so: +/// +/// ```rs +/// uniffi_macros::include_scaffolding!("my_component_name"); +/// ``` +/// +/// This will expand to the appropriate `include!` invocation to include +/// the generated `my_component_name.uniffi.rs` (which it assumes has +/// been successfully built by your crate's `build.rs` script). +#[proc_macro] +pub fn include_scaffolding(udl_stem: TokenStream) -> TokenStream { + let udl_stem = syn::parse_macro_input!(udl_stem as LitStr); + if std::env::var("OUT_DIR").is_err() { + quote! { + compile_error!("This macro assumes the crate has a build.rs script, but $OUT_DIR is not present"); + } + } else { + let toml_path = match util::manifest_path() { + Ok(path) => path.display().to_string(), + Err(_) => { + return quote! { + compile_error!("This macro assumes the crate has a build.rs script, but $OUT_DIR is not present"); + }.into(); + } + }; + + quote! { + // FIXME(HACK): + // Include the `Cargo.toml` file into the build. + // That way cargo tracks the file and other tools relying on file + // tracking see it as well. + // See https://bugzilla.mozilla.org/show_bug.cgi?id=1846223 + // In the future we should handle that by using the `track_path::path` API, + // see https://github.com/rust-lang/rust/pull/84029 + #[allow(dead_code)] + mod __unused { + const _: &[u8] = include_bytes!(#toml_path); + } + + include!(concat!(env!("OUT_DIR"), "/", #udl_stem, ".uniffi.rs")); + } + }.into() +} + +// Use a UniFFI types from dependent crates that uses UDL files +// See the derive_for_udl and export_for_udl section for a discussion of why this is needed. +#[proc_macro] +pub fn use_udl_record(tokens: TokenStream) -> TokenStream { + use_udl_simple_type(tokens) +} + +#[proc_macro] +pub fn use_udl_enum(tokens: TokenStream) -> TokenStream { + use_udl_simple_type(tokens) +} + +#[proc_macro] +pub fn use_udl_error(tokens: TokenStream) -> TokenStream { + use_udl_simple_type(tokens) +} + +fn use_udl_simple_type(tokens: TokenStream) -> TokenStream { + let util::ExternalTypeItem { + crate_ident, + type_ident, + .. + } = parse_macro_input!(tokens); + quote! { + ::uniffi::ffi_converter_forward!(#type_ident, #crate_ident::UniFfiTag, crate::UniFfiTag); + } + .into() +} + +#[proc_macro] +pub fn use_udl_object(tokens: TokenStream) -> TokenStream { + let util::ExternalTypeItem { + crate_ident, + type_ident, + .. + } = parse_macro_input!(tokens); + quote! { + ::uniffi::ffi_converter_arc_forward!(#type_ident, #crate_ident::UniFfiTag, crate::UniFfiTag); + }.into() +} + +/// A helper macro to generate and include component scaffolding. +/// +/// This is a convenience macro designed for writing `trybuild`-style tests and +/// probably shouldn't be used for production code. Given the path to a `.udl` file, +/// if will run `uniffi-bindgen` to produce the corresponding Rust scaffolding and then +/// include it directly into the calling file. Like so: +/// +/// ```rs +/// uniffi_macros::generate_and_include_scaffolding!("path/to/my/interface.udl"); +/// ``` +#[proc_macro] +pub fn generate_and_include_scaffolding(udl_file: TokenStream) -> TokenStream { + let udl_file = syn::parse_macro_input!(udl_file as LitStr); + let udl_file_string = udl_file.value(); + let udl_file_path = Utf8Path::new(&udl_file_string); + if std::env::var("OUT_DIR").is_err() { + quote! { + compile_error!("This macro assumes the crate has a build.rs script, but $OUT_DIR is not present"); + } + } else if let Err(e) = uniffi_build::generate_scaffolding(udl_file_path) { + let err = format!("{e:#}"); + quote! { + compile_error!(concat!("Failed to generate scaffolding from UDL file at ", #udl_file, ": ", #err)); + } + } else { + // We know the filename is good because `generate_scaffolding` succeeded, + // so this `unwrap` will never fail. + let name = LitStr::new(udl_file_path.file_stem().unwrap(), udl_file.span()); + quote! { + uniffi_macros::include_scaffolding!(#name); + } + }.into() +} + +/// A dummy macro that does nothing. +/// +/// This exists so `#[uniffi::export]` can emit its input verbatim without +/// causing unexpected errors, plus some extra code in case everything is okay. +/// +/// It is important for `#[uniffi::export]` to not raise unexpected errors if it +/// fails to parse the input as this happens very often when the proc-macro is +/// run on an incomplete input by rust-analyzer while the developer is typing. +#[proc_macro_attribute] +pub fn constructor(_attrs: TokenStream, input: TokenStream) -> TokenStream { + input +} diff --git a/third_party/rust/uniffi_macros/src/object.rs b/third_party/rust/uniffi_macros/src/object.rs new file mode 100644 index 0000000000..573a1eaadd --- /dev/null +++ b/third_party/rust/uniffi_macros/src/object.rs @@ -0,0 +1,147 @@ +use proc_macro2::{Ident, Span, TokenStream}; +use quote::quote; +use syn::DeriveInput; +use uniffi_meta::free_fn_symbol_name; + +use crate::util::{create_metadata_items, ident_to_string, mod_path, tagged_impl_header}; + +pub fn expand_object(input: DeriveInput, udl_mode: bool) -> syn::Result<TokenStream> { + let module_path = mod_path()?; + let ident = &input.ident; + let name = ident_to_string(ident); + let free_fn_ident = Ident::new(&free_fn_symbol_name(&module_path, &name), Span::call_site()); + let meta_static_var = (!udl_mode).then(|| { + interface_meta_static_var(ident, false, &module_path) + .unwrap_or_else(syn::Error::into_compile_error) + }); + let interface_impl = interface_impl(ident, udl_mode); + + Ok(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()); + let ptr = ptr.cast::<#ident>(); + unsafe { + ::std::sync::Arc::decrement_strong_count(ptr); + } + Ok(()) + }); + } + + #interface_impl + #meta_static_var + }) +} + +pub(crate) fn interface_impl(ident: &Ident, udl_mode: bool) -> TokenStream { + let name = ident_to_string(ident); + let impl_spec = tagged_impl_header("FfiConverterArc", ident, udl_mode); + let lift_ref_impl_spec = tagged_impl_header("LiftRef", ident, udl_mode); + let mod_path = match mod_path() { + Ok(p) => p, + Err(e) => return e.into_compile_error(), + }; + + quote! { + // All Object structs must be `Sync + Send`. The generated scaffolding will fail to compile + // if they are not, but unfortunately it fails with an unactionably obscure error message. + // By asserting the requirement explicitly, we help Rust produce a more scrutable error message + // and thus help the user debug why the requirement isn't being met. + uniffi::deps::static_assertions::assert_impl_all!(#ident: Sync, Send); + + #[doc(hidden)] + #[automatically_derived] + /// Support for passing reference-counted shared objects via the FFI. + /// + /// To avoid dealing with complex lifetime semantics over the FFI, any data passed + /// by reference must be encapsulated in an `Arc`, and must be safe to share + /// across threads. + unsafe #impl_spec { + // Don't use a pointer to <T> as that requires a `pub <T>` + type FfiType = *const ::std::os::raw::c_void; + + /// When lowering, we have an owned `Arc` and we transfer that ownership + /// to the foreign-language code, "leaking" it out of Rust's ownership system + /// as a raw pointer. This works safely because we have unique ownership of `self`. + /// The foreign-language code is responsible for freeing this by calling the + /// `ffi_object_free` FFI function provided by the corresponding UniFFI type. + /// + /// Safety: when freeing the resulting pointer, the foreign-language code must + /// call the destructor function specific to the type `T`. Calling the destructor + /// function for other types may lead to undefined behaviour. + fn lower(obj: ::std::sync::Arc<Self>) -> Self::FfiType { + ::std::sync::Arc::into_raw(obj) as Self::FfiType + } + + /// When lifting, we receive a "borrow" of the `Arc` that is owned by + /// the foreign-language code, and make a clone of it for our own use. + /// + /// Safety: the provided value must be a pointer previously obtained by calling + /// the `lower()` or `write()` method of this impl. + fn try_lift(v: Self::FfiType) -> ::uniffi::Result<::std::sync::Arc<Self>> { + let v = v as *const #ident; + // We musn't drop the `Arc` that is owned by the foreign-language code. + let foreign_arc = ::std::mem::ManuallyDrop::new(unsafe { ::std::sync::Arc::<Self>::from_raw(v) }); + // Take a clone for our own use. + Ok(::std::sync::Arc::clone(&*foreign_arc)) + } + + /// When writing as a field of a complex structure, make a clone and transfer ownership + /// of it to the foreign-language code by writing its pointer into the buffer. + /// The foreign-language code is responsible for freeing this by calling the + /// `ffi_object_free` FFI function provided by the corresponding UniFFI type. + /// + /// Safety: when freeing the resulting pointer, the foreign-language code must + /// call the destructor function specific to the type `T`. Calling the destructor + /// function for other types may lead to undefined behaviour. + fn write(obj: ::std::sync::Arc<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); + } + + /// When reading as a field of a complex structure, we receive a "borrow" of the `Arc` + /// that is owned by the foreign-language code, and make a clone for our own use. + /// + /// Safety: the buffer must contain a pointer previously obtained by calling + /// the `lower()` or `write()` method of this impl. + fn try_read(buf: &mut &[u8]) -> ::uniffi::Result<::std::sync::Arc<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(false); + } + + unsafe #lift_ref_impl_spec { + type LiftType = ::std::sync::Arc<Self>; + } + } +} + +pub(crate) fn interface_meta_static_var( + ident: &Ident, + is_trait: bool, + module_path: &str, +) -> syn::Result<TokenStream> { + let name = ident_to_string(ident); + Ok(create_metadata_items( + "interface", + &name, + quote! { + ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::INTERFACE) + .concat_str(#module_path) + .concat_str(#name) + .concat_bool(#is_trait) + }, + None, + )) +} diff --git a/third_party/rust/uniffi_macros/src/record.rs b/third_party/rust/uniffi_macros/src/record.rs new file mode 100644 index 0000000000..abf2743ec6 --- /dev/null +++ b/third_party/rust/uniffi_macros/src/record.rs @@ -0,0 +1,224 @@ +use proc_macro2::{Ident, Span, TokenStream}; +use quote::{quote, ToTokens}; +use syn::{ + parse::{Parse, ParseStream}, + Data, DataStruct, DeriveInput, Field, Lit, Token, +}; + +use crate::util::{ + create_metadata_items, derive_all_ffi_traits, either_attribute_arg, ident_to_string, kw, + mod_path, tagged_impl_header, try_metadata_value_from_usize, try_read_field, AttributeSliceExt, + UniffiAttributeArgs, +}; + +pub fn expand_record(input: DeriveInput, udl_mode: bool) -> syn::Result<TokenStream> { + let record = match input.data { + Data::Struct(s) => s, + _ => { + return Err(syn::Error::new( + Span::call_site(), + "This derive must only be used on structs", + )); + } + }; + + let ident = &input.ident; + let ffi_converter = record_ffi_converter_impl(ident, &record, udl_mode) + .unwrap_or_else(syn::Error::into_compile_error); + let meta_static_var = (!udl_mode).then(|| { + record_meta_static_var(ident, &record).unwrap_or_else(syn::Error::into_compile_error) + }); + + Ok(quote! { + #ffi_converter + #meta_static_var + }) +} + +pub(crate) fn record_ffi_converter_impl( + ident: &Ident, + record: &DataStruct, + udl_mode: bool, +) -> syn::Result<TokenStream> { + let impl_spec = tagged_impl_header("FfiConverter", ident, udl_mode); + let derive_ffi_traits = derive_all_ffi_traits(ident, udl_mode); + let name = ident_to_string(ident); + let mod_path = mod_path()?; + let write_impl: TokenStream = record.fields.iter().map(write_field).collect(); + let try_read_fields: TokenStream = record.fields.iter().map(try_read_field).collect(); + + Ok(quote! { + #[automatically_derived] + unsafe #impl_spec { + ::uniffi::ffi_converter_rust_buffer_lift_and_lower!(crate::UniFfiTag); + + fn write(obj: Self, buf: &mut ::std::vec::Vec<u8>) { + #write_impl + } + + fn try_read(buf: &mut &[::std::primitive::u8]) -> ::uniffi::deps::anyhow::Result<Self> { + Ok(Self { #try_read_fields }) + } + + const TYPE_ID_META: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::TYPE_RECORD) + .concat_str(#mod_path) + .concat_str(#name); + } + + #derive_ffi_traits + }) +} + +fn write_field(f: &Field) -> TokenStream { + let ident = &f.ident; + let ty = &f.ty; + + quote! { + <#ty as ::uniffi::Lower<crate::UniFfiTag>>::write(obj.#ident, buf); + } +} + +pub enum FieldDefault { + Literal(Lit), + Null(kw::None), +} + +impl ToTokens for FieldDefault { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + FieldDefault::Literal(lit) => lit.to_tokens(tokens), + FieldDefault::Null(kw) => kw.to_tokens(tokens), + } + } +} + +impl Parse for FieldDefault { + fn parse(input: ParseStream<'_>) -> syn::Result<Self> { + let lookahead = input.lookahead1(); + if lookahead.peek(kw::None) { + let none_kw: kw::None = input.parse()?; + Ok(Self::Null(none_kw)) + } else { + Ok(Self::Literal(input.parse()?)) + } + } +} + +#[derive(Default)] +pub struct FieldAttributeArguments { + pub(crate) default: Option<FieldDefault>, +} + +impl UniffiAttributeArgs for FieldAttributeArguments { + fn parse_one(input: ParseStream<'_>) -> syn::Result<Self> { + let _: kw::default = input.parse()?; + let _: Token![=] = input.parse()?; + let default = input.parse()?; + Ok(Self { + default: Some(default), + }) + } + + fn merge(self, other: Self) -> syn::Result<Self> { + Ok(Self { + default: either_attribute_arg(self.default, other.default)?, + }) + } +} + +pub(crate) fn record_meta_static_var( + ident: &Ident, + record: &DataStruct, +) -> syn::Result<TokenStream> { + let name = ident_to_string(ident); + let module_path = mod_path()?; + let fields_len = + try_metadata_value_from_usize(record.fields.len(), "UniFFI limits structs to 256 fields")?; + + let concat_fields: TokenStream = record + .fields + .iter() + .map(|f| { + let attrs = f + .attrs + .parse_uniffi_attr_args::<FieldAttributeArguments>()?; + + let name = ident_to_string(f.ident.as_ref().unwrap()); + let ty = &f.ty; + let default = match attrs.default { + Some(default) => { + let default_value = default_value_concat_calls(default)?; + quote! { + .concat_bool(true) + #default_value + } + } + None => quote! { .concat_bool(false) }, + }; + + // Note: fields need to implement both `Lower` and `Lift` to be used in a record. The + // TYPE_ID_META should be the same for both traits. + Ok(quote! { + .concat_str(#name) + .concat(<#ty as ::uniffi::Lower<crate::UniFfiTag>>::TYPE_ID_META) + #default + }) + }) + .collect::<syn::Result<_>>()?; + + Ok(create_metadata_items( + "record", + &name, + quote! { + ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::RECORD) + .concat_str(#module_path) + .concat_str(#name) + .concat_value(#fields_len) + #concat_fields + }, + None, + )) +} + +fn default_value_concat_calls(default: FieldDefault) -> syn::Result<TokenStream> { + match default { + FieldDefault::Literal(Lit::Int(i)) if !i.suffix().is_empty() => Err( + syn::Error::new_spanned(i, "integer literals with suffix not supported here"), + ), + FieldDefault::Literal(Lit::Float(f)) if !f.suffix().is_empty() => Err( + syn::Error::new_spanned(f, "float literals with suffix not supported here"), + ), + + FieldDefault::Literal(Lit::Str(s)) => Ok(quote! { + .concat_value(::uniffi::metadata::codes::LIT_STR) + .concat_str(#s) + }), + FieldDefault::Literal(Lit::Int(i)) => { + let digits = i.base10_digits(); + Ok(quote! { + .concat_value(::uniffi::metadata::codes::LIT_INT) + .concat_str(#digits) + }) + } + FieldDefault::Literal(Lit::Float(f)) => { + let digits = f.base10_digits(); + Ok(quote! { + .concat_value(::uniffi::metadata::codes::LIT_FLOAT) + .concat_str(#digits) + }) + } + FieldDefault::Literal(Lit::Bool(b)) => Ok(quote! { + .concat_value(::uniffi::metadata::codes::LIT_BOOL) + .concat_bool(#b) + }), + + FieldDefault::Literal(_) => Err(syn::Error::new_spanned( + default, + "this type of literal is not currently supported as a default", + )), + + FieldDefault::Null(_) => Ok(quote! { + .concat_value(::uniffi::metadata::codes::LIT_NULL) + }), + } +} diff --git a/third_party/rust/uniffi_macros/src/setup_scaffolding.rs b/third_party/rust/uniffi_macros/src/setup_scaffolding.rs new file mode 100644 index 0000000000..afdb119cc4 --- /dev/null +++ b/third_party/rust/uniffi_macros/src/setup_scaffolding.rs @@ -0,0 +1,232 @@ +/* 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::TokenStream; +use quote::{format_ident, quote}; +use syn::Result; + +use crate::util::mod_path; +use uniffi_meta::UNIFFI_CONTRACT_VERSION; + +pub fn setup_scaffolding(namespace: String) -> Result<TokenStream> { + let module_path = mod_path()?; + let ffi_contract_version_ident = format_ident!("ffi_{module_path}_uniffi_contract_version"); + let namespace_upper = namespace.to_ascii_uppercase(); + let namespace_const_ident = format_ident!("UNIFFI_META_CONST_NAMESPACE_{namespace_upper}"); + let namespace_static_ident = format_ident!("UNIFFI_META_NAMESPACE_{namespace_upper}"); + let ffi_rustbuffer_alloc_ident = format_ident!("ffi_{module_path}_rustbuffer_alloc"); + let ffi_rustbuffer_from_bytes_ident = format_ident!("ffi_{module_path}_rustbuffer_from_bytes"); + let ffi_rustbuffer_free_ident = format_ident!("ffi_{module_path}_rustbuffer_free"); + let ffi_rustbuffer_reserve_ident = format_ident!("ffi_{module_path}_rustbuffer_reserve"); + let reexport_hack_ident = format_ident!("{module_path}_uniffi_reexport_hack"); + let ffi_foreign_executor_callback_set_ident = + format_ident!("ffi_{module_path}_foreign_executor_callback_set"); + let ffi_rust_future_continuation_callback_set = + format_ident!("ffi_{module_path}_rust_future_continuation_callback_set"); + let ffi_rust_future_scaffolding_fns = rust_future_scaffolding_fns(&module_path); + let continuation_cell = format_ident!( + "RUST_FUTURE_CONTINUATION_CALLBACK_CELL_{}", + module_path.to_uppercase() + ); + + Ok(quote! { + // Unit struct to parameterize the FfiConverter trait. + // + // We use FfiConverter<UniFfiTag> to handle lowering/lifting/serializing types for this crate. See + // https://mozilla.github.io/uniffi-rs/internals/lifting_and_lowering.html#code-generation-and-the-fficonverter-trait + // for details. + // + // This is pub, since we need to access it to support external types + #[doc(hidden)] + pub struct UniFfiTag; + + #[allow(clippy::missing_safety_doc, missing_docs)] + #[doc(hidden)] + #[no_mangle] + pub extern "C" fn #ffi_contract_version_ident() -> u32 { + #UNIFFI_CONTRACT_VERSION + } + + + /// Export namespace metadata. + /// + /// See `uniffi_bindgen::macro_metadata` for how this is used. + + const #namespace_const_ident: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::NAMESPACE) + .concat_str(#module_path) + .concat_str(#namespace); + + #[doc(hidden)] + #[no_mangle] + pub static #namespace_static_ident: [u8; #namespace_const_ident.size] = #namespace_const_ident.into_array(); + + // Everybody gets basic buffer support, since it's needed for passing complex types over the FFI. + // + // See `uniffi/src/ffi/rustbuffer.rs` for documentation on these functions + + #[allow(clippy::missing_safety_doc, missing_docs)] + #[doc(hidden)] + #[no_mangle] + pub extern "C" fn #ffi_rustbuffer_alloc_ident(size: i32, call_status: &mut uniffi::RustCallStatus) -> uniffi::RustBuffer { + uniffi::ffi::uniffi_rustbuffer_alloc(size, call_status) + } + + #[allow(clippy::missing_safety_doc, missing_docs)] + #[doc(hidden)] + #[no_mangle] + pub unsafe extern "C" fn #ffi_rustbuffer_from_bytes_ident(bytes: uniffi::ForeignBytes, call_status: &mut uniffi::RustCallStatus) -> uniffi::RustBuffer { + uniffi::ffi::uniffi_rustbuffer_from_bytes(bytes, call_status) + } + + #[allow(clippy::missing_safety_doc, missing_docs)] + #[doc(hidden)] + #[no_mangle] + pub unsafe extern "C" fn #ffi_rustbuffer_free_ident(buf: uniffi::RustBuffer, call_status: &mut uniffi::RustCallStatus) { + uniffi::ffi::uniffi_rustbuffer_free(buf, call_status); + } + + #[allow(clippy::missing_safety_doc, missing_docs)] + #[doc(hidden)] + #[no_mangle] + pub unsafe extern "C" fn #ffi_rustbuffer_reserve_ident(buf: uniffi::RustBuffer, additional: i32, call_status: &mut uniffi::RustCallStatus) -> uniffi::RustBuffer { + uniffi::ffi::uniffi_rustbuffer_reserve(buf, additional, call_status) + } + + static #continuation_cell: ::uniffi::deps::once_cell::sync::OnceCell<::uniffi::RustFutureContinuationCallback> = ::uniffi::deps::once_cell::sync::OnceCell::new(); + + #[allow(clippy::missing_safety_doc, missing_docs)] + #[doc(hidden)] + #[no_mangle] + pub extern "C" fn #ffi_foreign_executor_callback_set_ident(callback: uniffi::ffi::ForeignExecutorCallback) { + uniffi::ffi::foreign_executor_callback_set(callback) + } + + #[allow(clippy::missing_safety_doc, missing_docs)] + #[doc(hidden)] + #[no_mangle] + pub unsafe extern "C" fn #ffi_rust_future_continuation_callback_set(callback: ::uniffi::RustFutureContinuationCallback) { + if let Err(existing) = #continuation_cell.set(callback) { + // Don't panic if this to be called multiple times with the same callback. + if existing != callback { + panic!("Attempt to set the RustFuture continuation callback twice"); + } + } + } + + #ffi_rust_future_scaffolding_fns + + // Code to re-export the UniFFI scaffolding functions. + // + // Rust won't always re-export the functions from dependencies + // ([rust-lang#50007](https://github.com/rust-lang/rust/issues/50007)) + // + // A workaround for this is to have the dependent crate reference a function from its dependency in + // an extern "C" function. This is clearly hacky and brittle, but at least we have some unittests + // that check if this works (fixtures/reexport-scaffolding-macro). + // + // The main way we use this macro is for that contain multiple UniFFI components (libxul, + // megazord). The combined library has a cargo dependency for each component and calls + // uniffi_reexport_scaffolding!() for each one. + + #[allow(missing_docs)] + #[doc(hidden)] + pub const fn uniffi_reexport_hack() {} + + #[doc(hidden)] + #[macro_export] + macro_rules! uniffi_reexport_scaffolding { + () => { + #[doc(hidden)] + #[no_mangle] + pub extern "C" fn #reexport_hack_ident() { + $crate::uniffi_reexport_hack() + } + }; + } + + // A trait that's in our crate for our external wrapped types to implement. + #[allow(unused)] + #[doc(hidden)] + pub trait UniffiCustomTypeConverter { + type Builtin; + fn into_custom(val: Self::Builtin) -> uniffi::Result<Self> where Self: Sized; + fn from_custom(obj: Self) -> Self::Builtin; + } + }) +} + +/// Generates the rust_future_* functions +/// +/// The foreign side uses a type-erased `RustFutureHandle` to interact with futures, which presents +/// a problem when creating scaffolding functions. What is the `ReturnType` parameter of `RustFutureFfi`? +/// +/// Handle this by using some brute-force monomorphization. For each possible ffi type, we +/// generate a set of scaffolding functions. The bindings code is responsible for calling the one +/// corresponds the scaffolding function that created the `RustFutureHandle`. +/// +/// This introduces safety issues, but we do get some type checking. If the bindings code calls +/// the wrong rust_future_complete function, they should get an unexpected return type, which +/// hopefully will result in a compile-time error. +fn rust_future_scaffolding_fns(module_path: &str) -> TokenStream { + let fn_info = [ + (quote! { u8 }, "u8"), + (quote! { i8 }, "i8"), + (quote! { u16 }, "u16"), + (quote! { i16 }, "i16"), + (quote! { u32 }, "u32"), + (quote! { i32 }, "i32"), + (quote! { u64 }, "u64"), + (quote! { i64 }, "i64"), + (quote! { f32 }, "f32"), + (quote! { f64 }, "f64"), + (quote! { *const ::std::ffi::c_void }, "pointer"), + (quote! { ::uniffi::RustBuffer }, "rust_buffer"), + (quote! { () }, "void"), + ]; + fn_info.iter() + .map(|(return_type, fn_suffix)| { + let ffi_rust_future_poll = format_ident!("ffi_{module_path}_rust_future_poll_{fn_suffix}"); + let ffi_rust_future_cancel = format_ident!("ffi_{module_path}_rust_future_cancel_{fn_suffix}"); + let ffi_rust_future_complete = format_ident!("ffi_{module_path}_rust_future_complete_{fn_suffix}"); + let ffi_rust_future_free = format_ident!("ffi_{module_path}_rust_future_free_{fn_suffix}"); + let continuation_cell = format_ident!("RUST_FUTURE_CONTINUATION_CALLBACK_CELL_{}", module_path.to_uppercase()); + + quote! { + #[allow(clippy::missing_safety_doc, missing_docs)] + #[doc(hidden)] + #[no_mangle] + pub unsafe extern "C" fn #ffi_rust_future_poll(handle: ::uniffi::RustFutureHandle, data: *const ()) { + let callback = #continuation_cell + .get() + .expect("RustFuture continuation callback not set. This is likely a uniffi bug."); + ::uniffi::ffi::rust_future_poll::<#return_type>(handle, *callback, data); + } + + #[allow(clippy::missing_safety_doc, missing_docs)] + #[doc(hidden)] + #[no_mangle] + pub unsafe extern "C" fn #ffi_rust_future_cancel(handle: ::uniffi::RustFutureHandle) { + ::uniffi::ffi::rust_future_cancel::<#return_type>(handle) + } + + #[allow(clippy::missing_safety_doc, missing_docs)] + #[doc(hidden)] + #[no_mangle] + pub unsafe extern "C" fn #ffi_rust_future_complete( + handle: ::uniffi::RustFutureHandle, + out_status: &mut ::uniffi::RustCallStatus + ) -> #return_type { + ::uniffi::ffi::rust_future_complete::<#return_type>(handle, out_status) + } + + #[allow(clippy::missing_safety_doc, missing_docs)] + #[doc(hidden)] + #[no_mangle] + pub unsafe extern "C" fn #ffi_rust_future_free(handle: ::uniffi::RustFutureHandle) { + ::uniffi::ffi::rust_future_free::<#return_type>(handle) + } + } + }) + .collect() +} diff --git a/third_party/rust/uniffi_macros/src/test.rs b/third_party/rust/uniffi_macros/src/test.rs new file mode 100644 index 0000000000..da7a343dbc --- /dev/null +++ b/third_party/rust/uniffi_macros/src/test.rs @@ -0,0 +1,90 @@ +/* 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 camino::{Utf8Path, Utf8PathBuf}; +use proc_macro::TokenStream; +use quote::{format_ident, quote}; +use std::env; +use syn::{parse_macro_input, punctuated::Punctuated, LitStr, Token}; + +pub(crate) fn build_foreign_language_testcases(tokens: TokenStream) -> TokenStream { + let input = parse_macro_input!(tokens as BuildForeignLanguageTestCaseInput); + // we resolve each path relative to the crate root directory. + let pkg_dir = env::var("CARGO_MANIFEST_DIR") + .expect("Missing $CARGO_MANIFEST_DIR, cannot build tests for generated bindings"); + + // For each test file found, generate a matching testcase. + let test_functions = input + .test_scripts + .iter() + .map(|file_path| { + let test_file_pathbuf: Utf8PathBuf = [&pkg_dir, file_path].iter().collect(); + let test_file_path = test_file_pathbuf.to_string(); + let test_file_name = test_file_pathbuf + .file_name() + .expect("Test file has no name, cannot build tests for generated bindings"); + let test_name = format_ident!( + "uniffi_foreign_language_testcase_{}", + test_file_name.replace(|c: char| !c.is_alphanumeric(), "_") + ); + let run_test = match test_file_pathbuf.extension() { + Some("kts") => quote! { + uniffi::kotlin_run_test + }, + Some("swift") => quote! { + uniffi::swift_run_test + }, + Some("py") => quote! { + uniffi::python_run_test + }, + Some("rb") => quote! { + uniffi::ruby_run_test + }, + _ => panic!("Unexpected extension for test script: {test_file_name}"), + }; + let maybe_ignore = if should_skip_path(&test_file_pathbuf) { + quote! { #[ignore] } + } else { + quote! {} + }; + quote! { + #maybe_ignore + #[test] + fn #test_name () -> uniffi::deps::anyhow::Result<()> { + #run_test( + std::env!("CARGO_TARGET_TMPDIR"), + std::env!("CARGO_PKG_NAME"), + #test_file_path) + } + } + }) + .collect::<Vec<proc_macro2::TokenStream>>(); + let test_module = quote! { + #(#test_functions)* + }; + TokenStream::from(test_module) +} + +// UNIFFI_TESTS_DISABLE_EXTENSIONS contains a comma-sep'd list of extensions (without leading `.`) +fn should_skip_path(path: &Utf8Path) -> bool { + let ext = path.extension().expect("File has no extension!"); + env::var("UNIFFI_TESTS_DISABLE_EXTENSIONS") + .map(|v| v.split(',').any(|look| look == ext)) + .unwrap_or(false) +} + +struct BuildForeignLanguageTestCaseInput { + test_scripts: Vec<String>, +} + +impl syn::parse::Parse for BuildForeignLanguageTestCaseInput { + fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> { + let test_scripts = Punctuated::<LitStr, Token![,]>::parse_terminated(input)? + .iter() + .map(|s| s.value()) + .collect(); + + Ok(Self { test_scripts }) + } +} diff --git a/third_party/rust/uniffi_macros/src/util.rs b/third_party/rust/uniffi_macros/src/util.rs new file mode 100644 index 0000000000..9f213ea1d7 --- /dev/null +++ b/third_party/rust/uniffi_macros/src/util.rs @@ -0,0 +1,278 @@ +/* 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::{format_ident, quote, ToTokens}; +use std::path::{Path as StdPath, PathBuf}; +use syn::{ + ext::IdentExt, + parse::{Parse, ParseStream}, + Attribute, Token, +}; + +pub fn manifest_path() -> Result<PathBuf, String> { + let manifest_dir = + std::env::var_os("CARGO_MANIFEST_DIR").ok_or("`CARGO_MANIFEST_DIR` is not set")?; + + Ok(StdPath::new(&manifest_dir).join("Cargo.toml")) +} + +#[cfg(not(feature = "nightly"))] +pub fn mod_path() -> syn::Result<String> { + // Without the nightly feature and TokenStream::expand_expr, just return the crate name + + use fs_err as fs; + use once_cell::sync::Lazy; + use serde::Deserialize; + + #[derive(Deserialize)] + struct CargoToml { + package: Package, + #[serde(default)] + lib: Lib, + } + + #[derive(Deserialize)] + struct Package { + name: String, + } + + #[derive(Default, Deserialize)] + struct Lib { + name: Option<String>, + } + + static LIB_CRATE_MOD_PATH: Lazy<Result<String, String>> = Lazy::new(|| { + let file = manifest_path()?; + let cargo_toml_bytes = fs::read(file).map_err(|e| e.to_string())?; + + let cargo_toml = toml::from_slice::<CargoToml>(&cargo_toml_bytes) + .map_err(|e| format!("Failed to parse `Cargo.toml`: {e}"))?; + + let lib_crate_name = cargo_toml + .lib + .name + .unwrap_or_else(|| cargo_toml.package.name.replace('-', "_")); + + Ok(lib_crate_name) + }); + + LIB_CRATE_MOD_PATH + .clone() + .map_err(|e| syn::Error::new(Span::call_site(), e)) +} + +#[cfg(feature = "nightly")] +pub fn mod_path() -> syn::Result<String> { + use proc_macro::TokenStream; + + let module_path_invoc = TokenStream::from(quote! { ::core::module_path!() }); + // We ask the compiler what `module_path!()` expands to here. + // This is a nightly feature, tracked at https://github.com/rust-lang/rust/issues/90765 + let expanded_module_path = TokenStream::expand_expr(&module_path_invoc) + .map_err(|e| syn::Error::new(Span::call_site(), e))?; + Ok(syn::parse::<syn::LitStr>(expanded_module_path)?.value()) +} + +pub fn try_read_field(f: &syn::Field) -> TokenStream { + let ident = &f.ident; + let ty = &f.ty; + + quote! { + #ident: <#ty as ::uniffi::Lift<crate::UniFfiTag>>::try_read(buf)?, + } +} + +pub fn ident_to_string(ident: &Ident) -> String { + ident.unraw().to_string() +} + +pub fn crate_name() -> String { + std::env::var("CARGO_CRATE_NAME").unwrap().replace('-', "_") +} + +pub fn create_metadata_items( + kind: &str, + name: &str, + metadata_expr: TokenStream, + checksum_fn_name: Option<String>, +) -> TokenStream { + let crate_name = crate_name(); + let crate_name_upper = crate_name.to_uppercase(); + let kind_upper = kind.to_uppercase(); + let name_upper = name.to_uppercase(); + let const_ident = + format_ident!("UNIFFI_META_CONST_{crate_name_upper}_{kind_upper}_{name_upper}"); + let static_ident = format_ident!("UNIFFI_META_{crate_name_upper}_{kind_upper}_{name_upper}"); + + let checksum_fn = checksum_fn_name.map(|name| { + let ident = Ident::new(&name, Span::call_site()); + quote! { + #[doc(hidden)] + #[no_mangle] + pub extern "C" fn #ident() -> u16 { + #const_ident.checksum() + } + } + }); + + quote! { + const #const_ident: ::uniffi::MetadataBuffer = #metadata_expr; + #[no_mangle] + #[doc(hidden)] + pub static #static_ident: [u8; #const_ident.size] = #const_ident.into_array(); + + #checksum_fn + } +} + +pub fn try_metadata_value_from_usize(value: usize, error_message: &str) -> syn::Result<u8> { + value + .try_into() + .map_err(|_| syn::Error::new(Span::call_site(), error_message)) +} + +pub fn chain<T>( + a: impl IntoIterator<Item = T>, + b: impl IntoIterator<Item = T>, +) -> impl Iterator<Item = T> { + a.into_iter().chain(b) +} + +pub trait UniffiAttributeArgs: Default { + fn parse_one(input: ParseStream<'_>) -> syn::Result<Self>; + fn merge(self, other: Self) -> syn::Result<Self>; +} + +pub fn parse_comma_separated<T: UniffiAttributeArgs>(input: ParseStream<'_>) -> syn::Result<T> { + let punctuated = input.parse_terminated(T::parse_one, Token![,])?; + punctuated.into_iter().try_fold(T::default(), T::merge) +} + +#[derive(Default)] +pub struct ArgumentNotAllowedHere; + +impl Parse for ArgumentNotAllowedHere { + fn parse(input: ParseStream<'_>) -> syn::Result<Self> { + parse_comma_separated(input) + } +} + +impl UniffiAttributeArgs for ArgumentNotAllowedHere { + fn parse_one(input: ParseStream<'_>) -> syn::Result<Self> { + Err(syn::Error::new( + input.span(), + "attribute arguments are not currently recognized in this position", + )) + } + + fn merge(self, _other: Self) -> syn::Result<Self> { + Ok(Self) + } +} + +pub trait AttributeSliceExt { + fn parse_uniffi_attr_args<T: UniffiAttributeArgs>(&self) -> syn::Result<T>; + fn uniffi_attr_args_not_allowed_here(&self) -> Option<syn::Error> { + self.parse_uniffi_attr_args::<ArgumentNotAllowedHere>() + .err() + } +} + +impl AttributeSliceExt for [Attribute] { + fn parse_uniffi_attr_args<T: UniffiAttributeArgs>(&self) -> syn::Result<T> { + self.iter() + .filter(|attr| attr.path().is_ident("uniffi")) + .try_fold(T::default(), |res, attr| { + let parsed = attr.parse_args_with(parse_comma_separated)?; + res.merge(parsed) + }) + } +} + +pub fn either_attribute_arg<T: ToTokens>(a: Option<T>, b: Option<T>) -> syn::Result<Option<T>> { + match (a, b) { + (None, None) => Ok(None), + (Some(val), None) | (None, Some(val)) => Ok(Some(val)), + (Some(a), Some(b)) => { + let mut error = syn::Error::new_spanned(a, "redundant attribute argument"); + error.combine(syn::Error::new_spanned(b, "note: first one here")); + Err(error) + } + } +} + +pub(crate) fn tagged_impl_header( + trait_name: &str, + ident: &impl ToTokens, + udl_mode: bool, +) -> TokenStream { + let trait_name = Ident::new(trait_name, Span::call_site()); + if udl_mode { + quote! { impl ::uniffi::#trait_name<crate::UniFfiTag> for #ident } + } else { + quote! { impl<T> ::uniffi::#trait_name<T> for #ident } + } +} + +pub(crate) fn derive_all_ffi_traits(ty: &Ident, udl_mode: bool) -> TokenStream { + if udl_mode { + quote! { ::uniffi::derive_ffi_traits!(local #ty); } + } else { + quote! { ::uniffi::derive_ffi_traits!(blanket #ty); } + } +} + +pub(crate) fn derive_ffi_traits(ty: &Ident, udl_mode: bool, trait_names: &[&str]) -> TokenStream { + let trait_idents = trait_names + .iter() + .map(|name| Ident::new(name, Span::call_site())); + if udl_mode { + quote! { + #( + ::uniffi::derive_ffi_traits!(impl #trait_idents<crate::UniFfiTag> for #ty); + )* + } + } else { + quote! { + #( + ::uniffi::derive_ffi_traits!(impl<UT> #trait_idents<UT> for #ty); + )* + } + } +} + +/// Custom keywords +pub mod kw { + syn::custom_keyword!(async_runtime); + syn::custom_keyword!(callback_interface); + syn::custom_keyword!(constructor); + syn::custom_keyword!(default); + syn::custom_keyword!(flat_error); + syn::custom_keyword!(None); + syn::custom_keyword!(with_try_read); + syn::custom_keyword!(Debug); + syn::custom_keyword!(Display); + syn::custom_keyword!(Eq); + syn::custom_keyword!(Hash); + // Not used anymore + syn::custom_keyword!(handle_unknown_callback_error); +} + +/// Specifies a type from a dependent crate +pub struct ExternalTypeItem { + pub crate_ident: Ident, + pub sep: Token![,], + pub type_ident: Ident, +} + +impl Parse for ExternalTypeItem { + fn parse(input: ParseStream<'_>) -> syn::Result<Self> { + Ok(Self { + crate_ident: input.parse()?, + sep: input.parse()?, + type_ident: input.parse()?, + }) + } +} |