summaryrefslogtreecommitdiffstats
path: root/third_party/rust/uniffi_macros
diff options
context:
space:
mode:
Diffstat (limited to '')
-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/enum_.rs160
-rw-r--r--third_party/rust/uniffi_macros/src/error.rs170
-rw-r--r--third_party/rust/uniffi_macros/src/export.rs210
-rw-r--r--third_party/rust/uniffi_macros/src/export/metadata.rs26
-rw-r--r--third_party/rust/uniffi_macros/src/export/metadata/convert.rs222
-rw-r--r--third_party/rust/uniffi_macros/src/export/metadata/function.rs32
-rw-r--r--third_party/rust/uniffi_macros/src/export/metadata/impl_.rs93
-rw-r--r--third_party/rust/uniffi_macros/src/export/scaffolding.rs199
-rw-r--r--third_party/rust/uniffi_macros/src/lib.rs179
-rw-r--r--third_party/rust/uniffi_macros/src/object.rs35
-rw-r--r--third_party/rust/uniffi_macros/src/record.rs108
-rw-r--r--third_party/rust/uniffi_macros/src/test.rs90
-rw-r--r--third_party/rust/uniffi_macros/src/util.rs223
15 files changed, 1817 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..8a84c40226
--- /dev/null
+++ b/third_party/rust/uniffi_macros/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"495a97879e9a66a87a410c742fa2ac36e2b85e5f16bee1cdde878db02090d7ee","src/enum_.rs":"0b2aa1bce2b8f1c476f2874e6bb9ae49b9610abd6963f3336db63794296494a8","src/error.rs":"b5fccfa3d4aa2d7454c5d27504a2d8d2e02e761843ea2da94b76ef8ffb0d0bf5","src/export.rs":"f95d31c9445142efe59617392ce9135acbefc4ac852654a482a2360c5c78ee75","src/export/metadata.rs":"af89a9942c7c0c4043a3cd57d1e6bd71cde19005e1f9f246efac761f47eff6be","src/export/metadata/convert.rs":"82c9602ddee0af2c93733bbe6cb77c50dccb0134c927b582f04f5bb9f8b5203c","src/export/metadata/function.rs":"e775f838025bee429b294f786579ac6598966b1b4f1f415dfb9012c70a0fea6c","src/export/metadata/impl_.rs":"1f55990f15293f2a3e30ebb0c132f68caa25a56113e0451c5307c9ed32cb8fb8","src/export/scaffolding.rs":"1cd5b8cf62195e35cd851ee9dd5aa787eaa8a1c293c1b3e899a1de8feb183b2d","src/lib.rs":"4357a13d0e9deac75239007431211188d7ef6270256f002d17d9e937ee3aba5a","src/object.rs":"955b596f344304013692042bdc1760bbb1192ec33950b0dd2932cb8de94ec297","src/record.rs":"f0c2adb794d5996ff8545c10469e63f24164509f0566e6c401a26c363c278003","src/test.rs":"069d91947685de083c9faa9042c328c235445b84bcf69f03117016d9c8464736","src/util.rs":"4834ece6808595ef5fbf735f9610dd1ef1631ea2e5919e6a1da9f290beb7091a"},"package":"fa03394de21e759e0022f1ea8d992d2e39290d735b9ed52b1f74b20a684f794e"} \ 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..dc5b9a9184
--- /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.23.0"
+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 = "1.0"
+features = [
+ "full",
+ "visit-mut",
+]
+
+[dependencies.toml]
+version = "0.5.9"
+
+[dependencies.uniffi_build]
+version = "=0.23.0"
+
+[dependencies.uniffi_meta]
+version = "=0.23.0"
+
+[features]
+default = []
+nightly = []
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..c4e49beb8b
--- /dev/null
+++ b/third_party/rust/uniffi_macros/src/enum_.rs
@@ -0,0 +1,160 @@
+use proc_macro2::{Ident, Span, TokenStream};
+use quote::quote;
+use syn::{punctuated::Punctuated, Data, DeriveInput, Field, Index, Token, Variant};
+use uniffi_meta::{EnumMetadata, FieldMetadata, VariantMetadata};
+
+use crate::{
+ export::metadata::convert::convert_type,
+ util::{assert_type_eq, create_metadata_static_var, try_read_field},
+};
+
+pub fn expand_enum(input: DeriveInput, module_path: Vec<String>) -> TokenStream {
+ let variants = match input.data {
+ Data::Enum(e) => Some(e.variants),
+ _ => None,
+ };
+
+ let ident = &input.ident;
+
+ let ffi_converter_impl = enum_ffi_converter_impl(variants.as_ref(), ident);
+
+ let meta_static_var = if let Some(variants) = variants {
+ match enum_metadata(ident, variants, module_path) {
+ Ok(metadata) => create_metadata_static_var(ident, metadata.into()),
+ Err(e) => e.into_compile_error(),
+ }
+ } else {
+ syn::Error::new(Span::call_site(), "This derive must only be used on enums")
+ .into_compile_error()
+ };
+
+ let type_assertion = assert_type_eq(ident, quote! { crate::uniffi_types::#ident });
+
+ quote! {
+ #ffi_converter_impl
+ #meta_static_var
+ #type_assertion
+ }
+}
+
+pub(crate) fn enum_ffi_converter_impl(
+ variants: Option<&Punctuated<Variant, Token![,]>>,
+ ident: &Ident,
+) -> TokenStream {
+ let (write_impl, try_read_impl) = match variants {
+ Some(variants) => {
+ let write_match_arms = 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 = 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),
+ })
+ };
+
+ (write_impl, try_read_impl)
+ }
+ None => {
+ let unimplemented = quote! { ::std::unimplemented!() };
+ (unimplemented.clone(), unimplemented)
+ }
+ };
+
+ quote! {
+ #[automatically_derived]
+ impl ::uniffi::RustBufferFfiConverter for #ident {
+ type RustType = Self;
+
+ 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
+ }
+ }
+ }
+}
+
+fn enum_metadata(
+ ident: &Ident,
+ variants: Punctuated<Variant, Token![,]>,
+ module_path: Vec<String>,
+) -> syn::Result<EnumMetadata> {
+ let name = ident.to_string();
+ let variants = variants
+ .iter()
+ .map(variant_metadata)
+ .collect::<syn::Result<_>>()?;
+
+ Ok(EnumMetadata {
+ module_path,
+ name,
+ variants,
+ })
+}
+
+pub(crate) fn variant_metadata(v: &Variant) -> syn::Result<VariantMetadata> {
+ let name = v.ident.to_string();
+ let fields = v
+ .fields
+ .iter()
+ .map(|f| field_metadata(f, v))
+ .collect::<syn::Result<_>>()?;
+
+ Ok(VariantMetadata { name, fields })
+}
+
+fn field_metadata(f: &Field, v: &Variant) -> syn::Result<FieldMetadata> {
+ let name = 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)",
+ )
+ })?
+ .to_string();
+
+ Ok(FieldMetadata {
+ name,
+ ty: convert_type(&f.ty)?,
+ })
+}
+
+fn write_field(f: &Field) -> TokenStream {
+ let ident = &f.ident;
+ let ty = &f.ty;
+
+ quote! {
+ <#ty as ::uniffi::FfiConverter>::write(#ident, buf);
+ }
+}
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..76c34cf78d
--- /dev/null
+++ b/third_party/rust/uniffi_macros/src/error.rs
@@ -0,0 +1,170 @@
+use proc_macro2::{Ident, Span, TokenStream};
+use quote::quote;
+use syn::{
+ parse::{Parse, ParseStream},
+ punctuated::Punctuated,
+ Data, DeriveInput, Index, Token, Variant,
+};
+use uniffi_meta::{ErrorMetadata, VariantMetadata};
+
+use crate::{
+ enum_::{enum_ffi_converter_impl, variant_metadata},
+ util::{
+ assert_type_eq, chain, create_metadata_static_var, either_attribute_arg, AttributeSliceExt,
+ UniffiAttribute,
+ },
+};
+
+pub fn expand_error(input: DeriveInput, module_path: Vec<String>) -> TokenStream {
+ let variants = match input.data {
+ Data::Enum(e) => Ok(e.variants),
+ _ => Err(syn::Error::new(
+ Span::call_site(),
+ "This derive currently only supports enums",
+ )),
+ };
+
+ let ident = &input.ident;
+ let attr = input.attrs.parse_uniffi_attributes::<ErrorAttr>();
+ let ffi_converter_impl = match &attr {
+ Ok(a) if a.flat.is_some() => flat_error_ffi_converter_impl(variants.as_ref().ok(), ident),
+ _ => enum_ffi_converter_impl(variants.as_ref().ok(), ident),
+ };
+
+ let meta_static_var = match (&variants, &attr) {
+ (Ok(vs), Ok(a)) => Some(match error_metadata(ident, vs, module_path, a) {
+ Ok(metadata) => create_metadata_static_var(ident, metadata.into()),
+ Err(e) => e.into_compile_error(),
+ }),
+ _ => None,
+ };
+
+ let type_assertion = assert_type_eq(ident, quote! { crate::uniffi_types::#ident });
+ let variant_errors: TokenStream = match variants {
+ Ok(vs) => vs
+ .iter()
+ .flat_map(|variant| {
+ chain(
+ variant.attrs.attributes_not_allowed_here(),
+ variant
+ .fields
+ .iter()
+ .flat_map(|field| field.attrs.attributes_not_allowed_here()),
+ )
+ })
+ .map(syn::Error::into_compile_error)
+ .collect(),
+ Err(e) => e.into_compile_error(),
+ };
+ let attr_error = attr.err().map(syn::Error::into_compile_error);
+
+ quote! {
+ #ffi_converter_impl
+
+ #[automatically_derived]
+ impl ::uniffi::FfiError for #ident {}
+
+ #meta_static_var
+ #type_assertion
+ #variant_errors
+ #attr_error
+ }
+}
+
+pub(crate) fn flat_error_ffi_converter_impl(
+ variants: Option<&Punctuated<Variant, Token![,]>>,
+ ident: &Ident,
+) -> TokenStream {
+ let write_impl = match variants {
+ Some(variants) => {
+ let write_match_arms = 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::FfiConverter>::write(error_msg, buf);
+ }
+ }
+ });
+ let write_impl = quote! {
+ let error_msg = ::std::string::ToString::to_string(&obj);
+ match obj { #(#write_match_arms)* }
+ };
+
+ write_impl
+ }
+ None => quote! { ::std::unimplemented!() },
+ };
+
+ quote! {
+ #[automatically_derived]
+ impl ::uniffi::RustBufferFfiConverter for #ident {
+ type RustType = Self;
+
+ 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> {
+ ::std::panic!("try_read not supported for flat errors");
+ }
+ }
+ }
+}
+
+fn error_metadata(
+ ident: &Ident,
+ variants: &Punctuated<Variant, Token![,]>,
+ module_path: Vec<String>,
+ attr: &ErrorAttr,
+) -> syn::Result<ErrorMetadata> {
+ let name = ident.to_string();
+ let flat = attr.flat.is_some();
+ let variants = if flat {
+ variants
+ .iter()
+ .map(|v| VariantMetadata {
+ name: v.ident.to_string(),
+ fields: vec![],
+ })
+ .collect()
+ } else {
+ variants
+ .iter()
+ .map(variant_metadata)
+ .collect::<syn::Result<_>>()?
+ };
+
+ Ok(ErrorMetadata {
+ module_path,
+ name,
+ variants,
+ flat,
+ })
+}
+
+mod kw {
+ syn::custom_keyword!(flat_error);
+}
+
+#[derive(Default)]
+struct ErrorAttr {
+ flat: Option<kw::flat_error>,
+}
+
+impl Parse for ErrorAttr {
+ fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+ let flat = input.parse()?;
+ Ok(ErrorAttr { flat })
+ }
+}
+
+impl UniffiAttribute for ErrorAttr {
+ fn merge(self, other: Self) -> syn::Result<Self> {
+ Ok(Self {
+ flat: either_attribute_arg(self.flat, other.flat)?,
+ })
+ }
+}
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..22d469eec6
--- /dev/null
+++ b/third_party/rust/uniffi_macros/src/export.rs
@@ -0,0 +1,210 @@
+/* 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 std::collections::BTreeMap;
+
+use proc_macro2::{Ident, TokenStream};
+use quote::{format_ident, quote, quote_spanned};
+use uniffi_meta::{checksum, FnMetadata, MethodMetadata, Type};
+
+pub(crate) mod metadata;
+mod scaffolding;
+
+pub use self::metadata::gen_metadata;
+use self::{
+ metadata::convert::{convert_type, try_split_result},
+ scaffolding::{gen_fn_scaffolding, gen_method_scaffolding},
+};
+use crate::util::{assert_type_eq, create_metadata_static_var};
+
+// TODO(jplatte): Ensure no generics, no async, …
+// TODO(jplatte): Aggregate errors instead of short-circuiting, wherever possible
+
+pub enum ExportItem {
+ Function {
+ sig: Signature,
+ metadata: FnMetadata,
+ },
+ Impl {
+ self_ident: Ident,
+ methods: Vec<syn::Result<Method>>,
+ },
+}
+
+pub struct Method {
+ sig: Signature,
+ metadata: MethodMetadata,
+}
+
+pub struct Signature {
+ ident: Ident,
+ inputs: Vec<syn::FnArg>,
+ output: Option<FunctionReturn>,
+}
+
+impl Signature {
+ fn new(item: syn::Signature) -> syn::Result<Self> {
+ let output = match item.output {
+ syn::ReturnType::Default => None,
+ syn::ReturnType::Type(_, ty) => Some(FunctionReturn::new(ty)?),
+ };
+
+ Ok(Self {
+ ident: item.ident,
+ inputs: item.inputs.into_iter().collect(),
+ output,
+ })
+ }
+}
+
+pub struct FunctionReturn {
+ ty: Box<syn::Type>,
+ throws: Option<Ident>,
+}
+
+impl FunctionReturn {
+ fn new(ty: Box<syn::Type>) -> syn::Result<Self> {
+ Ok(match try_split_result(&ty)? {
+ Some((ok_type, throws)) => FunctionReturn {
+ ty: Box::new(ok_type.to_owned()),
+ throws: Some(throws),
+ },
+ None => FunctionReturn { ty, throws: None },
+ })
+ }
+}
+
+pub fn expand_export(metadata: ExportItem, mod_path: &[String]) -> TokenStream {
+ match metadata {
+ ExportItem::Function { sig, metadata } => {
+ let checksum = checksum(&metadata);
+ let scaffolding = gen_fn_scaffolding(&sig, mod_path, checksum);
+ let type_assertions = fn_type_assertions(&sig);
+ let meta_static_var = create_metadata_static_var(&sig.ident, metadata.into());
+
+ quote! {
+ #scaffolding
+ #type_assertions
+ #meta_static_var
+ }
+ }
+ ExportItem::Impl {
+ methods,
+ self_ident,
+ } => {
+ let method_tokens: TokenStream = methods
+ .into_iter()
+ .map(|res| {
+ res.map_or_else(
+ syn::Error::into_compile_error,
+ |Method { sig, metadata }| {
+ let checksum = checksum(&metadata);
+ let scaffolding =
+ gen_method_scaffolding(&sig, mod_path, checksum, &self_ident);
+ let type_assertions = fn_type_assertions(&sig);
+ let meta_static_var = create_metadata_static_var(
+ &format_ident!("{}_{}", metadata.self_name, sig.ident),
+ metadata.into(),
+ );
+
+ quote! {
+ #scaffolding
+ #type_assertions
+ #meta_static_var
+ }
+ },
+ )
+ })
+ .collect();
+
+ quote_spanned! {self_ident.span()=>
+ ::uniffi::deps::static_assertions::assert_type_eq_all!(
+ #self_ident,
+ crate::uniffi_types::#self_ident
+ );
+
+ #method_tokens
+ }
+ }
+ }
+}
+
+fn fn_type_assertions(sig: &Signature) -> TokenStream {
+ // Convert uniffi_meta::Type back to a Rust type
+ fn convert_type_back(ty: &Type) -> TokenStream {
+ match &ty {
+ Type::U8 => quote! { ::std::primitive::u8 },
+ Type::U16 => quote! { ::std::primitive::u16 },
+ Type::U32 => quote! { ::std::primitive::u32 },
+ Type::U64 => quote! { ::std::primitive::u64 },
+ Type::I8 => quote! { ::std::primitive::i8 },
+ Type::I16 => quote! { ::std::primitive::i16 },
+ Type::I32 => quote! { ::std::primitive::i32 },
+ Type::I64 => quote! { ::std::primitive::i64 },
+ Type::F32 => quote! { ::std::primitive::f32 },
+ Type::F64 => quote! { ::std::primitive::f64 },
+ Type::Bool => quote! { ::std::primitive::bool },
+ Type::String => quote! { ::std::string::String },
+ Type::Option { inner_type } => {
+ let inner = convert_type_back(inner_type);
+ quote! { ::std::option::Option<#inner> }
+ }
+ Type::Vec { inner_type } => {
+ let inner = convert_type_back(inner_type);
+ quote! { ::std::vec::Vec<#inner> }
+ }
+ Type::HashMap {
+ key_type,
+ value_type,
+ } => {
+ let key = convert_type_back(key_type);
+ let value = convert_type_back(value_type);
+ quote! { ::std::collections::HashMap<#key, #value> }
+ }
+ Type::ArcObject { object_name } => {
+ let object_ident = format_ident!("{object_name}");
+ quote! { ::std::sync::Arc<crate::uniffi_types::#object_ident> }
+ }
+ Type::Unresolved { name } => {
+ let ident = format_ident!("{name}");
+ quote! { crate::uniffi_types::#ident }
+ }
+ }
+ }
+
+ let input_types = sig.inputs.iter().filter_map(|input| match input {
+ syn::FnArg::Receiver(_) => None,
+ syn::FnArg::Typed(pat_ty) => match &*pat_ty.pat {
+ // Self type is asserted separately for impl blocks
+ syn::Pat::Ident(i) if i.ident == "self" => None,
+ _ => Some(&pat_ty.ty),
+ },
+ });
+ let output_type = sig.output.as_ref().map(|s| &s.ty);
+
+ let type_assertions: BTreeMap<_, _> = input_types
+ .chain(output_type)
+ .filter_map(|ty| {
+ convert_type(ty).ok().map(|meta_ty| {
+ let expected_ty = convert_type_back(&meta_ty);
+ let assert = assert_type_eq(ty, expected_ty);
+ (meta_ty, assert)
+ })
+ })
+ .collect();
+ let input_output_type_assertions: TokenStream = type_assertions.into_values().collect();
+
+ let throws_type_assertion = sig.output.as_ref().and_then(|s| {
+ let ident = s.throws.as_ref()?;
+ Some(assert_type_eq(
+ ident,
+ quote! { crate::uniffi_types::#ident },
+ ))
+ });
+
+ quote! {
+ #input_output_type_assertions
+ #throws_type_assertion
+ }
+}
diff --git a/third_party/rust/uniffi_macros/src/export/metadata.rs b/third_party/rust/uniffi_macros/src/export/metadata.rs
new file mode 100644
index 0000000000..2d0b284333
--- /dev/null
+++ b/third_party/rust/uniffi_macros/src/export/metadata.rs
@@ -0,0 +1,26 @@
+/* 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::Span;
+
+use super::ExportItem;
+
+pub(crate) mod convert;
+mod function;
+mod impl_;
+
+use self::{function::gen_fn_metadata, impl_::gen_impl_metadata};
+
+pub fn gen_metadata(item: syn::Item, mod_path: &[String]) -> syn::Result<ExportItem> {
+ match item {
+ syn::Item::Fn(item) => gen_fn_metadata(item.sig, mod_path),
+ syn::Item::Impl(item) => gen_impl_metadata(item, mod_path),
+ // FIXME: Support const / static?
+ _ => Err(syn::Error::new(
+ Span::call_site(),
+ "unsupported item: only functions and impl \
+ blocks may be annotated with this attribute",
+ )),
+ }
+}
diff --git a/third_party/rust/uniffi_macros/src/export/metadata/convert.rs b/third_party/rust/uniffi_macros/src/export/metadata/convert.rs
new file mode 100644
index 0000000000..c19ae579c2
--- /dev/null
+++ b/third_party/rust/uniffi_macros/src/export/metadata/convert.rs
@@ -0,0 +1,222 @@
+/* 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;
+use quote::ToTokens;
+use uniffi_meta::{FnParamMetadata, Type};
+
+pub(super) fn fn_param_metadata(params: &[syn::FnArg]) -> syn::Result<Vec<FnParamMetadata>> {
+ params
+ .iter()
+ .filter_map(|a| {
+ let _is_method = false;
+ let (name, ty) = match a {
+ // methods currently have an implicit self parameter in uniffi_meta
+ syn::FnArg::Receiver(_) => return None,
+ syn::FnArg::Typed(pat_ty) => {
+ let name = match &*pat_ty.pat {
+ syn::Pat::Ident(pat_id) => pat_id.ident.to_string(),
+ _ => unimplemented!(),
+ };
+
+ // methods currently have an implicit self parameter in uniffi_meta
+ if name == "self" {
+ return None;
+ }
+
+ (name, &pat_ty.ty)
+ }
+ };
+
+ Some(convert_type(ty).map(|ty| FnParamMetadata { name, ty }))
+ })
+ .collect()
+}
+
+pub(crate) fn convert_return_type(ty: &syn::Type) -> syn::Result<Option<Type>> {
+ match ty {
+ syn::Type::Tuple(tup) if tup.elems.is_empty() => Ok(None),
+ _ => convert_type(ty).map(Some),
+ }
+}
+
+pub(crate) fn convert_type(ty: &syn::Type) -> syn::Result<Type> {
+ let type_path = type_as_type_path(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",
+ ));
+ }
+
+ if type_path.path.segments.len() > 1 {
+ return Err(syn::Error::new_spanned(
+ type_path,
+ "qualified paths in types are not currently supported by uniffi::export",
+ ));
+ }
+
+ match &type_path.path.segments.first() {
+ Some(seg) => match &seg.arguments {
+ syn::PathArguments::None => Ok(convert_bare_type_name(&seg.ident)),
+ syn::PathArguments::AngleBracketed(a) => convert_generic_type(&seg.ident, a),
+ syn::PathArguments::Parenthesized(_) => Err(type_not_supported(type_path)),
+ },
+ None => Err(syn::Error::new_spanned(
+ type_path,
+ "unreachable: TypePath must have non-empty segments",
+ )),
+ }
+}
+
+fn convert_generic_type(
+ ident: &Ident,
+ a: &syn::AngleBracketedGenericArguments,
+) -> syn::Result<Type> {
+ let mut it = a.args.iter();
+ match it.next() {
+ // `u8<>` is a valid way to write `u8` in the type namespace, so why not?
+ None => Ok(convert_bare_type_name(ident)),
+ Some(arg1) => match it.next() {
+ None => convert_generic_type1(ident, arg1),
+ Some(arg2) => match it.next() {
+ None => convert_generic_type2(ident, arg1, arg2),
+ Some(_) => Err(syn::Error::new_spanned(
+ ident,
+ "types with more than two generics are not currently
+ supported by uniffi::export",
+ )),
+ },
+ },
+ }
+}
+
+fn convert_bare_type_name(ident: &Ident) -> Type {
+ let name = ident.to_string();
+ match name.as_str() {
+ "u8" => Type::U8,
+ "u16" => Type::U16,
+ "u32" => Type::U32,
+ "u64" => Type::U64,
+ "i8" => Type::I8,
+ "i16" => Type::I16,
+ "i32" => Type::I32,
+ "i64" => Type::I64,
+ "f32" => Type::F32,
+ "f64" => Type::F64,
+ "bool" => Type::Bool,
+ "String" => Type::String,
+ _ => Type::Unresolved { name },
+ }
+}
+
+fn convert_generic_type1(ident: &Ident, arg: &syn::GenericArgument) -> syn::Result<Type> {
+ let arg = arg_as_type(arg)?;
+ match ident.to_string().as_str() {
+ "Arc" => Ok(Type::ArcObject {
+ object_name: type_as_type_name(arg)?.to_string(),
+ }),
+ "Option" => Ok(Type::Option {
+ inner_type: convert_type(arg)?.into(),
+ }),
+ "Vec" => Ok(Type::Vec {
+ inner_type: convert_type(arg)?.into(),
+ }),
+ _ => Err(type_not_supported(ident)),
+ }
+}
+
+fn convert_generic_type2(
+ ident: &Ident,
+ arg1: &syn::GenericArgument,
+ arg2: &syn::GenericArgument,
+) -> syn::Result<Type> {
+ let arg1 = arg_as_type(arg1)?;
+ let arg2 = arg_as_type(arg2)?;
+
+ match ident.to_string().as_str() {
+ "HashMap" => Ok(Type::HashMap {
+ key_type: convert_type(arg1)?.into(),
+ value_type: convert_type(arg2)?.into(),
+ }),
+ _ => Err(type_not_supported(ident)),
+ }
+}
+
+fn type_as_type_name(arg: &syn::Type) -> syn::Result<&Ident> {
+ type_as_type_path(arg)?
+ .path
+ .get_ident()
+ .ok_or_else(|| type_not_supported(arg))
+}
+
+pub(super) 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 arg_as_type(arg: &syn::GenericArgument) -> syn::Result<&syn::Type> {
+ match arg {
+ syn::GenericArgument::Type(t) => Ok(t),
+ _ => Err(syn::Error::new_spanned(
+ arg,
+ "non-type generic parameters are not currently supported by uniffi::export",
+ )),
+ }
+}
+
+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",
+ )
+}
+
+pub(crate) fn try_split_result(ty: &syn::Type) -> syn::Result<Option<(&syn::Type, Ident)>> {
+ let type_path = type_as_type_path(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",
+ ));
+ }
+
+ if type_path.path.segments.len() > 1 {
+ return Err(syn::Error::new_spanned(
+ type_path,
+ "qualified paths in types are not currently supported by uniffi::export",
+ ));
+ }
+
+ let (ident, a) = match &type_path.path.segments.first() {
+ Some(seg) => match &seg.arguments {
+ syn::PathArguments::AngleBracketed(a) => (&seg.ident, a),
+ syn::PathArguments::None | syn::PathArguments::Parenthesized(_) => return Ok(None),
+ },
+ None => return Ok(None),
+ };
+
+ let mut it = a.args.iter();
+ if let Some(arg1) = it.next() {
+ if let Some(arg2) = it.next() {
+ if it.next().is_none() {
+ let arg1 = arg_as_type(arg1)?;
+ let arg2 = arg_as_type(arg2)?;
+
+ if let "Result" = ident.to_string().as_str() {
+ let throws = type_as_type_name(arg2)?.to_owned();
+ return Ok(Some((arg1, throws)));
+ }
+ }
+ }
+ }
+
+ Ok(None)
+}
diff --git a/third_party/rust/uniffi_macros/src/export/metadata/function.rs b/third_party/rust/uniffi_macros/src/export/metadata/function.rs
new file mode 100644
index 0000000000..a2cf6b7aa1
--- /dev/null
+++ b/third_party/rust/uniffi_macros/src/export/metadata/function.rs
@@ -0,0 +1,32 @@
+/* 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 uniffi_meta::FnMetadata;
+
+use super::convert::{convert_return_type, fn_param_metadata};
+use crate::export::{ExportItem, Signature};
+
+pub(super) fn gen_fn_metadata(sig: syn::Signature, mod_path: &[String]) -> syn::Result<ExportItem> {
+ let sig = Signature::new(sig)?;
+ let metadata = fn_metadata(&sig, mod_path)?;
+ Ok(ExportItem::Function { sig, metadata })
+}
+
+fn fn_metadata(sig: &Signature, mod_path: &[String]) -> syn::Result<FnMetadata> {
+ let (return_type, throws) = match &sig.output {
+ Some(ret) => (
+ convert_return_type(&ret.ty)?,
+ ret.throws.as_ref().map(ToString::to_string),
+ ),
+ None => (None, None),
+ };
+
+ Ok(FnMetadata {
+ module_path: mod_path.to_owned(),
+ name: sig.ident.to_string(),
+ inputs: fn_param_metadata(&sig.inputs)?,
+ return_type,
+ throws,
+ })
+}
diff --git a/third_party/rust/uniffi_macros/src/export/metadata/impl_.rs b/third_party/rust/uniffi_macros/src/export/metadata/impl_.rs
new file mode 100644
index 0000000000..302f0bfa13
--- /dev/null
+++ b/third_party/rust/uniffi_macros/src/export/metadata/impl_.rs
@@ -0,0 +1,93 @@
+/* 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 uniffi_meta::MethodMetadata;
+
+use super::convert::{convert_return_type, fn_param_metadata, type_as_type_path};
+use crate::export::{ExportItem, Method, Signature};
+
+pub(super) fn gen_impl_metadata(
+ item: syn::ItemImpl,
+ mod_path: &[String],
+) -> syn::Result<ExportItem> {
+ 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 methods = item
+ .items
+ .into_iter()
+ .map(|it| gen_method_metadata(it, &self_ident.to_string(), mod_path))
+ .collect();
+
+ Ok(ExportItem::Impl {
+ methods,
+ self_ident: self_ident.to_owned(),
+ })
+}
+
+fn gen_method_metadata(
+ it: syn::ImplItem,
+ self_name: &str,
+ mod_path: &[String],
+) -> syn::Result<Method> {
+ let sig = match it {
+ syn::ImplItem::Method(m) => Signature::new(m.sig)?,
+ _ => {
+ return Err(syn::Error::new_spanned(
+ it,
+ "only methods are supported in impl blocks annotated with uniffi::export",
+ ));
+ }
+ };
+
+ let metadata = method_metadata(self_name, &sig, mod_path)?;
+
+ Ok(Method { sig, metadata })
+}
+
+fn method_metadata(
+ self_name: &str,
+ sig: &Signature,
+ mod_path: &[String],
+) -> syn::Result<MethodMetadata> {
+ let (return_type, throws) = match &sig.output {
+ Some(ret) => (
+ convert_return_type(&ret.ty)?,
+ ret.throws.as_ref().map(ToString::to_string),
+ ),
+ None => (None, None),
+ };
+
+ Ok(MethodMetadata {
+ module_path: mod_path.to_owned(),
+ self_name: self_name.to_owned(),
+ name: sig.ident.to_string(),
+ inputs: fn_param_metadata(&sig.inputs)?,
+ return_type,
+ throws,
+ })
+}
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..f66e8bf844
--- /dev/null
+++ b/third_party/rust/uniffi_macros/src/export/scaffolding.rs
@@ -0,0 +1,199 @@
+/* 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 syn::{parse_quote, FnArg, Pat};
+
+use super::{FunctionReturn, Signature};
+
+pub(super) fn gen_fn_scaffolding(
+ sig: &Signature,
+ mod_path: &[String],
+ checksum: u16,
+) -> TokenStream {
+ let name = &sig.ident;
+ let name_s = name.to_string();
+
+ let ffi_ident = Ident::new(
+ &uniffi_meta::fn_ffi_symbol_name(mod_path, &name_s, checksum),
+ Span::call_site(),
+ );
+
+ const ERROR_MSG: &str =
+ "uniffi::export must be used on the impl block, not its containing fn's";
+ let (params, args): (Vec<_>, Vec<_>) = collect_params(&sig.inputs, ERROR_MSG).unzip();
+
+ let fn_call = quote! {
+ #name(#(#args),*)
+ };
+
+ gen_ffi_function(sig, ffi_ident, &params, fn_call)
+}
+
+pub(super) fn gen_method_scaffolding(
+ sig: &Signature,
+ mod_path: &[String],
+ checksum: u16,
+ self_ident: &Ident,
+) -> TokenStream {
+ let name = &sig.ident;
+ let name_s = name.to_string();
+
+ let ffi_name = format!("impl_{self_ident}_{name_s}");
+ let ffi_ident = Ident::new(
+ &uniffi_meta::fn_ffi_symbol_name(mod_path, &ffi_name, checksum),
+ Span::call_site(),
+ );
+
+ let mut params_args = (Vec::new(), Vec::new());
+
+ const RECEIVER_ERROR: &str = "unreachable: only first parameter can be method receiver";
+ let mut assoc_fn_error = None;
+ let fn_call_prefix = match sig.inputs.first() {
+ Some(arg) if is_receiver(arg) => {
+ let ffi_converter = quote! {
+ <::std::sync::Arc<#self_ident> as ::uniffi::FfiConverter>
+ };
+
+ params_args.0.push(quote! { this: #ffi_converter::FfiType });
+
+ let remaining_args = sig.inputs.iter().skip(1);
+ params_args.extend(collect_params(remaining_args, RECEIVER_ERROR));
+
+ quote! {
+ #ffi_converter::try_lift(this).unwrap_or_else(|err| {
+ ::std::panic!("Failed to convert arg 'self': {}", err)
+ }).
+ }
+ }
+ _ => {
+ assoc_fn_error = Some(
+ syn::Error::new_spanned(
+ &sig.ident,
+ "associated functions are not currently supported",
+ )
+ .into_compile_error(),
+ );
+ params_args.extend(collect_params(&sig.inputs, RECEIVER_ERROR));
+ quote! { #self_ident:: }
+ }
+ };
+
+ let (params, args) = params_args;
+
+ let fn_call = quote! {
+ #assoc_fn_error
+ #fn_call_prefix #name(#(#args),*)
+ };
+
+ gen_ffi_function(sig, ffi_ident, &params, fn_call)
+}
+
+fn is_receiver(fn_arg: &FnArg) -> bool {
+ match fn_arg {
+ FnArg::Receiver(_) => true,
+ FnArg::Typed(pat_ty) => matches!(&*pat_ty.pat, Pat::Ident(i) if i.ident == "self"),
+ }
+}
+
+fn collect_params<'a>(
+ inputs: impl IntoIterator<Item = &'a FnArg> + 'a,
+ receiver_error_msg: &'static str,
+) -> impl Iterator<Item = (TokenStream, TokenStream)> + 'a {
+ fn receiver_error(
+ receiver: impl ToTokens,
+ receiver_error_msg: &str,
+ ) -> (TokenStream, TokenStream) {
+ let param = quote! { &self };
+ let arg = syn::Error::new_spanned(receiver, receiver_error_msg).into_compile_error();
+ (param, arg)
+ }
+
+ inputs.into_iter().enumerate().map(|(i, arg)| {
+ let (ty, name) = match arg {
+ FnArg::Receiver(r) => {
+ return receiver_error(r, receiver_error_msg);
+ }
+ FnArg::Typed(pat_ty) => {
+ let name = match &*pat_ty.pat {
+ Pat::Ident(i) if i.ident == "self" => {
+ return receiver_error(i, receiver_error_msg);
+ }
+ Pat::Ident(i) => Some(i.ident.to_string()),
+ _ => None,
+ };
+
+ (&pat_ty.ty, name)
+ }
+ };
+
+ let arg_n = format_ident!("arg{i}");
+ let param = quote! { #arg_n: <#ty as ::uniffi::FfiConverter>::FfiType };
+
+ // FIXME: With UDL, fallible functions use uniffi::lower_anyhow_error_or_panic instead of
+ // panicking unconditionally. This seems cleaner though.
+ let panic_fmt = match name {
+ Some(name) => format!("Failed to convert arg '{name}': {{}}"),
+ None => format!("Failed to convert arg #{i}: {{}}"),
+ };
+ let arg = quote! {
+ <#ty as ::uniffi::FfiConverter>::try_lift(#arg_n).unwrap_or_else(|err| {
+ ::std::panic!(#panic_fmt, err)
+ })
+ };
+
+ (param, arg)
+ })
+}
+
+fn gen_ffi_function(
+ sig: &Signature,
+ ffi_ident: Ident,
+ params: &[TokenStream],
+ rust_fn_call: TokenStream,
+) -> TokenStream {
+ let name = &sig.ident;
+ let name_s = name.to_string();
+
+ let unit_slot;
+ let (ty, throws) = match &sig.output {
+ Some(FunctionReturn { ty, throws }) => (ty, throws),
+ None => {
+ unit_slot = parse_quote! { () };
+ (&unit_slot, &None)
+ }
+ };
+
+ let return_expr = if let Some(error_ident) = throws {
+ quote! {
+ ::uniffi::call_with_result(call_status, || {
+ let val = #rust_fn_call.map_err(|e| {
+ <#error_ident as ::uniffi::FfiConverter>::lower(
+ ::std::convert::Into::into(e),
+ )
+ })?;
+ Ok(<#ty as ::uniffi::FfiReturn>::lower(val))
+ })
+ }
+ } else {
+ quote! {
+ ::uniffi::call_with_output(call_status, || {
+ <#ty as ::uniffi::FfiReturn>::lower(#rust_fn_call)
+ })
+ }
+ };
+
+ quote! {
+ #[doc(hidden)]
+ #[no_mangle]
+ pub extern "C" fn #ffi_ident(
+ #(#params,)*
+ call_status: &mut ::uniffi::RustCallStatus,
+ ) -> <#ty as ::uniffi::FfiReturn>::FfiType {
+ ::uniffi::deps::log::debug!(#name_s);
+ #return_expr
+ }
+ }
+}
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..a38705e57d
--- /dev/null
+++ b/third_party/rust/uniffi_macros/src/lib.rs
@@ -0,0 +1,179 @@
+/* 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))]
+
+//! 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_macro_input, LitStr};
+use util::rewrite_self_type;
+
+mod enum_;
+mod error;
+mod export;
+mod object;
+mod record;
+mod test;
+mod util;
+
+use self::{
+ enum_::expand_enum, error::expand_error, export::expand_export, object::expand_object,
+ record::expand_record,
+};
+
+/// 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)
+}
+
+#[proc_macro_attribute]
+pub fn export(_attr: TokenStream, input: TokenStream) -> TokenStream {
+ let input2 = proc_macro2::TokenStream::from(input.clone());
+
+ let gen_output = || {
+ let mod_path = util::mod_path()?;
+ let mut item = syn::parse(input)?;
+
+ // 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 = export::gen_metadata(item, &mod_path)?;
+ Ok(expand_export(metadata, &mod_path))
+ };
+ let output = gen_output().unwrap_or_else(syn::Error::into_compile_error);
+
+ quote! {
+ #input2
+ #output
+ }
+ .into()
+}
+
+#[proc_macro_derive(Record)]
+pub fn derive_record(input: TokenStream) -> TokenStream {
+ let mod_path = match util::mod_path() {
+ Ok(p) => p,
+ Err(e) => return e.into_compile_error().into(),
+ };
+ let input = parse_macro_input!(input);
+
+ expand_record(input, mod_path).into()
+}
+
+#[proc_macro_derive(Enum)]
+pub fn derive_enum(input: TokenStream) -> TokenStream {
+ let mod_path = match util::mod_path() {
+ Ok(p) => p,
+ Err(e) => return e.into_compile_error().into(),
+ };
+ let input = parse_macro_input!(input);
+
+ expand_enum(input, mod_path).into()
+}
+
+#[proc_macro_derive(Object)]
+pub fn derive_object(input: TokenStream) -> TokenStream {
+ let mod_path = match util::mod_path() {
+ Ok(p) => p,
+ Err(e) => return e.into_compile_error().into(),
+ };
+ let input = parse_macro_input!(input);
+
+ expand_object(input, mod_path).into()
+}
+
+#[proc_macro_derive(Error, attributes(uniffi))]
+pub fn derive_error(input: TokenStream) -> TokenStream {
+ let mod_path = match util::mod_path() {
+ Ok(p) => p,
+ Err(e) => return e.into_compile_error().into(),
+ };
+ let input = parse_macro_input!(input);
+
+ expand_error(input, mod_path).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(component_name: TokenStream) -> TokenStream {
+ let name = syn::parse_macro_input!(component_name as syn::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 {
+ quote! {
+ include!(concat!(env!("OUT_DIR"), "/", #name, ".uniffi.rs"));
+ }
+ }.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 syn::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 uniffi_build::generate_scaffolding(udl_file_path).is_err() {
+ quote! {
+ compile_error!(concat!("Failed to generate scaffolding from UDL file at ", #udl_file));
+ }
+ } 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()
+}
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..2f4988530c
--- /dev/null
+++ b/third_party/rust/uniffi_macros/src/object.rs
@@ -0,0 +1,35 @@
+use proc_macro2::{Ident, Span, TokenStream};
+use quote::quote;
+use syn::DeriveInput;
+use uniffi_meta::ObjectMetadata;
+
+use crate::util::{assert_type_eq, create_metadata_static_var};
+
+pub fn expand_object(input: DeriveInput, module_path: Vec<String>) -> TokenStream {
+ let ident = &input.ident;
+ let name = ident.to_string();
+ let metadata = ObjectMetadata { module_path, name };
+ let free_fn_ident = Ident::new(&metadata.free_ffi_symbol_name(), Span::call_site());
+ let meta_static_var = create_metadata_static_var(ident, metadata.into());
+ let type_assertion = assert_type_eq(ident, quote! { crate::uniffi_types::#ident });
+
+ quote! {
+ #[doc(hidden)]
+ #[no_mangle]
+ pub extern "C" fn #free_fn_ident(
+ ptr: *const ::std::ffi::c_void,
+ call_status: &mut ::uniffi::RustCallStatus
+ ) {
+ uniffi::call_with_output(call_status, || {
+ assert!(!ptr.is_null());
+ let ptr = ptr.cast::<#ident>();
+ unsafe {
+ ::std::sync::Arc::decrement_strong_count(ptr);
+ }
+ });
+ }
+
+ #meta_static_var
+ #type_assertion
+ }
+}
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..60d0962503
--- /dev/null
+++ b/third_party/rust/uniffi_macros/src/record.rs
@@ -0,0 +1,108 @@
+use proc_macro2::{Ident, Span, TokenStream};
+use quote::quote;
+use syn::{Data, DeriveInput, Field, Fields};
+use uniffi_meta::{FieldMetadata, RecordMetadata};
+
+use crate::{
+ export::metadata::convert::convert_type,
+ util::{assert_type_eq, create_metadata_static_var, try_read_field},
+};
+
+pub fn expand_record(input: DeriveInput, module_path: Vec<String>) -> TokenStream {
+ let fields = match input.data {
+ Data::Struct(s) => Some(s.fields),
+ _ => None,
+ };
+
+ let ident = &input.ident;
+
+ let (write_impl, try_read_fields) = match &fields {
+ Some(fields) => (
+ fields.iter().map(write_field).collect(),
+ fields.iter().map(try_read_field).collect(),
+ ),
+ None => {
+ let unimplemented = quote! { ::std::unimplemented!() };
+ (unimplemented.clone(), unimplemented)
+ }
+ };
+
+ let meta_static_var = if let Some(fields) = fields {
+ match record_metadata(ident, fields, module_path) {
+ Ok(metadata) => create_metadata_static_var(ident, metadata.into()),
+ Err(e) => e.into_compile_error(),
+ }
+ } else {
+ syn::Error::new(
+ Span::call_site(),
+ "This derive must only be used on structs",
+ )
+ .into_compile_error()
+ };
+
+ let type_assertion = assert_type_eq(ident, quote! { crate::uniffi_types::#ident });
+
+ quote! {
+ #[automatically_derived]
+ impl ::uniffi::RustBufferFfiConverter for #ident {
+ type RustType = Self;
+
+ 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 })
+ }
+ }
+
+ #meta_static_var
+ #type_assertion
+ }
+}
+
+fn record_metadata(
+ ident: &Ident,
+ fields: Fields,
+ module_path: Vec<String>,
+) -> syn::Result<RecordMetadata> {
+ let name = ident.to_string();
+ let fields = match fields {
+ Fields::Named(fields) => fields.named,
+ _ => {
+ return Err(syn::Error::new(
+ Span::call_site(),
+ "UniFFI only supports structs with named fields",
+ ));
+ }
+ };
+
+ let fields = fields
+ .iter()
+ .map(field_metadata)
+ .collect::<syn::Result<_>>()?;
+
+ Ok(RecordMetadata {
+ module_path,
+ name,
+ fields,
+ })
+}
+
+fn field_metadata(f: &Field) -> syn::Result<FieldMetadata> {
+ let name = f.ident.as_ref().unwrap().to_string();
+
+ Ok(FieldMetadata {
+ name,
+ ty: convert_type(&f.ty)?,
+ })
+}
+
+fn write_field(f: &Field) -> TokenStream {
+ let ident = &f.ident;
+ let ty = &f.ty;
+
+ quote! {
+ <#ty as ::uniffi::FfiConverter>::write(obj.#ident, buf);
+ }
+}
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..ddcbf46000
--- /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..e8e03b3b5d
--- /dev/null
+++ b/third_party/rust/uniffi_macros/src/util.rs
@@ -0,0 +1,223 @@
+/* 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, quote_spanned, ToTokens};
+use syn::{
+ parse::{Parse, ParseStream},
+ punctuated::Punctuated,
+ spanned::Spanned,
+ visit_mut::VisitMut,
+ Attribute, Item, Token, Type,
+};
+use uniffi_meta::Metadata;
+
+#[cfg(not(feature = "nightly"))]
+pub fn mod_path() -> syn::Result<Vec<String>> {
+ // Without the nightly feature and TokenStream::expand_expr, just return the crate name
+
+ use std::path::Path;
+
+ 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<Vec<String>, String>> = Lazy::new(|| {
+ let manifest_dir =
+ std::env::var_os("CARGO_MANIFEST_DIR").ok_or("`CARGO_MANIFEST_DIR` is not set")?;
+
+ let cargo_toml_bytes =
+ fs::read(Path::new(&manifest_dir).join("Cargo.toml")).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(vec![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<Vec<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()
+ .split("::")
+ .collect())
+}
+
+/// 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);
+ }
+}
+
+pub fn try_read_field(f: &syn::Field) -> TokenStream {
+ let ident = &f.ident;
+ let ty = &f.ty;
+
+ quote! {
+ #ident: <#ty as ::uniffi::FfiConverter>::try_read(buf)?,
+ }
+}
+
+pub fn create_metadata_static_var(name: &Ident, val: Metadata) -> TokenStream {
+ let data: Vec<u8> = bincode::serialize(&val).expect("Error serializing metadata item");
+ let count = data.len();
+ let var_name = format_ident!("UNIFFI_META_{}", name);
+
+ quote! {
+ #[no_mangle]
+ #[doc(hidden)]
+ pub static #var_name: [u8; #count] = [#(#data),*];
+ }
+}
+
+pub fn assert_type_eq(a: impl ToTokens + Spanned, b: impl ToTokens) -> TokenStream {
+ quote_spanned! {a.span()=>
+ #[allow(unused_qualifications)]
+ const _: () = {
+ ::uniffi::deps::static_assertions::assert_type_eq_all!(#a, #b);
+ };
+ }
+}
+
+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 UniffiAttribute: Default + Parse {
+ fn merge(self, other: Self) -> syn::Result<Self>;
+}
+
+#[derive(Default)]
+struct AttributeNotAllowedHere;
+
+impl Parse for AttributeNotAllowedHere {
+ fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+ Err(syn::Error::new(
+ input.span(),
+ "UniFFI attributes are not currently recognized in this position",
+ ))
+ }
+}
+
+impl UniffiAttribute for AttributeNotAllowedHere {
+ fn merge(self, _other: Self) -> syn::Result<Self> {
+ Ok(Self)
+ }
+}
+
+pub trait AttributeSliceExt {
+ fn parse_uniffi_attributes<T: UniffiAttribute>(&self) -> syn::Result<T>;
+ fn attributes_not_allowed_here(&self) -> Option<syn::Error>;
+}
+
+impl AttributeSliceExt for [Attribute] {
+ fn parse_uniffi_attributes<T: UniffiAttribute>(&self) -> syn::Result<T> {
+ self.iter()
+ .filter(|attr| attr.path.is_ident("uniffi"))
+ .try_fold(T::default(), |res, attr| {
+ let list: Punctuated<T, Token![,]> =
+ attr.parse_args_with(Punctuated::parse_terminated)?;
+ list.into_iter().try_fold(res, T::merge)
+ })
+ }
+
+ fn attributes_not_allowed_here(&self) -> Option<syn::Error> {
+ self.parse_uniffi_attributes::<AttributeNotAllowedHere>()
+ .err()
+ }
+}
+
+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)
+ }
+ }
+}