-
Notifications
You must be signed in to change notification settings - Fork 77
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
cxx_qt::Constructor: Add Rust generation
- Loading branch information
1 parent
2793fc8
commit 6794073
Showing
9 changed files
with
470 additions
and
129 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.