summaryrefslogtreecommitdiffstats
path: root/third_party/rust/uniffi_macros/src/export
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/uniffi_macros/src/export')
-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
5 files changed, 572 insertions, 0 deletions
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
+ }
+ }
+}