Skip to main content

matrix_sdk_ffi_macros/
lib.rs

1// Copyright 2024 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use proc_macro::TokenStream;
16use quote::quote;
17use syn::{ImplItem, Item, TraitItem};
18
19/// Attribute to specify the async runtime parameter for the `uniffi`
20/// export macros if there any `async fn`s in the input.
21#[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}