matrix_sdk_ffi_macros/
lib.rs1use proc_macro::TokenStream;
16use quote::quote;
17use syn::{ImplItem, Item, TraitItem};
18
19#[proc_macro_attribute]
22pub fn export(attr: TokenStream, input: TokenStream) -> TokenStream {
23 do_export(attr.into(), input.into()).into()
24}
25
26fn do_export(
27 attr: proc_macro2::TokenStream,
28 input: proc_macro2::TokenStream,
29) -> proc_macro2::TokenStream {
30 let has_async_fn = |item| {
31 if let Item::Fn(fun) = &item {
32 if fun.sig.asyncness.is_some() {
33 return true;
34 }
35 } else if let Item::Impl(blk) = &item {
36 for item in &blk.items {
37 if let ImplItem::Fn(fun) = item
38 && fun.sig.asyncness.is_some()
39 {
40 return true;
41 }
42 }
43 } else if let Item::Trait(blk) = &item {
44 for item in &blk.items {
45 if let TraitItem::Fn(fun) = item
46 && fun.sig.asyncness.is_some()
47 {
48 return true;
49 }
50 }
51 }
52
53 false
54 };
55
56 let res = match syn::parse2(input.clone()) {
57 Ok(item) => match has_async_fn(item) {
58 true => {
59 quote! {
60 #[cfg_attr(target_family = "wasm", uniffi::export(#attr))]
61 #[cfg_attr(not(target_family = "wasm"), uniffi::export(async_runtime = "tokio", #attr))]
62 }
63 }
64 false => quote! { #[uniffi::export(#attr)] },
65 },
66 Err(e) => e.into_compile_error(),
67 };
68
69 quote! {
70 #res
71 #input
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use quote::quote;
78
79 use super::*;
80
81 #[test]
82 fn export_adds_uniffi_attribute_to_struct() {
83 let attr = quote! { #[export] };
84
85 let input = quote! {
86 struct MyStruct {}
87 };
88
89 let output = do_export(attr, input);
90
91 assert_eq!(
92 output.to_string(),
93 quote! {
94 #[uniffi::export(#[export])]
95 struct MyStruct {}
96 }
97 .to_string()
98 );
99 }
100
101 #[test]
102 fn export_adds_uniffi_attribute_to_struct_impl() {
103 let attr = quote! { #[export] };
104
105 let input = quote! {
106 impl MyStruct {
107 fn foo() {}
108 }
109 };
110
111 let output = do_export(attr, input);
112
113 assert_eq!(
114 output.to_string(),
115 quote! {
116 #[uniffi::export(#[export])]
117 impl MyStruct {
118 fn foo() {}
119 }
120 }
121 .to_string()
122 );
123 }
124
125 #[test]
126 fn export_adds_tokio_to_nonwasm_impl_export() {
127 let attr = quote! { #[export] };
128
129 let input = quote! {
130 impl MyStruct {
131 async fn foo() {}
132 }
133 };
134
135 let output = do_export(attr, input);
136
137 assert_eq!(
138 output.to_string(),
139 quote! {
140 #[cfg_attr(target_family = "wasm", uniffi::export(#[export]))]
141 #[cfg_attr(not(target_family = "wasm"), uniffi::export(async_runtime = "tokio" , #[export]))]
142 impl MyStruct {
143 async fn foo() {}
144 }
145 }
146 .to_string()
147 );
148 }
149
150 #[test]
151 fn export_preserves_fn_attributes_when_no_async_fns() {
152 let attr = quote! { #[export] };
153
154 let input = quote! {
155 impl MyStruct {
156 #[cfg(feature = "myfeature")]
157 fn bar() {}
158 }
159 };
160
161 let output = do_export(attr, input);
162
163 assert_eq!(
164 output.to_string(),
165 quote! {
166 #[uniffi::export(#[export])]
167 impl MyStruct {
168 #[cfg(feature = "myfeature")]
169 fn bar() {}
170 }
171 }
172 .to_string()
173 );
174 }
175
176 #[test]
177 fn export_preserves_fn_attributes_even_with_async_fns() {
178 let attr = quote! { #[export] };
179
180 let input = quote! {
181 impl MyStruct {
182 async fn foo() {}
183 #[cfg(feature = "myfeature")]
184 fn bar() {}
185 }
186 };
187
188 let output = do_export(attr, input);
189
190 assert_eq!(
191 output.to_string(),
192 quote! {
193 #[cfg_attr(target_family = "wasm", uniffi::export(#[export]))]
194 #[cfg_attr(not(target_family = "wasm"), uniffi::export(async_runtime = "tokio" , #[export]))]
195 impl MyStruct {
196 async fn foo() {}
197 #[cfg(feature = "myfeature")]
198 fn bar() {}
199 }
200 }
201 .to_string()
202 );
203 }
204}