summaryrefslogtreecommitdiffstats
path: root/third_party/rust/uniffi_macros
diff options
context:
space:
mode:
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.toml70
-rw-r--r--third_party/rust/uniffi_macros/src/export.rs163
-rw-r--r--third_party/rust/uniffi_macros/src/export/metadata.rs26
-rw-r--r--third_party/rust/uniffi_macros/src/export/metadata/convert.rs179
-rw-r--r--third_party/rust/uniffi_macros/src/export/metadata/function.rs26
-rw-r--r--third_party/rust/uniffi_macros/src/export/metadata/impl_.rs84
-rw-r--r--third_party/rust/uniffi_macros/src/export/scaffolding.rs184
-rw-r--r--third_party/rust/uniffi_macros/src/lib.rs238
-rw-r--r--third_party/rust/uniffi_macros/src/object.rs35
-rw-r--r--third_party/rust/uniffi_macros/src/record.rs100
-rw-r--r--third_party/rust/uniffi_macros/src/util.rs146
12 files changed, 1252 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..0e3ebe4a5a
--- /dev/null
+++ b/third_party/rust/uniffi_macros/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"6511b493b676ac3941d70477c91abec62642c3c9aab088ecdf4f733eef3f1faa","src/export.rs":"e23929cf6fb5542d29514fe668f3b3d836fad968eacd9c6fcba74c5cd9cf2b61","src/export/metadata.rs":"af89a9942c7c0c4043a3cd57d1e6bd71cde19005e1f9f246efac761f47eff6be","src/export/metadata/convert.rs":"81060fb3390165d77db021f44142a2f3f10882515f859d7393857083370f2d35","src/export/metadata/function.rs":"11833cabd37e7671c0a01944bec73b8892a15df814bbe4c26fdae57aad89a2ba","src/export/metadata/impl_.rs":"ecfdaa132f05dd946414281e52165ef19c90c0bfd76ec651d4ec86837bd41d1c","src/export/scaffolding.rs":"66939405063e56fc983126f249e2d7ddc3257cb045a738abd0cf813a4aafc59c","src/lib.rs":"ca77b437a58cfb3ddeb106d3c1c8378545c46ef241298e62ab1190c5136d1fb1","src/object.rs":"955b596f344304013692042bdc1760bbb1192ec33950b0dd2932cb8de94ec297","src/record.rs":"67a5c7ed6a448f7ad8f5c8e930c5e3007b2b0cac32f52cc8596bdae6fb3c816e","src/util.rs":"6389a9b4258808a3af168cf85658fb7c069172d5e528ee0e94210fa664f2a414"},"package":"c96a574677566f83ea8458dac1dd7792fd63e7c3f9dbcd865f0e8d6f8057b127"} \ 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..af38af08f4
--- /dev/null
+++ b/third_party/rust/uniffi_macros/Cargo.toml
@@ -0,0 +1,70 @@
+# 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.21.1"
+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.21.1"
+
+[dependencies.uniffi_meta]
+version = "=0.21.1"
+
+[features]
+builtin-bindgen = ["uniffi_build/builtin-bindgen"]
+default = []
+nightly = []
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..0910f1f602
--- /dev/null
+++ b/third_party/rust/uniffi_macros/src/export.rs
@@ -0,0 +1,163 @@
+/* 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::scaffolding::{gen_fn_scaffolding, gen_method_scaffolding};
+use crate::{
+ export::metadata::convert::convert_type,
+ util::{assert_type_eq, create_metadata_static_var},
+};
+
+// TODO(jplatte): Ensure no generics, no async, …
+// TODO(jplatte): Aggregate errors instead of short-circuiting, whereever possible
+
+pub enum ExportItem {
+ Function {
+ sig: Box<syn::Signature>,
+ metadata: FnMetadata,
+ },
+ Impl {
+ self_ident: Ident,
+ methods: Vec<syn::Result<Method>>,
+ },
+}
+
+pub struct Method {
+ item: syn::ImplItemMethod,
+ metadata: MethodMetadata,
+}
+
+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 { item, metadata }| {
+ let checksum = checksum(&metadata);
+ let scaffolding =
+ gen_method_scaffolding(&item.sig, mod_path, checksum, &self_ident);
+ let type_assertions = fn_type_assertions(&item.sig);
+ let meta_static_var = create_metadata_static_var(
+ &format_ident!("{}_{}", metadata.self_name, item.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: &syn::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 = match &sig.output {
+ syn::ReturnType::Default => None,
+ syn::ReturnType::Type(_, ty) => Some(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();
+
+ type_assertions.into_values().collect()
+}
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..2d0027b695
--- /dev/null
+++ b/third_party/rust/uniffi_macros/src/export/metadata/convert.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/. */
+
+use proc_macro2::Ident;
+use quote::ToTokens;
+use syn::{punctuated::Punctuated, Token};
+use uniffi_meta::{FnParamMetadata, Type};
+
+pub(super) fn fn_param_metadata(
+ params: &Punctuated<syn::FnArg, Token![,]>,
+) -> 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(super) fn return_type_metadata(ty: &syn::ReturnType) -> syn::Result<Option<Type>> {
+ Ok(match ty {
+ syn::ReturnType::Default => None,
+ syn::ReturnType::Type(_, ty) => Some(convert_type(ty)?),
+ })
+}
+
+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_path(arg)?
+ .path
+ .get_ident()
+ .ok_or_else(|| type_not_supported(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)),
+ }
+}
+
+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",
+ )
+}
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..b19e8108c6
--- /dev/null
+++ b/third_party/rust/uniffi_macros/src/export/metadata/function.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 uniffi_meta::FnMetadata;
+
+use super::convert::{fn_param_metadata, return_type_metadata};
+use crate::export::ExportItem;
+
+pub(super) fn gen_fn_metadata(sig: syn::Signature, mod_path: &[String]) -> syn::Result<ExportItem> {
+ let metadata = fn_metadata(&sig, mod_path)?;
+
+ Ok(ExportItem::Function {
+ sig: Box::new(sig),
+ metadata,
+ })
+}
+
+fn fn_metadata(sig: &syn::Signature, mod_path: &[String]) -> syn::Result<FnMetadata> {
+ Ok(FnMetadata {
+ module_path: mod_path.to_owned(),
+ name: sig.ident.to_string(),
+ inputs: fn_param_metadata(&sig.inputs)?,
+ return_type: return_type_metadata(&sig.output)?,
+ })
+}
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..33709ba692
--- /dev/null
+++ b/third_party/rust/uniffi_macros/src/export/metadata/impl_.rs
@@ -0,0 +1,84 @@
+/* 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::{fn_param_metadata, return_type_metadata, type_as_type_path};
+use crate::export::{ExportItem, Method};
+
+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 item = match it {
+ syn::ImplItem::Method(m) => m,
+ _ => {
+ return Err(syn::Error::new_spanned(
+ it,
+ "only methods are supported in impl blocks annotated with uniffi::export",
+ ));
+ }
+ };
+
+ let metadata = method_metadata(self_name, &item, mod_path)?;
+
+ Ok(Method { item, metadata })
+}
+
+fn method_metadata(
+ self_name: &str,
+ f: &syn::ImplItemMethod,
+ mod_path: &[String],
+) -> syn::Result<MethodMetadata> {
+ Ok(MethodMetadata {
+ module_path: mod_path.to_owned(),
+ self_name: self_name.to_owned(),
+ name: f.sig.ident.to_string(),
+ inputs: fn_param_metadata(&f.sig.inputs)?,
+ return_type: return_type_metadata(&f.sig.output)?,
+ })
+}
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..8d46a3aec5
--- /dev/null
+++ b/third_party/rust/uniffi_macros/src/export/scaffolding.rs
@@ -0,0 +1,184 @@
+/* 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::{FnArg, Pat, ReturnType, 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: &syn::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, "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 };
+
+ 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: &syn::Signature,
+ ffi_ident: Ident,
+ params: &[TokenStream],
+ rust_fn_call: TokenStream,
+) -> TokenStream {
+ let name = &sig.ident;
+ let name_s = name.to_string();
+
+ // FIXME(jplatte): Use an extra trait implemented for `T: FfiConverter` as
+ // well as `()` so no different codegen is needed?
+ let (output, return_expr);
+ match &sig.output {
+ ReturnType::Default => {
+ output = None;
+ return_expr = rust_fn_call;
+ }
+ ReturnType::Type(_, ty) => {
+ output = Some(quote! {
+ -> <#ty as ::uniffi::FfiConverter>::FfiType
+ });
+ return_expr = quote! {
+ <#ty as ::uniffi::FfiConverter>::lower(#rust_fn_call)
+ };
+ }
+ }
+
+ quote! {
+ #[doc(hidden)]
+ #[no_mangle]
+ pub extern "C" fn #ffi_ident(
+ #(#params,)*
+ call_status: &mut ::uniffi::RustCallStatus,
+ ) #output {
+ ::uniffi::deps::log::debug!(#name_s);
+ ::uniffi::call_with_output(call_status, || {
+ #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..fb19d0711c
--- /dev/null
+++ b/third_party/rust/uniffi_macros/src/lib.rs
@@ -0,0 +1,238 @@
+/* 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, Utf8PathBuf};
+use proc_macro::TokenStream;
+use quote::{format_ident, quote};
+use std::env;
+use syn::{bracketed, parse_macro_input, punctuated::Punctuated, LitStr, Token};
+use util::rewrite_self_type;
+
+mod export;
+mod object;
+mod record;
+mod util;
+
+use self::{export::expand_export, object::expand_object, record::expand_record};
+
+#[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(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()
+}
+
+/// 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 one or more udl files 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(paths: TokenStream) -> TokenStream {
+ let paths = syn::parse_macro_input!(paths as FilePaths);
+ // 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");
+
+ // Create an array of UDL files.
+ let udl_files = &paths
+ .udl_files
+ .iter()
+ .map(|file_path| {
+ let pathbuf: Utf8PathBuf = [&pkg_dir, file_path].iter().collect();
+ let path = pathbuf.to_string();
+ quote! { #path }
+ })
+ .collect::<Vec<proc_macro2::TokenStream>>();
+
+ // For each test file found, generate a matching testcase.
+ let test_functions = paths.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 maybe_ignore = if should_skip_path(&test_file_pathbuf) {
+ quote! { #[ignore] }
+ } else {
+ quote! { }
+ };
+ quote! {
+ #maybe_ignore
+ #[test]
+ fn #test_name () -> uniffi::deps::anyhow::Result<()> {
+ uniffi::testing::run_foreign_language_testcase(#pkg_dir, &[ #(#udl_files),* ], #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)
+}
+
+/// Newtype to simplifying parsing a list of file paths from macro input.
+#[derive(Debug)]
+struct FilePaths {
+ udl_files: Vec<String>,
+ test_scripts: Vec<String>,
+}
+
+impl syn::parse::Parse for FilePaths {
+ fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+ let udl_array;
+ bracketed!(udl_array in input);
+ let udl_files = Punctuated::<LitStr, Token![,]>::parse_terminated(&udl_array)?
+ .iter()
+ .map(|s| s.value())
+ .collect();
+
+ let _comma: Token![,] = input.parse()?;
+
+ let scripts_array;
+ bracketed!(scripts_array in input);
+ let test_scripts = Punctuated::<LitStr, Token![,]>::parse_terminated(&scripts_array)?
+ .iter()
+ .map(|s| s.value())
+ .collect();
+
+ Ok(FilePaths {
+ udl_files,
+ test_scripts,
+ })
+ }
+}
+
+/// 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..b8bbc3e10e
--- /dev/null
+++ b/third_party/rust/uniffi_macros/src/record.rs
@@ -0,0 +1,100 @@
+use proc_macro2::{Span, TokenStream};
+use quote::quote;
+use syn::{Data, DeriveInput};
+use uniffi_meta::{FieldMetadata, RecordMetadata};
+
+use crate::{
+ export::metadata::convert::convert_type,
+ util::{assert_type_eq, create_metadata_static_var},
+};
+
+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(|f| {
+ let ident = &f.ident;
+ let ty = &f.ty;
+
+ let write_field = quote! {
+ <#ty as ::uniffi::FfiConverter>::write(obj.#ident, buf);
+ };
+ let try_read_field = quote! {
+ #ident: <#ty as ::uniffi::FfiConverter>::try_read(buf)?,
+ };
+
+ (write_field, try_read_field)
+ })
+ .unzip(),
+ None => {
+ let unimplemented = quote! { ::std::unimplemented!() };
+ (unimplemented.clone(), unimplemented)
+ }
+ };
+
+ let meta_static_var = fields
+ .map(|fields| {
+ let name = ident.to_string();
+ let fields_res: syn::Result<_> = fields
+ .iter()
+ .map(|f| {
+ let name = f
+ .ident
+ .as_ref()
+ .expect("We only allow record structs")
+ .to_string();
+
+ Ok(FieldMetadata {
+ name,
+ ty: convert_type(&f.ty)?,
+ })
+ })
+ .collect();
+
+ match fields_res {
+ Ok(fields) => {
+ let metadata = RecordMetadata {
+ module_path,
+ name,
+ fields,
+ };
+
+ create_metadata_static_var(ident, metadata.into())
+ }
+ Err(e) => e.into_compile_error(),
+ }
+ })
+ .unwrap_or_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! {
+ 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
+ }
+}
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..32ca08fc79
--- /dev/null
+++ b/third_party/rust/uniffi_macros/src/util.rs
@@ -0,0 +1,146 @@
+/* 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::{spanned::Spanned, visit_mut::VisitMut, Item, 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;
+ use quote::quote;
+
+ 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 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);
+ };
+ }
+}