diff options
Diffstat (limited to 'third_party/rust/uniffi_macros/src/export')
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, ¶ms, 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, ¶ms, 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 + } + } +} |