Skip to content

Commit

Permalink
generator: add has_unsafe_cxx_type for Rust generation
Browse files Browse the repository at this point in the history
Certain types such as *mut T pointers require that the CXX
functions are marked as unsafe. This is required for nested
objects to work.
  • Loading branch information
ahayzen-kdab authored and Be-ing committed Dec 17, 2022
1 parent f9e8cb5 commit f0daf13
Show file tree
Hide file tree
Showing 6 changed files with 277 additions and 17 deletions.
74 changes: 67 additions & 7 deletions crates/cxx-qt-gen/src/generator/rust/invokable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@
use crate::{
generator::{
naming::{invokable::QInvokableName, qobject::QObjectName},
rust::{fragment::RustFragmentPair, qobject::GeneratedRustQObjectBlocks},
rust::{
fragment::RustFragmentPair, qobject::GeneratedRustQObjectBlocks,
types::is_unsafe_cxx_type,
},
},
parser::invokable::ParsedQInvokable,
};
use proc_macro2::TokenStream;
use quote::quote;
use syn::{Ident, Result};
use syn::{Ident, Result, ReturnType};

pub fn generate_rust_invokables(
invokables: &Vec<ParsedQInvokable>,
Expand Down Expand Up @@ -54,11 +57,26 @@ pub fn generate_rust_invokables(
quote! { self: #rust_struct, cpp: #cpp_struct, #(#parameters),* }
};
let return_type = &invokable.method.sig.output;
let has_return = if matches!(invokable.method.sig.output, syn::ReturnType::Default) {
let has_return = if matches!(invokable.method.sig.output, ReturnType::Default) {
quote! {}
} else {
quote! { return }
};
// Determine if unsafe is required due to an unsafe parameter or return type
let has_unsafe_param = invokable
.parameters
.iter()
.any(|parameter| is_unsafe_cxx_type(&parameter.ty));
let has_unsafe_return = if let ReturnType::Type(_, ty) = return_type {
is_unsafe_cxx_type(ty)
} else {
false
};
let has_unsafe = if has_unsafe_param || has_unsafe_return {
quote! { unsafe }
} else {
quote! {}
};
let parameter_names = invokable
.parameters
.iter()
Expand All @@ -69,14 +87,14 @@ pub fn generate_rust_invokables(
cxx_bridge: vec![quote! {
extern "Rust" {
#[cxx_name = #wrapper_ident_cpp]
fn #wrapper_ident_rust(#parameter_signatures) #return_type;
#has_unsafe fn #wrapper_ident_rust(#parameter_signatures) #return_type;
}
}],
implementation: vec![
// TODO: not all methods have a wrapper
quote! {
impl #rust_struct_name_rust {
pub fn #wrapper_ident_rust(#parameter_signatures) #return_type {
pub #has_unsafe fn #wrapper_ident_rust(#parameter_signatures) #return_type {
#has_return cpp.#invokable_ident_rust(#(#parameter_names),*);
}
}
Expand Down Expand Up @@ -144,13 +162,26 @@ mod tests {
return_cxx_type: Some("QColor".to_owned()),
specifiers: HashSet::new(),
},
ParsedQInvokable {
method: tokens_to_syn(
quote! { fn unsafe_invokable(&self, param: *mut T) -> *mut T {} },
),
mutable: false,
parameters: vec![ParsedFunctionParameter {
ident: format_ident!("param"),
ty: tokens_to_syn::<syn::Type>(quote! { *mut T }),
cxx_type: None,
}],
return_cxx_type: None,
specifiers: HashSet::new(),
},
];
let qobject_idents = create_qobjectname();

let generated = generate_rust_invokables(&invokables, &qobject_idents).unwrap();

assert_eq!(generated.cxx_mod_contents.len(), 3);
assert_eq!(generated.cxx_qt_mod_contents.len(), 6);
assert_eq!(generated.cxx_mod_contents.len(), 4);
assert_eq!(generated.cxx_qt_mod_contents.len(), 8);

// void_invokable
assert_tokens_eq(
Expand Down Expand Up @@ -238,5 +269,34 @@ mod tests {
}
},
);

// unsafe_invokable
assert_tokens_eq(
&generated.cxx_mod_contents[3],
quote! {
extern "Rust" {
#[cxx_name = "unsafeInvokableWrapper"]
unsafe fn unsafe_invokable_wrapper(self: &MyObject, cpp: &MyObjectQt, param: *mut T) -> *mut T;
}
},
);
assert_tokens_eq(
&generated.cxx_qt_mod_contents[6],
quote! {
impl MyObject {
pub unsafe fn unsafe_invokable_wrapper(self: &MyObject, cpp: &MyObjectQt, param: *mut T) -> *mut T {
return cpp.unsafe_invokable(param);
}
}
},
);
assert_tokens_eq(
&generated.cxx_qt_mod_contents[7],
quote! {
impl MyObjectQt {
fn unsafe_invokable(&self, param: *mut T) -> *mut T {}
}
},
);
}
}
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 @@ -9,6 +9,7 @@ pub mod invokable;
pub mod property;
pub mod qobject;
pub mod signals;
pub mod types;

use crate::generator::rust::qobject::GeneratedRustQObject;
use crate::parser::Parser;
Expand Down
98 changes: 96 additions & 2 deletions crates/cxx-qt-gen/src/generator/rust/property/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,20 @@ mod tests {
vis: tokens_to_syn::<syn::Visibility>(quote! { pub }),
cxx_type: Some("QColor".to_owned()),
},
ParsedQProperty {
ident: format_ident!("unsafe_property"),
ty: tokens_to_syn::<syn::Type>(quote! { *mut T }),
vis: syn::Visibility::Inherited,
cxx_type: None,
},
];
let qobject_idents = create_qobjectname();

let generated = generate_rust_properties(&properties, &qobject_idents).unwrap();

// Check that we have the expected number of blocks
assert_eq!(generated.cxx_mod_contents.len(), 6);
assert_eq!(generated.cxx_qt_mod_contents.len(), 10);
assert_eq!(generated.cxx_mod_contents.len(), 9);
assert_eq!(generated.cxx_qt_mod_contents.len(), 15);

// Trivial Property

Expand Down Expand Up @@ -262,5 +268,93 @@ mod tests {
}
})
);

// Unsafe Property

// Getter
assert_eq!(
generated.cxx_mod_contents[6],
tokens_to_syn::<syn::Item>(quote! {
extern "Rust" {
#[cxx_name = "getUnsafeProperty"]
unsafe fn unsafe_property<'a>(self: &'a MyObject, cpp: &'a MyObjectQt) -> &'a *mut T;
}
})
);
assert_eq!(
generated.cxx_qt_mod_contents[10],
tokens_to_syn::<syn::Item>(quote! {
impl MyObject {
pub fn unsafe_property<'a>(&'a self, cpp: &'a MyObjectQt) -> &'a *mut T {
cpp.unsafe_property()
}
}
})
);
assert_eq!(
generated.cxx_qt_mod_contents[11],
tokens_to_syn::<syn::Item>(quote! {
impl MyObjectQt {
pub fn unsafe_property(&self) -> &*mut T {
&self.rust().unsafe_property
}
}
})
);
assert_eq!(
generated.cxx_qt_mod_contents[12],
tokens_to_syn::<syn::Item>(quote! {
impl MyObjectQt {
pub unsafe fn unsafe_property_mut<'a>(mut self: Pin<&'a mut Self>) -> &'a mut *mut T {
&mut self.rust_mut().get_unchecked_mut().unsafe_property
}
}
})
);

// Setters
assert_eq!(
generated.cxx_mod_contents[7],
tokens_to_syn::<syn::Item>(quote! {
extern "Rust" {
#[cxx_name = "setUnsafeProperty"]
unsafe fn set_unsafe_property(self: &mut MyObject, cpp: Pin<&mut MyObjectQt>, value: *mut T);
}
})
);
assert_eq!(
generated.cxx_qt_mod_contents[13],
tokens_to_syn::<syn::Item>(quote! {
impl MyObject {
pub fn set_unsafe_property(&mut self, cpp: Pin<&mut MyObjectQt>, value: *mut T) {
cpp.set_unsafe_property(value);
}
}
})
);
assert_eq!(
generated.cxx_qt_mod_contents[14],
tokens_to_syn::<syn::Item>(quote! {
impl MyObjectQt {
pub fn set_unsafe_property(mut self: Pin<&mut Self>, value: *mut T) {
unsafe {
self.as_mut().rust_mut().unsafe_property = value;
}
self.as_mut().unsafe_property_changed();
}
}
})
);

// Notify
assert_eq!(
generated.cxx_mod_contents[8],
tokens_to_syn::<syn::Item>(quote! {
unsafe extern "C++" {
#[rust_name = "unsafe_property_changed"]
fn unsafePropertyChanged(self: Pin<&mut MyObjectQt>);
}
})
);
}
}
11 changes: 9 additions & 2 deletions crates/cxx-qt-gen/src/generator/rust/property/setter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

use crate::generator::{
naming::{property::QPropertyName, qobject::QObjectName},
rust::fragment::RustFragmentPair,
rust::{fragment::RustFragmentPair, types::is_unsafe_cxx_type},
};
use quote::quote;
use syn::Type;
Expand All @@ -22,11 +22,18 @@ pub fn generate(
let ident = &idents.name.rust;
let notify_ident = &idents.notify.rust;

// Determine if unsafe is required due to an unsafe type
let has_unsafe = if is_unsafe_cxx_type(ty) {
quote! { unsafe }
} else {
quote! {}
};

RustFragmentPair {
cxx_bridge: vec![quote! {
extern "Rust" {
#[cxx_name = #setter_cpp]
fn #setter_rust(self: &mut #rust_struct_name_rust, cpp: Pin<&mut #cpp_class_name_rust>, value: #ty);
#has_unsafe fn #setter_rust(self: &mut #rust_struct_name_rust, cpp: Pin<&mut #cpp_class_name_rust>, value: #ty);
}
}],
implementation: vec![
Expand Down
44 changes: 38 additions & 6 deletions crates/cxx-qt-gen/src/generator/rust/signals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
use crate::{
generator::{
naming::{qobject::QObjectName, signals::QSignalName},
rust::{fragment::RustFragmentPair, qobject::GeneratedRustQObjectBlocks},
rust::{
fragment::RustFragmentPair, qobject::GeneratedRustQObjectBlocks,
types::is_unsafe_cxx_type,
},
},
parser::signals::ParsedSignalsEnum,
};
Expand Down Expand Up @@ -62,17 +65,28 @@ pub fn generate_rust_signals(
.map(|parameter| parameter.ident.clone())
.collect::<Vec<Ident>>();

// Determine if unsafe is required due to an unsafe parameter
let has_unsafe = if signal
.parameters
.iter()
.any(|parameter| is_unsafe_cxx_type(&parameter.ty))
{
quote! { unsafe }
} else {
quote! {}
};

let fragment = RustFragmentPair {
cxx_bridge: vec![quote! {
unsafe extern "C++" {
#[rust_name = #emit_ident_rust_str]
fn #emit_ident_cpp(#parameter_signatures);
#has_unsafe fn #emit_ident_cpp(#parameter_signatures);
}
}],
implementation: vec![],
};
signal_matches.push(quote! {
#signal_enum_ident::#signal_ident_rust { #(#parameter_names),* } => self.#emit_ident_rust(#(#parameter_names),*)
#signal_enum_ident::#signal_ident_rust { #(#parameter_names),* } => #has_unsafe { self.#emit_ident_rust(#(#parameter_names),*) }
});

generated
Expand Down Expand Up @@ -119,14 +133,17 @@ mod tests {
#[cxx_type = "QColor"]
opaque: UniquePtr<QColor>
},
UnsafeSignal {
param: *mut T,
},
}
});
let signals_enum = ParsedSignalsEnum::from(&e, 0).unwrap();
let qobject_idents = create_qobjectname();

let generated = generate_rust_signals(&signals_enum, &qobject_idents).unwrap();

assert_eq!(generated.cxx_mod_contents.len(), 2);
assert_eq!(generated.cxx_mod_contents.len(), 3);
assert_eq!(generated.cxx_qt_mod_contents.len(), 2);

// Ready
Expand All @@ -151,6 +168,17 @@ mod tests {
},
);

// UnsafeSignal
assert_tokens_eq(
&generated.cxx_mod_contents[2],
quote! {
unsafe extern "C++" {
#[rust_name = "emit_unsafe_signal"]
unsafe fn emitUnsafeSignal(self: Pin<&mut MyObjectQt>, param: *mut T);
}
},
);

// enum
assert_tokens_eq(
&generated.cxx_qt_mod_contents[0],
Expand All @@ -161,6 +189,9 @@ mod tests {
trivial: i32,
opaque: UniquePtr<QColor>
},
UnsafeSignal {
param: *mut T,
},
}
},
);
Expand All @@ -170,8 +201,9 @@ mod tests {
impl MyObjectQt {
pub fn emit(self: Pin<&mut Self>, signal: MySignals) {
match signal {
MySignals::Ready {} => self.emit_ready(),
MySignals::DataChanged { trivial, opaque } => self.emit_data_changed(trivial, opaque)
MySignals::Ready {} => { self.emit_ready() },
MySignals::DataChanged { trivial, opaque } => { self.emit_data_changed(trivial, opaque) },
MySignals::UnsafeSignal { param } => unsafe { self.emit_unsafe_signal(param) }
}
}
}
Expand Down
Loading

0 comments on commit f0daf13

Please sign in to comment.