Skip to content

Add shorthand for &self in RustQt blocks where the type can be inferred #1249

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
7 changes: 7 additions & 0 deletions book/src/getting-started/2-our-first-cxx-qt-module.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,13 @@ These functions then need to be implemented **outside** the bridge using `impl q
{{#include ../../../examples/qml_minimal/rust/src/cxxqt_object.rs:book_rustobj_invokable_impl}}
```

### Inlining the self receiver

If an `extern "RustQt"` block contains exactly one `QObject`, the self type of methods can be inferred.
For instance, in a block with multiple or no `QObject`s, a function like `foo(&self)` or `foo(self: Pin<&mut Self>)`
would not compile, but will compile with the `Self` type set to that blocks `QObject`.
This is how CXX [handles it](https://cxx.rs/extern-rust.html) (see the Methods heading).

This setup is a bit unusual, as the type `qobject::MyObject` is actually defined in C++.
However, it is still possible to add member functions to it in Rust and then expose them back to C++.
This is the usual workflow for `QObject`s in CXX-Qt.
Expand Down
9 changes: 6 additions & 3 deletions crates/cxx-qt-build/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ use std::{
};

use cxx_qt_gen::{
parse_qt_file, write_cpp, write_rust, CppFragment, CxxQtItem, GeneratedCppBlocks, GeneratedOpt,
GeneratedRustBlocks, Parser,
parse_qt_file, qualify_self_types, write_cpp, write_rust, CppFragment, CxxQtItem,
GeneratedCppBlocks, GeneratedOpt, GeneratedRustBlocks, Parser,
};

// TODO: we need to eventually support having multiple modules defined in a single file. This
Expand Down Expand Up @@ -132,7 +132,10 @@ impl GeneratedCpp {
}
found_bridge = true;

let parser = Parser::from(m.clone())
let mut parser = Parser::from(m.clone())
.map_err(GeneratedError::from)
.map_err(to_diagnostic)?;
qualify_self_types(&mut parser)
.map_err(GeneratedError::from)
.map_err(to_diagnostic)?;
let generated_cpp = GeneratedCppBlocks::from(&parser, &cxx_qt_opt)
Expand Down
3 changes: 1 addition & 2 deletions crates/cxx-qt-gen/src/generator/rust/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,7 @@ impl GeneratedRustBlocks {
// Generate a type declaration for `QObject` if necessary
fn add_qobject_import(cxx_qt_data: &ParsedCxxQtData) -> Option<GeneratedRustFragment> {
let includes = cxx_qt_data
.qobjects
.iter()
.qobjects()
.any(|obj| obj.has_qobject_macro && obj.base_class.is_none());
if includes
|| cxx_qt_data
Expand Down
9 changes: 4 additions & 5 deletions crates/cxx-qt-gen/src/generator/structuring/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,7 @@ impl<'a> Structures<'a> {
/// Returns an error, if any references could not be resolved.
pub fn new(cxxqtdata: &'a ParsedCxxQtData) -> Result<Self> {
let mut qobjects: Vec<_> = cxxqtdata
.qobjects
.iter()
.qobjects()
.map(StructuredQObject::from_qobject)
.collect();

Expand All @@ -92,19 +91,19 @@ impl<'a> Structures<'a> {
}

// Associate each method parsed with its appropriate qobject
for method in &cxxqtdata.methods {
for method in cxxqtdata.methods() {
let qobject = find_qobject(&mut qobjects, &method.qobject_ident)?;
qobject.methods.push(method);
}

// Associate each inherited method parsed with its appropriate qobject
for inherited_method in &cxxqtdata.inherited_methods {
for inherited_method in cxxqtdata.inherited_methods() {
let qobject = find_qobject(&mut qobjects, &inherited_method.qobject_ident)?;
qobject.inherited_methods.push(inherited_method);
}

// Associate each signal parsed with its appropriate qobject
for signal in &cxxqtdata.signals {
for signal in cxxqtdata.signals() {
let qobject = find_qobject(&mut qobjects, &signal.qobject_ident)?;
qobject.signals.push(signal);
}
Expand Down
5 changes: 4 additions & 1 deletion crates/cxx-qt-gen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
mod generator;
mod naming;
mod parser;
mod preprocessor;
mod syntax;
mod writer;

Expand All @@ -20,6 +21,7 @@ pub use generator::{
GeneratedOpt,
};
pub use parser::Parser;
pub use preprocessor::self_inlining::qualify_self_types;
pub use syntax::{parse_qt_file, CxxQtFile, CxxQtItem};
pub use writer::{cpp::write_cpp, rust::write_rust};

Expand Down Expand Up @@ -174,7 +176,8 @@ mod tests {
expected_cpp_header: &str,
expected_cpp_source: &str,
) {
let parser = Parser::from(syn::parse_str(input).unwrap()).unwrap();
let mut parser = Parser::from(syn::parse_str(input).unwrap()).unwrap();
qualify_self_types(&mut parser).unwrap();

let mut cfg_evaluator = CfgEvaluatorTest::default();
cfg_evaluator.cfgs.insert("crate", Some("cxx-qt-gen"));
Expand Down
2 changes: 1 addition & 1 deletion crates/cxx-qt-gen/src/naming/name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use syn::{spanned::Spanned, Attribute, Error, Ident, Path, Result};
/// Naming in CXX can be rather complex.
/// The following Rules apply:
/// - If only a cxx_name **or** a rust_name is given, the identifier of the type/function will be
/// used for part that wasn't specified explicitly.
/// used for part that wasn't specified explicitly.
/// - If **both** attributes are present, the identifier itself is not used!
/// - The `rust_name` is always used to refer to the type within the bridge!.
#[derive(Clone, Debug, PartialEq, Eq)]
Expand Down
2 changes: 1 addition & 1 deletion crates/cxx-qt-gen/src/naming/type_names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ impl TypeNames {
module_ident: &Ident,
) -> Result<()> {
// Find and register the QObjects in the bridge
for qobject in cxx_qt_data.qobjects.iter() {
for qobject in cxx_qt_data.qobjects() {
self.populate_qobject(qobject)?;
}

Expand Down
101 changes: 68 additions & 33 deletions crates/cxx-qt-gen/src/parser/cxxqtdata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,16 @@ use syn::{
};

pub struct ParsedCxxQtData {
/// Map of the QObjects defined in the module that will be used for code generation
//
// We have to use a BTreeMap here, instead of a HashMap, to keep the order of QObjects stable.
// Otherwise, the output order would be different, depending on the environment, which makes it hard to test/debug.
pub qobjects: Vec<ParsedQObject>,
/// List of QObjects defined in the module, separated by block
pub qobjects: Vec<Vec<ParsedQObject>>,
/// List of QEnums defined in the module, that aren't associated with a QObject
pub qenums: Vec<ParsedQEnum>,
/// List of methods and Q_INVOKABLES found
pub methods: Vec<ParsedMethod>,
/// List of the Q_SIGNALS found
pub signals: Vec<ParsedSignal>,
/// List of the inherited methods found
pub inherited_methods: Vec<ParsedInheritedMethod>,
/// List of methods and Q_INVOKABLES found, separated by block
pub methods: Vec<Vec<ParsedMethod>>,
/// List of the Q_SIGNALS found, separated by block
pub signals: Vec<Vec<ParsedSignal>>,
/// List of the inherited methods found, separated by block
pub inherited_methods: Vec<Vec<ParsedInheritedMethod>>,
/// List of QNamespace declarations
pub qnamespaces: Vec<ParsedQNamespace>,
/// Blocks of extern "C++Qt"
Expand All @@ -48,6 +45,11 @@ pub struct ParsedCxxQtData {
pub module_ident: Ident,
}

/// Used to get a flat iterator view into a 2D Vector
fn flat_view<T>(v: &[Vec<T>]) -> impl Iterator<Item = &T> {
v.iter().flat_map(|v| v.iter())
}

impl ParsedCxxQtData {
/// Create a ParsedCxxQtData from a given module and namespace
pub fn new(module_ident: Ident, namespace: Option<String>) -> Self {
Expand All @@ -65,6 +67,26 @@ impl ParsedCxxQtData {
}
}

/// Iterator wrapper for methods
pub fn methods(&self) -> impl Iterator<Item = &ParsedMethod> {
flat_view(&self.methods)
}

/// Iterator wrapper for signals
pub fn signals(&self) -> impl Iterator<Item = &ParsedSignal> {
flat_view(&self.signals)
}

/// Iterator wrapper for inherited methods
pub fn inherited_methods(&self) -> impl Iterator<Item = &ParsedInheritedMethod> {
flat_view(&self.inherited_methods)
}

/// Iterator wrapper for QObjects
pub fn qobjects(&self) -> impl Iterator<Item = &ParsedQObject> {
flat_view(&self.qobjects)
}

/// Determine if the given [syn::Item] is a CXX-Qt related item
/// If it is then add the [syn::Item] into qobjects BTreeMap
/// Otherwise return the [syn::Item] to pass through to CXX
Expand Down Expand Up @@ -139,6 +161,12 @@ impl ParsedCxxQtData {

let auto_case = CaseConversion::from_attrs(&attrs)?;

let mut qobjects = vec![];

let mut methods = vec![];
let mut signals = vec![];
let mut inherited = vec![];

let namespace = attrs
.get("namespace")
.map(|attr| expr_to_string(&attr.meta.require_name_value()?.value))
Expand All @@ -159,7 +187,7 @@ impl ParsedCxxQtData {
return Err(Error::new(foreign_fn.span(), "block must be declared `unsafe extern \"RustQt\"` if it contains any safe-to-call #[inherit] qsignals"));
}

self.signals.push(parsed_signal_method);
signals.push(parsed_signal_method);

// Test if the function is an inheritance method
//
Expand All @@ -175,15 +203,15 @@ impl ParsedCxxQtData {
let parsed_inherited_method =
ParsedInheritedMethod::parse(foreign_fn, auto_case)?;

self.inherited_methods.push(parsed_inherited_method);
inherited.push(parsed_inherited_method);
// Remaining methods are either C++ methods or invokables
} else {
let parsed_method = ParsedMethod::parse(
foreign_fn,
auto_case,
foreign_mod.unsafety.is_some(),
)?;
self.methods.push(parsed_method);
methods.push(parsed_method);
}
}
ForeignItem::Verbatim(tokens) => {
Expand All @@ -199,12 +227,18 @@ impl ParsedCxxQtData {

// Note that we assume a compiler error will occur later
// if you had two structs with the same name
self.qobjects.push(qobject);
qobjects.push(qobject);
}
// Const Macro, Type are unsupported in extern "RustQt" for now
// Const, Macro, Type are unsupported in extern "RustQt" for now
_ => return Err(err_unsupported_item(&item)),
}
}

self.qobjects.push(qobjects);
self.methods.push(methods);
self.signals.push(signals);
self.inherited_methods.push(inherited);

Ok(())
}

Expand All @@ -223,8 +257,7 @@ impl ParsedCxxQtData {

#[cfg(test)]
fn find_object(&self, id: &Ident) -> Option<&ParsedQObject> {
self.qobjects
.iter()
self.qobjects()
.find(|obj| obj.name.rust_unqualified() == id)
}
}
Expand All @@ -241,8 +274,9 @@ mod tests {
/// Creates a ParsedCxxQtData with a QObject definition already found
pub fn create_parsed_cxx_qt_data() -> ParsedCxxQtData {
let mut cxx_qt_data = ParsedCxxQtData::new(format_ident!("ffi"), None);
cxx_qt_data.qobjects.push(create_parsed_qobject());
cxx_qt_data.qobjects.push(create_parsed_qobject());
cxx_qt_data
.qobjects
.push(vec![create_parsed_qobject(), create_parsed_qobject()]);
cxx_qt_data
}

Expand Down Expand Up @@ -352,9 +386,9 @@ mod tests {
let result = cxx_qt_data.parse_cxx_qt_item(item).unwrap();
assert!(result.is_none());

assert_eq!(cxx_qt_data.methods.len(), 2);
assert!(cxx_qt_data.methods[0].is_qinvokable);
assert!(!cxx_qt_data.methods[1].is_qinvokable)
assert_eq!(cxx_qt_data.methods().collect::<Vec<_>>().len(), 2);
assert!(cxx_qt_data.methods[0][0].is_qinvokable);
assert!(!cxx_qt_data.methods[0][1].is_qinvokable)
}

#[test]
Expand Down Expand Up @@ -382,7 +416,7 @@ mod tests {
};
cxx_qt_data.parse_cxx_qt_item(item).unwrap();
assert_eq!(cxx_qt_data.methods.len(), 1);
assert_eq!(cxx_qt_data.methods[0].name.cxx_unqualified(), "fooBar");
assert_eq!(cxx_qt_data.methods[0][0].name.cxx_unqualified(), "fooBar");
}

#[test]
Expand All @@ -397,9 +431,10 @@ mod tests {
}
};
cxx_qt_data.parse_cxx_qt_item(item).unwrap();
assert_eq!(cxx_qt_data.methods.len(), 1);
assert_eq!(cxx_qt_data.methods[0].name.cxx_unqualified(), "foo_bar");
assert_eq!(cxx_qt_data.methods[0].name.rust_unqualified(), "foo_bar");
let methods = &cxx_qt_data.methods[0];
assert_eq!(methods.len(), 1);
assert_eq!(methods[0].name.cxx_unqualified(), "foo_bar");
assert_eq!(methods[0].name.rust_unqualified(), "foo_bar");
}

#[test]
Expand All @@ -414,8 +449,8 @@ mod tests {
}
};
cxx_qt_data.parse_cxx_qt_item(item).unwrap();
assert_eq!(cxx_qt_data.methods.len(), 1);
assert_eq!(cxx_qt_data.methods[0].name.cxx_unqualified(), "renamed");
assert_eq!(cxx_qt_data.methods[0].len(), 1);
assert_eq!(cxx_qt_data.methods[0][0].name.cxx_unqualified(), "renamed");
}

#[test]
Expand Down Expand Up @@ -587,7 +622,7 @@ mod tests {
}
};
cxxqtdata.parse_cxx_qt_item(block).unwrap();
let signals = &cxxqtdata.signals;
let signals = &cxxqtdata.signals().collect::<Vec<_>>();
assert_eq!(signals.len(), 2);
assert!(signals[0].mutable);
assert!(signals[1].mutable);
Expand Down Expand Up @@ -617,7 +652,7 @@ mod tests {
};
cxxqtdata.parse_cxx_qt_item(block).unwrap();

let signals = &cxxqtdata.signals;
let signals = &cxxqtdata.signals().collect::<Vec<_>>();
assert_eq!(signals.len(), 1);
assert!(signals[0].mutable);
assert!(!signals[0].safe);
Expand Down Expand Up @@ -692,7 +727,7 @@ mod tests {
};

parsed_cxxqtdata.parse_cxx_qt_item(extern_rust_qt).unwrap();
assert_eq!(parsed_cxxqtdata.qobjects.len(), 2);
assert_eq!(parsed_cxxqtdata.qobjects().collect::<Vec<_>>().len(), 2);

assert!(parsed_cxxqtdata
.find_object(&format_ident!("MyObject"))
Expand All @@ -717,7 +752,7 @@ mod tests {
};

parsed_cxxqtdata.parse_cxx_qt_item(extern_rust_qt).unwrap();
assert_eq!(parsed_cxxqtdata.qobjects.len(), 2);
assert_eq!(parsed_cxxqtdata.qobjects().collect::<Vec<_>>().len(), 2);
assert_eq!(
parsed_cxxqtdata
.find_object(&format_ident!("MyObject"))
Expand Down
Loading
Loading