Skip to content

Commit 1306667

Browse files
cxx_qt::Constructor: Add Rust generation
1 parent 950537b commit 1306667

File tree

9 files changed

+484
-129
lines changed

9 files changed

+484
-129
lines changed
Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
2+
// SPDX-FileContributor: Leon Matthes <[email protected]>
3+
//
4+
// SPDX-License-Identifier: MIT OR Apache-2.0
5+
6+
use crate::{
7+
generator::{
8+
naming::{namespace::NamespaceName, qobject::QObjectName},
9+
rust::{qobject::GeneratedRustQObjectBlocks, types},
10+
},
11+
parser::constructor::Constructor,
12+
};
13+
14+
use convert_case::{Case, Casing};
15+
use proc_macro2::{Span, TokenStream};
16+
use quote::{format_ident, quote};
17+
use syn::{parse_quote, Ident, Result, Type};
18+
19+
const CONSTRUCTOR_ARGUMENTS: &str = "CxxQtConstructorArguments";
20+
const BASE_ARGUMENTS: &str = "CxxQtConstructorBaseArguments";
21+
const NEW_ARGUMENTS: &str = "CxxQtConstructorNewArguments";
22+
const INITIALIZE_ARGUMENTS: &str = "CxxQtConstructorInitializeArguments";
23+
24+
fn map_types<F: FnMut((usize, &Type)) -> TokenStream>(
25+
args: &Option<Vec<Type>>,
26+
f: F,
27+
) -> Vec<TokenStream> {
28+
args.as_ref()
29+
.map(|args| args.iter().enumerate().map(f).collect())
30+
.unwrap_or_default()
31+
}
32+
33+
fn extract_arguments_from_tuple(args: &Option<Vec<Type>>, tuple_name: Ident) -> Vec<TokenStream> {
34+
map_types(args, |(index, _ty)| {
35+
let arg_name = format_ident!("arg{index}");
36+
let index = syn::LitInt::new(index.to_string().as_str(), Span::call_site());
37+
quote! {
38+
#arg_name: #tuple_name.#index
39+
}
40+
})
41+
}
42+
43+
fn extract_arguments_from_struct(args: &Option<Vec<Type>>, struct_name: Ident) -> Vec<TokenStream> {
44+
map_types(args, |(index, _ty)| {
45+
let arg_name = format_ident!("arg{index}");
46+
quote! {
47+
#struct_name.#arg_name
48+
}
49+
})
50+
}
51+
52+
fn argument_members(args: &Option<Vec<Type>>) -> Vec<TokenStream> {
53+
map_types(args, |(index, ty)| {
54+
let arg_name = format_ident!("arg{index}");
55+
quote! {
56+
#arg_name: #ty
57+
}
58+
})
59+
}
60+
61+
fn generate_default_constructor(
62+
qobject_idents: &QObjectName,
63+
namespace: &NamespaceName,
64+
) -> GeneratedRustQObjectBlocks {
65+
let rust_struct_ident = &qobject_idents.rust_struct.rust;
66+
let create_rs_ident = format_ident!(
67+
"create_rs_{object_name}",
68+
object_name = rust_struct_ident.to_string().to_case(Case::Snake)
69+
);
70+
let namespace_internals = &namespace.internal;
71+
72+
GeneratedRustQObjectBlocks {
73+
cxx_mod_contents: vec![parse_quote! {
74+
extern "Rust" {
75+
#[cxx_name = "createRs"]
76+
#[namespace = #namespace_internals]
77+
fn #create_rs_ident() -> Box<#rust_struct_ident>;
78+
}
79+
}],
80+
cxx_qt_mod_contents: vec![parse_quote! {
81+
/// Generated CXX-Qt method which creates a boxed rust struct of a QObject
82+
pub fn #create_rs_ident() -> std::boxed::Box<#rust_struct_ident> {
83+
core::default::Default::default()
84+
}
85+
}],
86+
}
87+
}
88+
89+
pub fn generate(
90+
constructors: &[Constructor],
91+
qobject_idents: &QObjectName,
92+
namespace: &NamespaceName,
93+
) -> Result<GeneratedRustQObjectBlocks> {
94+
if constructors.is_empty() {
95+
return Ok(generate_default_constructor(qobject_idents, namespace));
96+
}
97+
98+
let mut result = GeneratedRustQObjectBlocks::default();
99+
let namespace_internals = &namespace.internal;
100+
let rust_struct_name_rust = &qobject_idents.rust_struct.rust;
101+
let rust_struct_name_snake = &qobject_idents
102+
.rust_struct
103+
.rust
104+
.to_string()
105+
.to_case(Case::Snake);
106+
let qobject_name_rust = &qobject_idents.cpp_class.rust;
107+
108+
for (index, constructor) in constructors.iter().enumerate() {
109+
let arguments_rust = format_ident!("{CONSTRUCTOR_ARGUMENTS}{rust_struct_name_rust}{index}");
110+
let base_arguments_rust = format_ident!("{BASE_ARGUMENTS}{rust_struct_name_rust}{index}");
111+
let new_arguments_rust = format_ident!("{NEW_ARGUMENTS}{rust_struct_name_rust}{index}");
112+
let initialize_arguments_rust =
113+
format_ident!("{INITIALIZE_ARGUMENTS}{rust_struct_name_rust}{index}");
114+
115+
let arguments_cxx = format!("{CONSTRUCTOR_ARGUMENTS}{index}");
116+
let base_arguments_cxx = format!("{BASE_ARGUMENTS}{index}");
117+
let new_arguments_cxx = format!("{NEW_ARGUMENTS}{index}");
118+
let initialize_arguments_cxx = format!("{INITIALIZE_ARGUMENTS}{index}");
119+
120+
let base_argument_members = argument_members(&constructor.items.base_arguments);
121+
let new_argument_members = argument_members(&constructor.items.new_arguments);
122+
let initialize_argument_members = argument_members(&constructor.items.initialize_arguments);
123+
124+
let new_rust = format_ident!("new_rs_{rust_struct_name_snake}_{index}");
125+
let new_cxx = format!("newRs{index}");
126+
127+
let initialize_rust = format_ident!("initialize_{rust_struct_name_snake}_{index}");
128+
let initialize_cxx = format!("initialize{index}");
129+
130+
let route_arguments_rust =
131+
format_ident!("route_arguments_{rust_struct_name_snake}_{index}");
132+
let route_arguemnts_cxx = format!("routeArguments{index}");
133+
134+
let argument_types = &constructor.arguments;
135+
let empty_vec = &Vec::new();
136+
let base_argument_types = constructor
137+
.items
138+
.base_arguments
139+
.as_ref()
140+
.unwrap_or(empty_vec);
141+
let new_argument_types = &constructor
142+
.items
143+
.new_arguments
144+
.as_ref()
145+
.unwrap_or(empty_vec);
146+
let initialize_argument_types = &constructor
147+
.items
148+
.initialize_arguments
149+
.as_ref()
150+
.unwrap_or(empty_vec);
151+
152+
let passthrough_items = &constructor.items.passthrough;
153+
154+
let route_arguments_parameters: Vec<TokenStream> = constructor
155+
.arguments
156+
.iter()
157+
.enumerate()
158+
.map(|(index, ty)| {
159+
let name = format_ident!("arg{index}");
160+
quote! { #name: #ty }
161+
})
162+
.collect();
163+
let route_arguments_safety = if constructor.arguments.iter().any(types::is_unsafe_cxx_type)
164+
{
165+
quote! { unsafe }
166+
} else {
167+
quote! {}
168+
};
169+
170+
let assign_arguments = constructor
171+
.arguments
172+
.iter()
173+
.enumerate()
174+
.map(|(index, _ty)| {
175+
let name = format_ident!("arg{index}");
176+
quote! { #name }
177+
})
178+
.collect::<Vec<_>>();
179+
180+
let init_new_arguments = extract_arguments_from_tuple(
181+
&constructor.items.new_arguments,
182+
format_ident!("new_arguments"),
183+
);
184+
let init_initialize_arguments = extract_arguments_from_tuple(
185+
&constructor.items.initialize_arguments,
186+
format_ident!("initialize_arguments"),
187+
);
188+
let init_base_arguments = extract_arguments_from_tuple(
189+
&constructor.items.base_arguments,
190+
format_ident!("base_arguments"),
191+
);
192+
193+
let extract_new_arguments = extract_arguments_from_struct(
194+
&constructor.items.new_arguments,
195+
format_ident!("new_arguments"),
196+
);
197+
198+
let extract_initialize_arguments = extract_arguments_from_struct(
199+
&constructor.items.initialize_arguments,
200+
format_ident!("initialize_arguments"),
201+
);
202+
203+
result.cxx_mod_contents.append(&mut vec![
204+
parse_quote! {
205+
#[namespace = #namespace_internals]
206+
#[cxx_name = #arguments_cxx]
207+
#[doc(hidden)]
208+
struct #arguments_rust {
209+
baseArguments: #base_arguments_rust,
210+
newArguments: #new_arguments_rust,
211+
initializeArguments: #initialize_arguments_rust,
212+
}
213+
},
214+
parse_quote! {
215+
#[namespace = #namespace_internals]
216+
#[cxx_name = #base_arguments_cxx]
217+
#[doc(hidden)]
218+
struct #base_arguments_rust {
219+
#(#base_argument_members,)*
220+
notEmpty: i8 // Make sure there's always at least one struct member, as CXX
221+
// doesn't support empty shared structs.
222+
}
223+
},
224+
parse_quote! {
225+
#[namespace = #namespace_internals]
226+
#[cxx_name = #new_arguments_cxx]
227+
#[doc(hidden)]
228+
struct #new_arguments_rust {
229+
#(#new_argument_members,)*
230+
notEmpty: i8 // Make sure there's always at least one struct member, as CXX
231+
// doesn't support empty shared structs.
232+
}
233+
},
234+
parse_quote! {
235+
#[namespace = #namespace_internals]
236+
#[cxx_name = #initialize_arguments_cxx]
237+
#[doc(hidden)]
238+
struct #initialize_arguments_rust {
239+
#(#initialize_argument_members,)*
240+
notEmpty: i8 // Make sure there's always at least one struct member, as CXX
241+
// doesn't support empty shared structs.
242+
}
243+
},
244+
parse_quote! {
245+
extern "Rust" {
246+
#[namespace = #namespace_internals]
247+
#[cxx_name = #route_arguemnts_cxx]
248+
// This function needs to marked unsafe, as some arguments may be pointers.
249+
#route_arguments_safety fn #route_arguments_rust(#(#route_arguments_parameters),*) -> #arguments_rust;
250+
251+
#[namespace = #namespace_internals]
252+
#[cxx_name = #new_cxx]
253+
fn #new_rust(args: #new_arguments_rust) -> Box<#rust_struct_name_rust>;
254+
255+
#[namespace = #namespace_internals]
256+
#[cxx_name = #initialize_cxx]
257+
fn #initialize_rust(qobject: Pin<&mut #qobject_name_rust>, args: #initialize_arguments_rust);
258+
}
259+
},
260+
]);
261+
result.cxx_qt_mod_contents.append(&mut vec![parse_quote! {
262+
#[doc(hidden)]
263+
pub fn #route_arguments_rust(#(#route_arguments_parameters),*) -> #arguments_rust {
264+
// These variables won't be used if the corresponding argument list is empty.
265+
#[allow(unused_variable)]
266+
let (
267+
new_arguments,
268+
base_arguments,
269+
initialize_arguments
270+
) = <#qobject_name_rust as cxx_qt::Constructor<(#(#argument_types,)*)>>
271+
::route_arguments((#(#assign_arguments,)*));
272+
#arguments_rust {
273+
baseArguments: #base_arguments_rust {
274+
#(#init_base_arguments,)*
275+
notEmpty: 0
276+
},
277+
initializeArguments: #initialize_arguments_rust {
278+
#(#init_initialize_arguments,)*
279+
notEmpty: 0
280+
},
281+
newArguments: #new_arguments_rust {
282+
#(#init_new_arguments,)*
283+
notEmpty: 0
284+
},
285+
}
286+
}
287+
},
288+
parse_quote! {
289+
#[doc(hidden)]
290+
pub fn #new_rust(new_arguments: #new_arguments_rust) -> std::boxed::Box<#rust_struct_name_rust> {
291+
let new_arguments = (#(#extract_new_arguments,)*);
292+
std::boxed::Box::new(<#qobject_name_rust as cxx_qt::Constructor<(#(#argument_types,)*)>>::new(new_arguments))
293+
}
294+
},
295+
parse_quote! {
296+
#[doc(hidden)]
297+
pub fn #initialize_rust(
298+
qobject: core::pin::Pin<&mut #qobject_name_rust>,
299+
initialize_arguments: #initialize_arguments_rust
300+
) {
301+
let initialize_arguments = (#(#extract_initialize_arguments,)*);
302+
<#qobject_name_rust as cxx_qt::Constructor<(#(#argument_types,)*)>>::initialize(qobject, initialize_arguments)
303+
}
304+
},
305+
parse_quote! {
306+
impl cxx_qt::Constructor<(#(#argument_types,)*)> for #qobject_name_rust {
307+
type NewArguments = (#(#new_argument_types,)*);
308+
type InitializeArguments = (#(#initialize_argument_types,)*);
309+
type BaseArguments = (#(#base_argument_types,)*);
310+
311+
#(#passthrough_items)*
312+
}
313+
}])
314+
}
315+
Ok(result)
316+
}

crates/cxx-qt-gen/src/generator/rust/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
//
44
// SPDX-License-Identifier: MIT OR Apache-2.0
55

6+
pub mod constructor;
67
pub mod fragment;
78
pub mod inherit;
89
pub mod invokable;

crates/cxx-qt-gen/src/generator/rust/qobject.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::{
77
generator::{
88
naming::{namespace::NamespaceName, qobject::QObjectName},
99
rust::{
10-
fragment::RustFragmentPair, inherit, invokable::generate_rust_invokables,
10+
constructor, fragment::RustFragmentPair, inherit, invokable::generate_rust_invokables,
1111
property::generate_rust_properties, signals::generate_rust_signals, threading,
1212
},
1313
},
@@ -130,6 +130,12 @@ impl GeneratedRustQObject {
130130
});
131131
}
132132

133+
generated.blocks.append(&mut constructor::generate(
134+
&qobject.constructors,
135+
&qobject_idents,
136+
&namespace_idents,
137+
)?);
138+
133139
Ok(generated)
134140
}
135141
}
@@ -256,7 +262,7 @@ mod tests {
256262

257263
let rust = GeneratedRustQObject::from(parser.cxx_qt_data.qobjects.values().next().unwrap())
258264
.unwrap();
259-
assert_eq!(rust.blocks.cxx_mod_contents.len(), 3);
265+
assert_eq!(rust.blocks.cxx_mod_contents.len(), 4);
260266
assert_tokens_eq(
261267
&rust.blocks.cxx_mod_contents[0],
262268
quote! {
@@ -289,5 +295,15 @@ mod tests {
289295
}
290296
},
291297
);
298+
assert_tokens_eq(
299+
&rust.blocks.cxx_mod_contents[3],
300+
quote! {
301+
extern "Rust" {
302+
#[cxx_name = "createRs"]
303+
#[namespace = "cxx_qt::cxx_qt_my_object"]
304+
fn create_rs_my_object() -> Box<MyObject>;
305+
}
306+
},
307+
);
292308
}
293309
}

0 commit comments

Comments
 (0)