summaryrefslogtreecommitdiffstats
path: root/third_party/rust/uniffi_macros
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /third_party/rust/uniffi_macros
parentInitial commit. (diff)
downloadfirefox-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')
-rw-r--r--third_party/rust/uniffi_macros/.cargo-checksum.json1
-rw-r--r--third_party/rust/uniffi_macros/Cargo.toml69
-rw-r--r--third_party/rust/uniffi_macros/src/custom.rs85
-rw-r--r--third_party/rust/uniffi_macros/src/enum_.rs195
-rw-r--r--third_party/rust/uniffi_macros/src/error.rs267
-rw-r--r--third_party/rust/uniffi_macros/src/export.rs291
-rw-r--r--third_party/rust/uniffi_macros/src/export/attributes.rs173
-rw-r--r--third_party/rust/uniffi_macros/src/export/callback_interface.rs175
-rw-r--r--third_party/rust/uniffi_macros/src/export/item.rs200
-rw-r--r--third_party/rust/uniffi_macros/src/export/scaffolding.rs274
-rw-r--r--third_party/rust/uniffi_macros/src/export/utrait.rs168
-rw-r--r--third_party/rust/uniffi_macros/src/fnsig.rs466
-rw-r--r--third_party/rust/uniffi_macros/src/lib.rs410
-rw-r--r--third_party/rust/uniffi_macros/src/object.rs147
-rw-r--r--third_party/rust/uniffi_macros/src/record.rs224
-rw-r--r--third_party/rust/uniffi_macros/src/setup_scaffolding.rs232
-rw-r--r--third_party/rust/uniffi_macros/src/test.rs90
-rw-r--r--third_party/rust/uniffi_macros/src/util.rs278
18 files changed, 3745 insertions, 0 deletions
diff --git a/third_party/rust/uniffi_macros/.cargo-checksum.json b/third_party/rust/uniffi_macros/.cargo-checksum.json
new file mode 100644
index 0000000000..96e44ac74e
--- /dev/null
+++ b/third_party/rust/uniffi_macros/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"376811e12479a0cced9375a12a2e3186da053b3c7520374bf6f2b54d23282082","src/custom.rs":"36cd6c2eeb8efdc34e59dff634a22e79471ab17f49ceb0f131da5f144313f7e4","src/enum_.rs":"2d3181ef22468deb4e8f48b7b1ed9421134604c2c226d5084c453ebb2cedbfd5","src/error.rs":"0b5beb8a2c8c93c30c56f2f80538bf1f1c8e6a99b2b1c934ad12a4feb75a2fc0","src/export.rs":"6d05417f0b10a9d6df9e96a6ed771c89a5e59e6e52d1ce812025bfe68e9f717e","src/export/attributes.rs":"53a27264882ab0a802a0ee109a2ea3f3456d4c83c85eaa5c0f5912d4486ab843","src/export/callback_interface.rs":"ad2782b7ca930dc067c391394480362be1fbf331d8786be089c0a87415c85a88","src/export/item.rs":"a7b74e6400ec6c5e8fb09d8842ce718b9555d75de13fdf5fecbab2fceeec7cbf","src/export/scaffolding.rs":"8ab2b9b0c5ad5b5477963843b7a58d496344da7de1a2a4b07f30f22275c8f3c9","src/export/utrait.rs":"ce4a3d629aaf0b44b8c5ce6794c5d5b0d7f86f46f0dd6b6ecb134514be330f0d","src/fnsig.rs":"886ceec806b429c7d86fe00d0d84f7b04e21142605f7a61d182f9f616210cd2b","src/lib.rs":"501c736647eff2705c5565f80e554d2e440cceceed95044c9fe147fc309afb48","src/object.rs":"0a14d6b8ccb4faef93a1f61a97ac7d47a80b6581383f4a6e0a4789f53e66e630","src/record.rs":"fbff287bb2d0b7a9eb35ef3161fbd1abbc21f3aa08e88bd4242dd12acbfa3ee9","src/setup_scaffolding.rs":"60b48a56fae16cf01824586b90e6440da3362135d98fc07aaed624c57f806163","src/test.rs":"1673f282bb35d6b0740ad0e5f11826c2852d7a0db29604c2258f457415b537e8","src/util.rs":"217ecef0e4dabd158a7597aa3d00d94477993a90b388afbbc0fb39e14f6b013e"},"package":"11cf7a58f101fcedafa5b77ea037999b88748607f0ef3a33eaa0efc5392e92e4"} \ No newline at end of file
diff --git a/third_party/rust/uniffi_macros/Cargo.toml b/third_party/rust/uniffi_macros/Cargo.toml
new file mode 100644
index 0000000000..9d3908ae8d
--- /dev/null
+++ b/third_party/rust/uniffi_macros/Cargo.toml
@@ -0,0 +1,69 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+name = "uniffi_macros"
+version = "0.25.3"
+authors = ["Firefox Sync Team <sync-team@mozilla.com>"]
+description = "a multi-language bindings generator for rust (convenience macros)"
+homepage = "https://mozilla.github.io/uniffi-rs"
+documentation = "https://mozilla.github.io/uniffi-rs"
+keywords = [
+ "ffi",
+ "bindgen",
+]
+license = "MPL-2.0"
+repository = "https://github.com/mozilla/uniffi-rs"
+
+[lib]
+proc-macro = true
+
+[dependencies.bincode]
+version = "1.3"
+
+[dependencies.camino]
+version = "1.0.8"
+
+[dependencies.fs-err]
+version = "2.7.0"
+
+[dependencies.once_cell]
+version = "1.10.0"
+
+[dependencies.proc-macro2]
+version = "1.0"
+
+[dependencies.quote]
+version = "1.0"
+
+[dependencies.serde]
+version = "1.0.136"
+
+[dependencies.syn]
+version = "2.0"
+features = [
+ "full",
+ "visit-mut",
+]
+
+[dependencies.toml]
+version = "0.5.9"
+
+[dependencies.uniffi_build]
+version = "=0.25.3"
+
+[dependencies.uniffi_meta]
+version = "=0.25.3"
+
+[features]
+default = []
+nightly = []
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", &quote! { dyn #trait_ident }, udl_mode);
+ let lift_ref_impl_spec = tagged_impl_header("LiftRef", &quote! { dyn #trait_ident }, udl_mode);
+ let name = ident_to_string(trait_ident);
+ let mod_path = match mod_path() {
+ Ok(p) => p,
+ Err(e) => return e.into_compile_error(),
+ };
+
+ quote! {
+ // All traits must be `Sync + Send`. The generated scaffolding will fail to compile
+ // if they are not, but unfortunately it fails with an unactionably obscure error message.
+ // By asserting the requirement explicitly, we help Rust produce a more scrutable error message
+ // and thus help the user debug why the requirement isn't being met.
+ uniffi::deps::static_assertions::assert_impl_all!(dyn #trait_ident: Sync, Send);
+
+ unsafe #impl_spec {
+ type FfiType = *const ::std::os::raw::c_void;
+
+ fn lower(obj: ::std::sync::Arc<Self>) -> Self::FfiType {
+ ::std::boxed::Box::into_raw(::std::boxed::Box::new(obj)) as *const ::std::os::raw::c_void
+ }
+
+ fn try_lift(v: Self::FfiType) -> ::uniffi::Result<::std::sync::Arc<Self>> {
+ let foreign_arc = ::std::boxed::Box::leak(unsafe { Box::from_raw(v as *mut ::std::sync::Arc<Self>) });
+ // Take a clone for our own use.
+ Ok(::std::sync::Arc::clone(foreign_arc))
+ }
+
+ fn write(obj: ::std::sync::Arc<Self>, buf: &mut Vec<u8>) {
+ ::uniffi::deps::static_assertions::const_assert!(::std::mem::size_of::<*const ::std::ffi::c_void>() <= 8);
+ ::uniffi::deps::bytes::BufMut::put_u64(
+ buf,
+ <Self as ::uniffi::FfiConverterArc<crate::UniFfiTag>>::lower(obj) as u64,
+ );
+ }
+
+ fn try_read(buf: &mut &[u8]) -> ::uniffi::Result<::std::sync::Arc<Self>> {
+ ::uniffi::deps::static_assertions::const_assert!(::std::mem::size_of::<*const ::std::ffi::c_void>() <= 8);
+ ::uniffi::check_remaining(buf, 8)?;
+ <Self as ::uniffi::FfiConverterArc<crate::UniFfiTag>>::try_lift(
+ ::uniffi::deps::bytes::Buf::get_u64(buf) as Self::FfiType)
+ }
+
+ const TYPE_ID_META: ::uniffi::MetadataBuffer = ::uniffi::MetadataBuffer::from_code(::uniffi::metadata::codes::TYPE_INTERFACE)
+ .concat_str(#mod_path)
+ .concat_str(#name)
+ .concat_bool(true);
+ }
+
+ unsafe #lift_ref_impl_spec {
+ type LiftType = ::std::sync::Arc<dyn #trait_ident>;
+ }
+ }
+}
+
+/// Rewrite Self type alias usage in an impl block to the type itself.
+///
+/// For example,
+///
+/// ```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()?,
+ })
+ }
+}