Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion bindings/matrix-sdk-ffi-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ publish = false

[lib]
proc-macro = true
test = false
doctest = false

[dependencies]
Expand Down
154 changes: 144 additions & 10 deletions bindings/matrix-sdk-ffi-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,14 @@ use syn::{ImplItem, Item, TraitItem};
/// Attribute to specify the async runtime parameter for the `uniffi`
/// export macros if there any `async fn`s in the input.
#[proc_macro_attribute]
pub fn export(attr: TokenStream, item: TokenStream) -> TokenStream {
pub fn export(attr: TokenStream, input: TokenStream) -> TokenStream {
do_export(attr.into(), input.into()).into()
}

fn do_export(
attr: proc_macro2::TokenStream,
input: proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
let has_async_fn = |item| {
if let Item::Fn(fun) = &item {
if fun.sig.asyncness.is_some() {
Expand All @@ -46,25 +53,152 @@ pub fn export(attr: TokenStream, item: TokenStream) -> TokenStream {
false
};

let attr2 = proc_macro2::TokenStream::from(attr);
let item2 = proc_macro2::TokenStream::from(item.clone());

let res = match syn::parse(item) {
let res = match syn::parse2(input.clone()) {
Ok(item) => match has_async_fn(item) {
true => {
quote! {
#[cfg_attr(target_family = "wasm", uniffi::export(#attr2))]
#[cfg_attr(not(target_family = "wasm"), uniffi::export(async_runtime = "tokio", #attr2))]
#[cfg_attr(target_family = "wasm", uniffi::export(#attr))]
#[cfg_attr(not(target_family = "wasm"), uniffi::export(async_runtime = "tokio", #attr))]
}
}
false => quote! { #[uniffi::export(#attr2)] },
false => quote! { #[uniffi::export(#attr)] },
},
Err(e) => e.into_compile_error(),
};

quote! {
#res
#item2
#input
}
}

#[cfg(test)]
mod tests {
use quote::quote;

use super::*;

#[test]
fn export_adds_uniffi_attribute_to_struct() {
let attr = quote! { #[export] };

let input = quote! {
struct MyStruct {}
};

let output = do_export(attr, input);

assert_eq!(
output.to_string(),
quote! {
#[uniffi::export(#[export])]
struct MyStruct {}
}
.to_string()
);
}

#[test]
fn export_adds_uniffi_attribute_to_struct_impl() {
let attr = quote! { #[export] };

let input = quote! {
impl MyStruct {
fn foo() {}
}
};

let output = do_export(attr, input);

assert_eq!(
output.to_string(),
quote! {
#[uniffi::export(#[export])]
impl MyStruct {
fn foo() {}
}
}
.to_string()
);
}

#[test]
fn export_adds_tokio_to_nonwasm_impl_export() {
let attr = quote! { #[export] };

let input = quote! {
impl MyStruct {
async fn foo() {}
}
};

let output = do_export(attr, input);

assert_eq!(
output.to_string(),
quote! {
#[cfg_attr(target_family = "wasm", uniffi::export(#[export]))]
#[cfg_attr(not(target_family = "wasm"), uniffi::export(async_runtime = "tokio" , #[export]))]
impl MyStruct {
async fn foo() {}
}
}
.to_string()
);
}

#[test]
fn export_preserves_fn_attributes_when_no_async_fns() {
let attr = quote! { #[export] };

let input = quote! {
impl MyStruct {
#[cfg(feature = "myfeature")]
fn bar() {}
}
};

let output = do_export(attr, input);

assert_eq!(
output.to_string(),
quote! {
#[uniffi::export(#[export])]
impl MyStruct {
#[cfg(feature = "myfeature")]
fn bar() {}
}
}
.to_string()
);
}

#[test]
fn export_preserves_fn_attributes_even_with_async_fns() {
let attr = quote! { #[export] };

let input = quote! {
impl MyStruct {
async fn foo() {}
#[cfg(feature = "myfeature")]
fn bar() {}
}
};

let output = do_export(attr, input);

assert_eq!(
output.to_string(),
quote! {
#[cfg_attr(target_family = "wasm", uniffi::export(#[export]))]
#[cfg_attr(not(target_family = "wasm"), uniffi::export(async_runtime = "tokio" , #[export]))]
impl MyStruct {
async fn foo() {}
#[cfg(feature = "myfeature")]
fn bar() {}
}
}
.to_string()
);
}
.into()
}
Loading