cxx_qt_gen/writer/rust/
mod.rs

1// SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
2// SPDX-FileContributor: Andrew Hayzen <andrew.hayzen@kdab.com>
3//
4// SPDX-License-Identifier: MIT OR Apache-2.0
5
6use crate::generator::rust::GeneratedRustBlocks;
7use proc_macro2::TokenStream;
8use quote::{quote, ToTokens};
9use syn::{parse_quote_spanned, spanned::Spanned};
10
11/// For a given GeneratedRustBlocks write this into a Rust TokenStream
12pub fn write_rust(generated: &GeneratedRustBlocks, include_path: Option<&str>) -> TokenStream {
13    // Retrieve the module contents and namespace
14    let mut cxx_mod = generated.cxx_mod.clone();
15    let mut cxx_mod_contents = vec![];
16    let mut cxx_qt_mod_contents = vec![];
17
18    // Add common includes for all objects
19    cxx_mod_contents.insert(
20        0,
21        syn::parse2(signal_boilerplate()).expect("Could not build CXX common block"),
22    );
23
24    // Inject the include path if we have one after the common CXX block
25    //
26    // We only generate the include when we are from the build script
27    // as in Rust macro expansion the include isn't relevant
28    //
29    // This is useful as then we don't need Span::source_file() to be stable
30    // but can use the name of the file from the build script.
31    if let Some(include_path) = include_path {
32        let import_path = format!("{include_path}.cxxqt.h");
33        cxx_mod_contents.insert(
34            1,
35            parse_quote_spanned! {cxx_mod.span() =>
36                unsafe extern "C++" {
37                    include!(#import_path);
38                }
39            },
40        );
41    }
42
43    for fragment in &generated.fragments {
44        // Add the blocks from the fragment
45        cxx_mod_contents.extend_from_slice(&fragment.cxx_mod_contents);
46        cxx_qt_mod_contents.extend_from_slice(&fragment.cxx_qt_mod_contents);
47    }
48
49    // Inject the CXX blocks
50    if let Some((_, items)) = &mut cxx_mod.content {
51        items.extend(cxx_mod_contents);
52    } else {
53        cxx_mod.content = Some((syn::token::Brace::default(), cxx_mod_contents));
54        cxx_mod.semi = None;
55    }
56
57    quote! {
58        #cxx_mod
59
60        #(#cxx_qt_mod_contents)*
61    }
62    .into_token_stream()
63}
64
65fn signal_boilerplate() -> TokenStream {
66    quote! {
67        unsafe extern "C++" {
68            include ! (< QtCore / QObject >);
69
70            include!("cxx-qt/connection.h");
71            #[doc(hidden)]
72            #[namespace = "Qt"]
73            #[rust_name = "CxxQtConnectionType"]
74            #[allow(dead_code)]
75            type ConnectionType = cxx_qt::ConnectionType;
76
77            #[doc(hidden)]
78            #[namespace = "rust::cxxqt1"]
79            #[rust_name = "CxxQtQMetaObjectConnection"]
80            #[allow(dead_code)]
81            type QMetaObjectConnection = cxx_qt::QMetaObjectConnection;
82        }
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    use crate::generator::rust::fragment::GeneratedRustFragment;
91    use crate::Parser;
92    use pretty_assertions::assert_str_eq;
93    use syn::{parse_quote, ItemMod};
94
95    /// Helper to create a GeneratedRustBlocks for testing
96    pub fn create_generated_rust() -> GeneratedRustBlocks {
97        GeneratedRustBlocks {
98            cxx_mod: parse_quote! {
99                #[cxx::bridge(namespace = "cxx_qt::my_object")]
100                mod ffi {}
101            },
102            namespace: "cxx_qt::my_object".to_owned(),
103            fragments: vec![GeneratedRustFragment {
104                cxx_mod_contents: vec![
105                    parse_quote! {
106                        unsafe extern "C++" {
107                            type MyObject;
108                        }
109                    },
110                    parse_quote! {
111                        extern "Rust" {
112                            type MyObjectRust;
113                        }
114                    },
115                ],
116                cxx_qt_mod_contents: vec![
117                    parse_quote! {
118                        #[derive(Default)]
119                        pub struct MyObjectRust;
120                    },
121                    parse_quote! {
122                        impl MyObjectRust {
123                            fn rust_method(&self) {
124
125                            }
126                        }
127                    },
128                ],
129            }],
130        }
131    }
132
133    /// Helper to create a GeneratedRustBlocks for testing with multiple qobjects
134    pub fn create_generated_rust_multi_qobjects() -> GeneratedRustBlocks {
135        GeneratedRustBlocks {
136            cxx_mod: parse_quote! {
137                #[cxx::bridge(namespace = "cxx_qt")]
138                mod ffi {}
139            },
140            namespace: "cxx_qt".to_owned(),
141            fragments: vec![
142                GeneratedRustFragment {
143                    cxx_mod_contents: vec![
144                        parse_quote! {
145                            unsafe extern "C++" {
146                                type FirstObject;
147                            }
148                        },
149                        parse_quote! {
150                            extern "Rust" {
151                                type FirstObjectRust;
152                            }
153                        },
154                    ],
155                    cxx_qt_mod_contents: vec![
156                        parse_quote! {
157                            #[derive(Default)]
158                            pub struct FirstObjectRust;
159                        },
160                        parse_quote! {
161                            impl FirstObjectRust {
162                                fn rust_method(&self) {
163
164                                }
165                            }
166                        },
167                    ],
168                },
169                GeneratedRustFragment {
170                    cxx_mod_contents: vec![
171                        parse_quote! {
172                            unsafe extern "C++" {
173                                type SecondObject;
174                            }
175                        },
176                        parse_quote! {
177                            extern "Rust" {
178                                type SecondObjectRust;
179                            }
180                        },
181                    ],
182                    cxx_qt_mod_contents: vec![
183                        parse_quote! {
184                            #[derive(Default)]
185                            pub struct SecondObjectRust;
186                        },
187                        parse_quote! {
188                            impl SecondObjectRust {
189                                fn rust_method(&self) {
190
191                                }
192                            }
193                        },
194                    ],
195                },
196            ],
197        }
198    }
199
200    /// Helper for the expected Rust
201    pub fn expected_rust() -> String {
202        let signal_boilerplate = signal_boilerplate();
203        quote! {
204            #[cxx::bridge(namespace = "cxx_qt::my_object")]
205            mod ffi {
206                #signal_boilerplate
207
208                unsafe extern "C++" {
209                    include!("myobject.cxxqt.h");
210                }
211
212                unsafe extern "C++" {
213                    type MyObject;
214                }
215
216                extern "Rust" {
217                    type MyObjectRust;
218                }
219            }
220
221            #[derive(Default)]
222            pub struct MyObjectRust;
223
224            impl MyObjectRust {
225                fn rust_method(&self) {
226
227                }
228            }
229        }
230        .into_token_stream()
231        .to_string()
232    }
233
234    /// Helper for the expected Rust with multiple qobjects
235    pub fn expected_rust_multi_qobjects() -> String {
236        let signal_boilerplate = signal_boilerplate();
237        quote! {
238            #[cxx::bridge(namespace = "cxx_qt")]
239            mod ffi {
240                #signal_boilerplate
241
242                unsafe extern "C++" {
243                    include!("multiobject.cxxqt.h");
244                }
245
246                unsafe extern "C++" {
247                    type FirstObject;
248                }
249
250                extern "Rust" {
251                    type FirstObjectRust;
252                }
253
254                unsafe extern "C++" {
255                    type SecondObject;
256                }
257
258                extern "Rust" {
259                    type SecondObjectRust;
260                }
261            }
262
263            #[derive(Default)]
264            pub struct FirstObjectRust;
265
266            impl FirstObjectRust {
267                fn rust_method(&self) {
268
269                }
270            }
271
272            #[derive(Default)]
273            pub struct SecondObjectRust;
274
275            impl SecondObjectRust {
276                fn rust_method(&self) {
277
278                }
279            }
280        }
281        .into_token_stream()
282        .to_string()
283    }
284
285    #[test]
286    fn test_write_rust() {
287        let generated = create_generated_rust();
288        let result = write_rust(&generated, Some("myobject"));
289        assert_str_eq!(result.to_string(), expected_rust());
290    }
291
292    #[test]
293    fn test_write_rust_empty_mod() {
294        let module: ItemMod = parse_quote! {
295            #[cxx_qt::bridge]
296            mod ffi;
297        };
298        let parser = Parser::from(module).unwrap();
299
300        let generated = GeneratedRustBlocks::from(&parser).unwrap();
301        write_rust(&generated, None);
302    }
303
304    #[test]
305    fn test_write_rust_multi_qobjects() {
306        let generated = create_generated_rust_multi_qobjects();
307        let result = write_rust(&generated, Some("multiobject"));
308        assert_str_eq!(result.to_string(), expected_rust_multi_qobjects());
309    }
310}