Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions book/src/bridge/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,8 @@ For [`#[qproperty]`](./extern_rustqt.md#properties), a CXX or Rust name can be p
The `#[auto_cxx_name]` and `#[auto_rust_name]` attributes can be used to automatically rename cxx and rust names.
These are placed at a block level on `extern "RustQt"` or `extern "C++Qt"` blocks, and will automatically case convert the items inside, unless they specify either a `rust_name` or `cxx_name`.
By default `#[auto_cxx_name]` will generate a camelCase conversion for`cxx_name` and `#[auto_rust_name]` will generate a snake_case conversion for `rust_name`.

### Automatic wrapping

A fairly common operation is calling a method on the inner rust type, via the `.rust()` accessor. This can be simplified
with the `#[auto_wrap]` attribute. This will generate a wrapper for your function which accesses the rust method of that name.
8 changes: 8 additions & 0 deletions crates/cxx-qt-gen/src/generator/naming/qobject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,14 @@ pub mod tests {
QObjectNames::from_qobject(&create_parsed_qobject(), &TypeNames::mock()).unwrap()
}

/// Used for opaqure objects mocking, adding the QColor type
pub fn create_qobjectname_with_qcolor() -> QObjectNames {
let mut type_names = TypeNames::mock();
type_names.mock_insert("QColor", Some(format_ident!("qobject")), None, None);

QObjectNames::from_qobject(&create_parsed_qobject(), &type_names).unwrap()
}

#[test]
fn test_parsed_property() {
let names =
Expand Down
158 changes: 133 additions & 25 deletions crates/cxx-qt-gen/src/generator/rust/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,22 @@
//
// SPDX-License-Identifier: MIT OR Apache-2.0

use crate::generator::rust::get_params_tokens;
use crate::generator::rust::{
get_call_params_tokens, get_params_tokens, get_params_tokens_qualified,
};
use crate::naming::rust::syn_type_cxx_bridge_to_qualified;
use crate::naming::TypeNames;
use crate::{
generator::{naming::qobject::QObjectNames, rust::fragment::GeneratedRustFragment},
parser::method::ParsedMethod,
};
use quote::quote;
use syn::{parse_quote_spanned, spanned::Spanned, Result};
use syn::{parse_quote_spanned, spanned::Spanned, Item, Result, ReturnType};

pub fn generate_rust_methods(
invokables: &[&ParsedMethod],
qobject_names: &QObjectNames,
type_names: &TypeNames,
) -> Result<GeneratedRustFragment> {
let cpp_class_name_rust = &qobject_names.name.rust_unqualified();

Expand All @@ -34,6 +39,7 @@ pub fn generate_rust_methods(
let return_type = &invokable.method.sig.output;

let cfgs = &invokable.cfgs;

let cxx_namespace = qobject_names.namespace_tokens();

let (block_type, block_safety) = if invokable.is_pure {
Expand All @@ -54,35 +60,101 @@ pub fn generate_rust_methods(
Some(quote! { unsafe })
};

GeneratedRustFragment::from_cxx_item(parse_quote_spanned! {
invokable.method.span() =>
// Note: extern "Rust" block does not need to be unsafe
#block_safety extern #block_type {
// Note that we are exposing a Rust method on the C++ type to C++
//
// CXX ends up generating the source, then we generate the matching header.
#[cxx_name = #invokable_ident_cpp]
// Needed for QObjects to have a namespace on their type or extern block
//
// A Namespace from cxx_qt::bridge would be automatically applied to all children
// but to apply it to only certain types, it is needed here too
#cxx_namespace
#(#cfgs)*
#[doc(hidden)]
#unsafe_call fn #invokable_ident_rust(#parameter_signatures) #return_type;
}
let wrapper_fn = if invokable.wrap {
vec![generate_auto_wrap_fn(type_names, qobject_names, invokable)?]
} else {
vec![]
};

Ok(GeneratedRustFragment {
cxx_mod_contents: vec![parse_quote_spanned! {
invokable.method.span() =>
// Note: extern "Rust" block does not need to be unsafe
#block_safety extern #block_type {
// Note that we are exposing a Rust method on the C++ type to C++
//
// CXX ends up generating the source, then we generate the matching header.
#[cxx_name = #invokable_ident_cpp]
// Needed for QObjects to have a namespace on their type or extern block
//
// A Namespace from cxx_qt::bridge would be automatically applied to all children
// but to apply it to only certain types, it is needed here too
#cxx_namespace
#(#cfgs)*
#[doc(hidden)]
#unsafe_call fn #invokable_ident_rust(#parameter_signatures) #return_type;
}
}],
cxx_qt_mod_contents: wrapper_fn,
})
})
.collect::<Vec<_>>();
.collect::<Result<Vec<_>>>()?;

Ok(GeneratedRustFragment::flatten(generated))
}

pub fn generate_auto_wrap_fn(
type_names: &TypeNames,
qobject_names: &QObjectNames,
invokable: &ParsedMethod,
) -> Result<Item> {
let docs = &invokable.docs;
let cfgs = &invokable.cfgs;

let qualified_impl = type_names.rust_qualified(&invokable.qobject_ident)?;

let invokable_ident_rust = invokable.name.rust_unqualified();

let inner_fn = if invokable.mutable {
quote! { rust_mut() }
} else {
quote! { rust() }
};

let qualified_return_type = match &invokable.method.sig.output {
ReturnType::Default => ReturnType::Default,
ReturnType::Type(arrow, boxed_type) => {
let ty = boxed_type.as_ref();
let qualified_type = syn_type_cxx_bridge_to_qualified(ty, type_names)?;
ReturnType::Type(*arrow, Box::new(qualified_type))
}
};

let parameter_signatures_qualified = get_params_tokens_qualified(
invokable.mutable,
&invokable.parameters,
&qobject_names.name.rust_qualified(),
type_names,
)?;

let call_parameters = get_call_params_tokens(&invokable.parameters);

let method_safety = if invokable.safe {
None
} else {
Some(quote! { unsafe })
};

Ok(parse_quote_spanned! {
invokable.method.span() =>
#(#cfgs)*
impl #qualified_impl {
#(#docs)*
pub #method_safety fn #invokable_ident_rust(#parameter_signatures_qualified) #qualified_return_type {
use ::cxx_qt::CxxQtType;

self.#inner_fn.#invokable_ident_rust(#call_parameters)
}
}
})
}

#[cfg(test)]
mod tests {
use super::*;
use quote::format_ident;

use crate::generator::naming::qobject::tests::create_qobjectname;
use crate::generator::naming::qobject::tests::create_qobjectname_with_qcolor;
use crate::tests::assert_tokens_eq;
use syn::{parse_quote, ForeignItemFn};

Expand All @@ -98,10 +170,12 @@ mod tests {
};
let method3: ForeignItemFn = parse_quote! {
#[cxx_name = "opaqueInvokable"]
#[auto_wrap]
fn opaque_invokable(self: Pin<&mut MyObject>, param: &QColor) -> UniquePtr<QColor>;
};
let method4: ForeignItemFn = parse_quote! {
#[cxx_name = "unsafeInvokable"]
#[auto_wrap]
unsafe fn unsafe_invokable(self: &MyObject, param: *mut T) -> *mut T;
};
let invokables = vec![
Expand All @@ -110,13 +184,21 @@ mod tests {
ParsedMethod::mock_qinvokable(&method3).make_mutable(),
ParsedMethod::mock_qinvokable(&method4).make_unsafe(),
];
let qobject_names = create_qobjectname();
let qobject_names = create_qobjectname_with_qcolor();

let mut type_names = TypeNames::mock();
type_names.mock_insert("QColor", Some(format_ident!("qobject")), None, None);
type_names.mock_insert("T", Some(format_ident!("qobject")), None, None);

let generated =
generate_rust_methods(&invokables.iter().collect::<Vec<_>>(), &qobject_names).unwrap();
let generated = generate_rust_methods(
&invokables.iter().collect::<Vec<_>>(),
&qobject_names,
&type_names,
)
.unwrap();

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

// void_invokable
assert_tokens_eq(
Expand Down Expand Up @@ -154,6 +236,19 @@ mod tests {
},
);

assert_tokens_eq(
&generated.cxx_qt_mod_contents[0],
quote! {
impl qobject::MyObject {
pub fn opaque_invokable(self: Pin<&mut qobject::MyObject>, param: &qobject::QColor) -> cxx::UniquePtr<qobject::QColor> {
use ::cxx_qt::CxxQtType;

self.rust_mut().opaque_invokable(param)
}
}
},
);

// unsafe_invokable
assert_tokens_eq(
&generated.cxx_mod_contents[3],
Expand All @@ -165,5 +260,18 @@ mod tests {
}
},
);

assert_tokens_eq(
&generated.cxx_qt_mod_contents[1],
quote! {
impl qobject::MyObject {
pub unsafe fn unsafe_invokable(self:&qobject::MyObject, param: *mut qobject::T) -> *mut qobject::T {
use ::cxx_qt::CxxQtType;

self.rust().unsafe_invokable(param)
}
}
},
);
}
}
47 changes: 46 additions & 1 deletion crates/cxx-qt-gen/src/generator/rust/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ pub mod signals;
pub mod threading;

use crate::generator::{rust::fragment::GeneratedRustFragment, structuring};
use crate::naming::rust::syn_type_cxx_bridge_to_qualified;
use crate::naming::TypeNames;
use crate::parser::cxxqtdata::ParsedCxxQtData;
use crate::parser::{parameter::ParsedFunctionParameter, Parser};
use proc_macro2::{Ident, TokenStream};
use quote::quote;
use syn::{parse_quote, ItemMod, Result};
use syn::{parse_quote, ItemMod, Path, Result};

/// Representation of the generated Rust code for a QObject
pub struct GeneratedRustBlocks {
Expand Down Expand Up @@ -148,6 +150,49 @@ pub fn get_params_tokens(
}
}

/// Return the [TokenStream] of the parsed parameters, with self type qualified for use in generation
pub fn get_params_tokens_qualified(
mutable: bool,
parameters: &[ParsedFunctionParameter],
class_name_path: &Path,
type_names: &TypeNames,
) -> Result<TokenStream> {
let struct_sig = if mutable {
quote! { Pin<&mut #class_name_path> }
} else {
quote! { &#class_name_path }
};
if parameters.is_empty() {
Ok(quote! { self: #struct_sig })
} else {
let parameters = parameters
.iter()
.map(|parameter| {
let ident = &parameter.ident;
let qualified_ty = &syn_type_cxx_bridge_to_qualified(&parameter.ty, type_names)?;
Ok(quote! { #ident: #qualified_ty })
})
.collect::<Result<Vec<TokenStream>>>()?;
Ok(quote! { self: #struct_sig, #(#parameters),* })
}
}

/// Return the [TokenStream] of the parsed parameters, which would be used to call the fn, for use in generation
pub fn get_call_params_tokens(parameters: &[ParsedFunctionParameter]) -> TokenStream {
if parameters.is_empty() {
quote! {}
} else {
let parameters = parameters
.iter()
.map(|parameter| {
let ident = &parameter.ident;
quote! { #ident }
})
.collect::<Vec<TokenStream>>();
quote! { #(#parameters),* }
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
2 changes: 1 addition & 1 deletion crates/cxx-qt-gen/src/generator/rust/qobject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ impl GeneratedRustFragment {
type_names,
structured_qobject,
)?,
generate_rust_methods(&structured_qobject.methods, &qobject_names)?,
generate_rust_methods(&structured_qobject.methods, &qobject_names, type_names)?,
inherit::generate(&qobject_names, &structured_qobject.inherited_methods)?,
generate_rust_signals(&structured_qobject.signals, &qobject_names, type_names)?,
];
Expand Down
15 changes: 11 additions & 4 deletions crates/cxx-qt-gen/src/parser/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-FileContributor: Andrew Hayzen <[email protected]>
//
// SPDX-License-Identifier: MIT OR Apache-2.0
use crate::parser::CaseConversion;
use crate::parser::{extract_docs, CaseConversion};
use crate::{
naming::Name,
parser::{extract_cfgs, parameter::ParsedFunctionParameter, require_attributes},
Expand Down Expand Up @@ -59,23 +59,26 @@ pub struct ParsedMethod {
pub is_qinvokable: bool,
/// Whether the method is a pure virtual method
pub is_pure: bool,
// No docs field since the docs should be on the method implementation outside the bridge
// This means any docs on the bridge declaration would be ignored
/// Whether to auto generate a wrapper for this method outside the bridge
pub wrap: bool,
/// Cfgs for the method
pub cfgs: Vec<Attribute>,
/// Docs for the method, for passing onto the auto_wrap generated methods
pub docs: Vec<Attribute>,
/// Whether the block containing the method is safe or unsafe
pub unsafe_block: bool,
}

impl ParsedMethod {
const ALLOWED_ATTRS: [&'static str; 9] = [
const ALLOWED_ATTRS: [&'static str; 10] = [
"cxx_name",
"rust_name",
"qinvokable",
"cxx_final",
"cxx_override",
"cxx_virtual",
"cxx_pure",
"auto_wrap",
"doc",
"cfg",
];
Expand Down Expand Up @@ -123,18 +126,22 @@ impl ParsedMethod {
let fields = MethodFields::parse(method, auto_case)?;
let attrs = require_attributes(&fields.method.attrs, &Self::ALLOWED_ATTRS)?;
let cfgs = extract_cfgs(&fields.method.attrs);
let docs = extract_docs(&fields.method.attrs);

// Determine if the method is invokable
let is_qinvokable = attrs.contains_key("qinvokable");
let is_pure = attrs.contains_key("cxx_pure");
let wrap = attrs.contains_key("auto_wrap");
let specifiers = ParsedQInvokableSpecifiers::from_attrs(attrs);

Ok(Self {
method_fields: fields,
specifiers,
is_qinvokable,
is_pure,
wrap,
cfgs,
docs,
unsafe_block,
})
}
Expand Down
Loading
Loading