Skip to content

Commit

Permalink
cxx_qt::Constructor: Add Rust generation
Browse files Browse the repository at this point in the history
  • Loading branch information
LeonMatthesKDAB committed Jun 7, 2023
1 parent 2793fc8 commit 6794073
Show file tree
Hide file tree
Showing 9 changed files with 470 additions and 129 deletions.
304 changes: 304 additions & 0 deletions crates/cxx-qt-gen/src/generator/rust/constructor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
// SPDX-FileContributor: Leon Matthes <leon.matthes@kdab.com>
//
// SPDX-License-Identifier: MIT OR Apache-2.0

use crate::{
generator::{
naming::{namespace::NamespaceName, qobject::QObjectName},
rust::qobject::GeneratedRustQObjectBlocks,
},
parser::constructor::Constructor,
};

use convert_case::{Case, Casing};
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote};
use syn::{parse_quote, Ident, Result, Type};

const CONSTRUCTOR_ARGUMENTS: &str = "CxxQtConstructorArguments";
const BASE_ARGUMENTS: &str = "CxxQtConstructorBaseArguments";
const NEW_ARGUMENTS: &str = "CxxQtConstructorNewArguments";
const INITIALIZE_ARGUMENTS: &str = "CxxQtConstructorInitializeArguments";

fn map_types<F: FnMut((usize, &Type)) -> TokenStream>(
args: &Option<Vec<Type>>,
f: F,
) -> Vec<TokenStream> {
args.as_ref()
.map(|args| args.iter().enumerate().map(f).collect())
.unwrap_or_default()
}

fn extract_arguments_from_tuple(args: &Option<Vec<Type>>, tuple_name: Ident) -> Vec<TokenStream> {
map_types(args, |(index, _ty)| {
let arg_name = format_ident!("arg{index}");
let index = syn::LitInt::new(index.to_string().as_str(), Span::call_site());
quote! {
#arg_name: #tuple_name.#index
}
})
}

fn extract_arguments_from_struct(args: &Option<Vec<Type>>, struct_name: Ident) -> Vec<TokenStream> {
map_types(args, |(index, _ty)| {
let arg_name = format_ident!("arg{index}");
quote! {
#struct_name.#arg_name
}
})
}

fn argument_members(args: &Option<Vec<Type>>) -> Vec<TokenStream> {
map_types(args, |(index, ty)| {
let arg_name = format_ident!("arg{index}");
quote! {
#arg_name: #ty
}
})
}

fn generate_default_constructor(
qobject_idents: &QObjectName,
namespace: &NamespaceName,
) -> GeneratedRustQObjectBlocks {
let rust_struct_ident = &qobject_idents.rust_struct.rust;
let create_rs_ident = format_ident!(
"create_rs_{object_name}",
object_name = rust_struct_ident.to_string().to_case(Case::Snake)
);
let namespace_internals = &namespace.internal;

GeneratedRustQObjectBlocks {
cxx_mod_contents: vec![parse_quote! {
extern "Rust" {
#[cxx_name = "createRs"]
#[namespace = #namespace_internals]
fn #create_rs_ident() -> Box<#rust_struct_ident>;
}
}],
cxx_qt_mod_contents: vec![parse_quote! {
/// Generated CXX-Qt method which creates a boxed rust struct of a QObject
pub fn #create_rs_ident() -> std::boxed::Box<#rust_struct_ident> {
std::default::Default::default()
}
}],
}
}

pub fn generate(
constructors: &[Constructor],
qobject_idents: &QObjectName,
namespace: &NamespaceName,
) -> Result<GeneratedRustQObjectBlocks> {
if constructors.is_empty() {
return Ok(generate_default_constructor(qobject_idents, namespace));
}

let mut result = GeneratedRustQObjectBlocks::default();
let namespace_internals = &namespace.internal;
let rust_struct_name_rust = &qobject_idents.rust_struct.rust;
let qobject_name_rust = &qobject_idents.cpp_class.rust;

for (index, constructor) in constructors.iter().enumerate() {
let arguments_rust = format_ident!("{CONSTRUCTOR_ARGUMENTS}{rust_struct_name_rust}{index}");
let base_arguments_rust = format_ident!("{BASE_ARGUMENTS}{rust_struct_name_rust}{index}");
let new_arguments_rust = format_ident!("{NEW_ARGUMENTS}{rust_struct_name_rust}{index}");
let initialize_arguments_rust =
format_ident!("{INITIALIZE_ARGUMENTS}{rust_struct_name_rust}{index}");

let arguments_cxx = format!("{CONSTRUCTOR_ARGUMENTS}{index}");
let base_arguments_cxx = format!("{BASE_ARGUMENTS}{index}");
let new_arguments_cxx = format!("{NEW_ARGUMENTS}{index}");
let initialize_arguments_cxx = format!("{INITIALIZE_ARGUMENTS}{index}");

let base_argument_members = argument_members(&constructor.items.base_arguments);
let new_argument_members = argument_members(&constructor.items.new_arguments);
let initialize_argument_members = argument_members(&constructor.items.initialize_arguments);

let new_rust = format_ident!("new_rs_{rust_struct_name_rust}_{index}");
let new_cxx = format!("newRs{index}");

let initialize_rust = format_ident!("intialize_{rust_struct_name_rust}_{index}");
let initialize_cxx = format!("initialize{index}");

let route_arguments_rust = format_ident!("route_arguments_{rust_struct_name_rust}_{index}");
let route_arguemnts_cxx = format!("routeArguments{index}");

let argument_types = &constructor.arguments;
let empty_vec = &Vec::new();
let base_argument_types = constructor
.items
.base_arguments
.as_ref()
.unwrap_or(empty_vec);
let new_argument_types = &constructor
.items
.new_arguments
.as_ref()
.unwrap_or(empty_vec);
let initialize_argument_types = &constructor
.items
.initialize_arguments
.as_ref()
.unwrap_or(empty_vec);

let passthrough_items = &constructor.items.passthrough;

let route_arguments_parameters: Vec<TokenStream> = constructor
.arguments
.iter()
.enumerate()
.map(|(index, ty)| {
let name = format_ident!("arg{index}");
quote! { #name: #ty }
})
.collect();

let assign_arguments = constructor
.arguments
.iter()
.enumerate()
.map(|(index, _ty)| {
let name = format_ident!("arg{index}");
quote! { #name }
})
.collect::<Vec<_>>();

let init_new_arguments = extract_arguments_from_tuple(
&constructor.items.new_arguments,
format_ident!("new_arguments"),
);
let init_initialize_arguments = extract_arguments_from_tuple(
&constructor.items.initialize_arguments,
format_ident!("initialize_arguments"),
);
let init_base_arguments = extract_arguments_from_tuple(
&constructor.items.base_arguments,
format_ident!("base_arguments"),
);

let extract_new_arguments = extract_arguments_from_struct(
&constructor.items.new_arguments,
format_ident!("new_arguments"),
);

let extract_initialize_arguments = extract_arguments_from_struct(
&constructor.items.initialize_arguments,
format_ident!("initialize_arguments"),
);

result.cxx_mod_contents.append(&mut vec![
parse_quote! {
#[namespace = #namespace_internals]
#[cxx_name = #arguments_cxx]
#[doc(hidden)]
struct #arguments_rust {
baseArguments: #base_arguments_rust,
newArguments: #new_arguments_rust,
initializeArguments: #initialize_arguments_rust,
}
},
parse_quote! {
#[namespace = #namespace_internals]
#[cxx_name = #base_arguments_cxx]
#[doc(hidden)]
struct #base_arguments_rust {
#(#base_argument_members,)*
notEmpty: i8 // Make sure there's always at least one struct member, as CXX
// doesn't support empty shared structs.
}
},
parse_quote! {
#[namespace = #namespace_internals]
#[cxx_name = #new_arguments_cxx]
#[doc(hidden)]
struct #new_arguments_rust {
#(#new_argument_members,)*
notEmpty: i8 // Make sure there's always at least one struct member, as CXX
// doesn't support empty shared structs.
}
},
parse_quote! {
#[namespace = #namespace_internals]
#[cxx_name = #initialize_arguments_cxx]
#[doc(hidden)]
struct #initialize_arguments_rust {
#(#initialize_argument_members,)*
notEmpty: i8 // Make sure there's always at least one struct member, as CXX
// doesn't support empty shared structs.
}
},
parse_quote! {
extern "Rust" {
#[namespace = #namespace_internals]
#[cxx_name = #route_arguemnts_cxx]
// This function needs to marked unsafe, as some arguments may be pointers.
unsafe fn #route_arguments_rust(#(#route_arguments_parameters),*) -> #arguments_rust;

#[namespace = #namespace_internals]
#[cxx_name = #new_cxx]
fn #new_rust(args: #new_arguments_rust) -> Box<#rust_struct_name_rust>;

#[namespace = #namespace_internals]
#[cxx_name = #initialize_cxx]
fn #initialize_rust(qobject: Pin<&mut #qobject_name_rust>, args: #initialize_arguments_rust);
}
},
]);
result.cxx_qt_mod_contents.append(&mut vec![parse_quote! {
#[doc(hidden)]
pub fn #route_arguments_rust(#(#route_arguments_parameters),*) -> #arguments_rust {
// these won't be used if any of them don't have any members
#[allow(unused_variable)]
let (
new_arguments,
base_arguments,
initialize_arguments
) = <#qobject_name_rust as cxx_qt::Constructor<(#(#argument_types,)*)>>
::route_arguments((#(#assign_arguments,)*));
#arguments_rust {
baseArguments: #base_arguments_rust {
#(#init_base_arguments,)*
notEmpty: 0
},
initializeArguments: #initialize_arguments_rust {
#(#init_initialize_arguments,)*
notEmpty: 0
},
newArguments: #new_arguments_rust {
#(#init_new_arguments,)*
notEmpty: 0
},
}
}
},
parse_quote! {
#[doc(hidden)]
pub fn #new_rust(new_arguments: #new_arguments_rust) -> Box<#rust_struct_name_rust> {
let new_arguments = (#(#extract_new_arguments,)*);
Box::new(<#qobject_name_rust as cxx_qt::Constructor<(#(#argument_types,)*)>>::new(new_arguments))
}
},
parse_quote! {
#[doc(hidden)]
pub fn #initialize_rust(
qobject: Pin<&mut #qobject_name_rust>,
initialize_arguments: #initialize_arguments_rust
) {
let initialize_arguments = (#(#extract_initialize_arguments,)*);
<#qobject_name_rust as cxx_qt::Constructor<(#(#argument_types,)*)>>::initialize(qobject, initialize_arguments)
}
},
parse_quote! {
impl cxx_qt::Constructor<(#(#argument_types,)*)> for #qobject_name_rust {
type NewArguments = (#(#new_argument_types,)*);
type InitializeArguments = (#(#initialize_argument_types,)*);
type BaseArguments = (#(#base_argument_types,)*);

#(#passthrough_items)*
}
}])
}
Ok(result)
}
1 change: 1 addition & 0 deletions crates/cxx-qt-gen/src/generator/rust/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//
// SPDX-License-Identifier: MIT OR Apache-2.0

pub mod constructor;
pub mod field;
pub mod fragment;
pub mod inherit;
Expand Down
20 changes: 18 additions & 2 deletions crates/cxx-qt-gen/src/generator/rust/qobject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::{
generator::{
naming::{namespace::NamespaceName, qobject::QObjectName},
rust::{
field::generate_rust_fields, fragment::RustFragmentPair, inherit,
constructor, field::generate_rust_fields, fragment::RustFragmentPair, inherit,
invokable::generate_rust_invokables, property::generate_rust_properties,
signals::generate_rust_signals, threading,
},
Expand Down Expand Up @@ -136,6 +136,12 @@ impl GeneratedRustQObject {
});
}

generated.blocks.append(&mut constructor::generate(
&qobject.constructors,
&qobject_idents,
&namespace_idents,
)?);

Ok(generated)
}
}
Expand Down Expand Up @@ -262,7 +268,7 @@ mod tests {

let rust = GeneratedRustQObject::from(parser.cxx_qt_data.qobjects.values().next().unwrap())
.unwrap();
assert_eq!(rust.blocks.cxx_mod_contents.len(), 3);
assert_eq!(rust.blocks.cxx_mod_contents.len(), 4);
assert_tokens_eq(
&rust.blocks.cxx_mod_contents[0],
quote! {
Expand Down Expand Up @@ -295,5 +301,15 @@ mod tests {
}
},
);
assert_tokens_eq(
&rust.blocks.cxx_mod_contents[3],
quote! {
extern "Rust" {
#[cxx_name = "createRs"]
#[namespace = "cxx_qt::cxx_qt_my_object"]
fn create_rs_my_object() -> Box<MyObject>;
}
},
);
}
}
Loading

0 comments on commit 6794073

Please sign in to comment.