From 86be955d4fb6c3294e4c792f99b585b595e253bc Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Thu, 25 Jun 2026 11:19:29 +0100 Subject: [PATCH 1/2] Rename export input to `input` --- bindings/matrix-sdk-ffi-macros/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bindings/matrix-sdk-ffi-macros/src/lib.rs b/bindings/matrix-sdk-ffi-macros/src/lib.rs index 69e1e6e53a4..c3758aedf91 100644 --- a/bindings/matrix-sdk-ffi-macros/src/lib.rs +++ b/bindings/matrix-sdk-ffi-macros/src/lib.rs @@ -19,7 +19,7 @@ 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 { let has_async_fn = |item| { if let Item::Fn(fun) = &item { if fun.sig.asyncness.is_some() { @@ -47,9 +47,9 @@ pub fn export(attr: TokenStream, item: TokenStream) -> TokenStream { }; let attr2 = proc_macro2::TokenStream::from(attr); - let item2 = proc_macro2::TokenStream::from(item.clone()); + let item2 = proc_macro2::TokenStream::from(input.clone()); - let res = match syn::parse(item) { + let res = match syn::parse(input) { Ok(item) => match has_async_fn(item) { true => { quote! { From cdc3d6b29492cef32bfcc46a45e36f0acbb0721a Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Thu, 25 Jun 2026 17:30:36 +0100 Subject: [PATCH 2/2] Tests for the `export` macro --- bindings/matrix-sdk-ffi-macros/Cargo.toml | 1 - bindings/matrix-sdk-ffi-macros/src/lib.rs | 152 ++++++++++++++++++++-- 2 files changed, 143 insertions(+), 10 deletions(-) diff --git a/bindings/matrix-sdk-ffi-macros/Cargo.toml b/bindings/matrix-sdk-ffi-macros/Cargo.toml index 8af36eb7de8..5efefa86dd0 100644 --- a/bindings/matrix-sdk-ffi-macros/Cargo.toml +++ b/bindings/matrix-sdk-ffi-macros/Cargo.toml @@ -13,7 +13,6 @@ publish = false [lib] proc-macro = true -test = false doctest = false [dependencies] diff --git a/bindings/matrix-sdk-ffi-macros/src/lib.rs b/bindings/matrix-sdk-ffi-macros/src/lib.rs index c3758aedf91..b7f83e13273 100644 --- a/bindings/matrix-sdk-ffi-macros/src/lib.rs +++ b/bindings/matrix-sdk-ffi-macros/src/lib.rs @@ -20,6 +20,13 @@ use syn::{ImplItem, Item, TraitItem}; /// export macros if there any `async fn`s in the input. #[proc_macro_attribute] 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() { @@ -46,25 +53,152 @@ pub fn export(attr: TokenStream, input: TokenStream) -> TokenStream { false }; - let attr2 = proc_macro2::TokenStream::from(attr); - let item2 = proc_macro2::TokenStream::from(input.clone()); - - let res = match syn::parse(input) { + 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() }