diff --git a/book/src/getting-started/2-our-first-cxx-qt-module.md b/book/src/getting-started/2-our-first-cxx-qt-module.md index b718f9986..d001750bc 100644 --- a/book/src/getting-started/2-our-first-cxx-qt-module.md +++ b/book/src/getting-started/2-our-first-cxx-qt-module.md @@ -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. diff --git a/crates/cxx-qt-build/src/lib.rs b/crates/cxx-qt-build/src/lib.rs index 4da40ba5b..6522a27c7 100644 --- a/crates/cxx-qt-build/src/lib.rs +++ b/crates/cxx-qt-build/src/lib.rs @@ -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, self_inlining::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 @@ -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) diff --git a/crates/cxx-qt-gen/src/generator/rust/mod.rs b/crates/cxx-qt-gen/src/generator/rust/mod.rs index dae4216bd..acd94e05d 100644 --- a/crates/cxx-qt-gen/src/generator/rust/mod.rs +++ b/crates/cxx-qt-gen/src/generator/rust/mod.rs @@ -103,7 +103,7 @@ impl GeneratedRustBlocks { // Generate a type declaration for `QObject` if necessary fn add_qobject_import(cxx_qt_data: &ParsedCxxQtData) -> Option { let includes = cxx_qt_data - .qobjects + .qobjects() .iter() .any(|obj| obj.has_qobject_macro && obj.base_class.is_none()); if includes diff --git a/crates/cxx-qt-gen/src/generator/structuring/mod.rs b/crates/cxx-qt-gen/src/generator/structuring/mod.rs index 18313697a..b63b1af03 100644 --- a/crates/cxx-qt-gen/src/generator/structuring/mod.rs +++ b/crates/cxx-qt-gen/src/generator/structuring/mod.rs @@ -79,8 +79,8 @@ impl<'a> Structures<'a> { /// Returns an error, if any references could not be resolved. pub fn new(cxxqtdata: &'a ParsedCxxQtData) -> Result { let mut qobjects: Vec<_> = cxxqtdata - .qobjects - .iter() + .qobjects() + .into_iter() .map(StructuredQObject::from_qobject) .collect(); @@ -92,19 +92,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); } diff --git a/crates/cxx-qt-gen/src/lib.rs b/crates/cxx-qt-gen/src/lib.rs index 89e83e6b9..3b50410d5 100644 --- a/crates/cxx-qt-gen/src/lib.rs +++ b/crates/cxx-qt-gen/src/lib.rs @@ -11,6 +11,7 @@ mod generator; mod naming; mod parser; +mod shorthand; mod syntax; mod writer; @@ -20,6 +21,7 @@ pub use generator::{ GeneratedOpt, }; pub use parser::Parser; +pub use shorthand::self_inlining; pub use syntax::{parse_qt_file, CxxQtFile, CxxQtItem}; pub use writer::{cpp::write_cpp, rust::write_rust}; @@ -86,6 +88,7 @@ mod tests { $(assert!($parse_fn(syn::parse_quote! $input).is_err());)* } } + use crate::self_inlining::qualify_self_types; pub(crate) use assert_parse_errors; /// Helper for formating C++ code @@ -174,7 +177,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")); diff --git a/crates/cxx-qt-gen/src/naming/name.rs b/crates/cxx-qt-gen/src/naming/name.rs index fac11ec27..ef4bbb6a0 100644 --- a/crates/cxx-qt-gen/src/naming/name.rs +++ b/crates/cxx-qt-gen/src/naming/name.rs @@ -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)] diff --git a/crates/cxx-qt-gen/src/naming/type_names.rs b/crates/cxx-qt-gen/src/naming/type_names.rs index d0f885811..5a4630896 100644 --- a/crates/cxx-qt-gen/src/naming/type_names.rs +++ b/crates/cxx-qt-gen/src/naming/type_names.rs @@ -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)?; } diff --git a/crates/cxx-qt-gen/src/parser/cxxqtdata.rs b/crates/cxx-qt-gen/src/parser/cxxqtdata.rs index ebb572f5a..3b7793b20 100644 --- a/crates/cxx-qt-gen/src/parser/cxxqtdata.rs +++ b/crates/cxx-qt-gen/src/parser/cxxqtdata.rs @@ -5,37 +5,19 @@ use super::qnamespace::ParsedQNamespace; use super::trait_impl::TraitImpl; -use crate::naming::cpp::err_unsupported_item; -use crate::parser::CaseConversion; +use crate::parser::externrustqt::ParsedExternRustQt; use crate::{ parser::{ externcxxqt::ParsedExternCxxQt, inherit::ParsedInheritedMethod, method::ParsedMethod, - qenum::ParsedQEnum, qobject::ParsedQObject, require_attributes, signals::ParsedSignal, + qenum::ParsedQEnum, qobject::ParsedQObject, signals::ParsedSignal, }, - syntax::{ - attribute::attribute_get_path, expr::expr_to_string, foreignmod::ForeignTypeIdentAlias, - path::path_compare_str, - }, -}; -use syn::{ - spanned::Spanned, Error, ForeignItem, Ident, Item, ItemEnum, ItemForeignMod, ItemImpl, - ItemMacro, Meta, Result, + syntax::{attribute::attribute_get_path, path::path_compare_str}, }; +use syn::{Ident, Item, ItemEnum, ItemForeignMod, ItemImpl, ItemMacro, Meta, Result}; 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, /// List of QEnums defined in the module, that aren't associated with a QObject pub qenums: Vec, - /// List of methods and Q_INVOKABLES found - pub methods: Vec, - /// List of the Q_SIGNALS found - pub signals: Vec, - /// List of the inherited methods found - pub inherited_methods: Vec, /// List of QNamespace declarations pub qnamespaces: Vec, /// Blocks of extern "C++Qt" @@ -46,25 +28,49 @@ pub struct ParsedCxxQtData { pub trait_impls: Vec, /// The ident of the module, used for mappings pub module_ident: Ident, + /// All the `extern "RustQt"` blocks + pub extern_rustqt_blocks: Vec, } impl ParsedCxxQtData { /// Create a ParsedCxxQtData from a given module and namespace pub fn new(module_ident: Ident, namespace: Option) -> Self { Self { - qobjects: Vec::new(), + module_ident, + namespace, qenums: vec![], - methods: vec![], - signals: vec![], - inherited_methods: vec![], qnamespaces: vec![], trait_impls: vec![], - extern_cxxqt_blocks: Vec::::default(), - module_ident, - namespace, + extern_cxxqt_blocks: vec![], + extern_rustqt_blocks: vec![], } } + /// Flatten a vector from each rust block into one larger vector, + /// e.g. all the methods in every block. + fn flatten_rust_blocks(&self, accessor: fn(&ParsedExternRustQt) -> &Vec) -> Vec<&T> { + self.extern_rustqt_blocks + .iter() + .flat_map(accessor) + .collect() + } + + pub fn methods(&self) -> Vec<&ParsedMethod> { + self.flatten_rust_blocks(|block| &block.methods) + } + + pub fn signals(&self) -> Vec<&ParsedSignal> { + self.flatten_rust_blocks(|block| &block.signals) + } + + pub fn inherited_methods(&self) -> Vec<&ParsedInheritedMethod> { + self.flatten_rust_blocks(|block| &block.inherited_methods) + } + + pub fn qobjects(&self) -> Vec<&ParsedQObject> { + self.flatten_rust_blocks(|block| &block.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 @@ -112,7 +118,11 @@ impl ParsedCxxQtData { if let Some(lit_str) = &foreign_mod.abi.name { match lit_str.value().as_str() { "RustQt" => { - self.parse_foreign_mod_rust_qt(foreign_mod)?; + self.extern_rustqt_blocks.push(ParsedExternRustQt::parse( + foreign_mod, + &self.module_ident, + self.namespace.as_deref(), + )?); return Ok(None); } "C++Qt" => { @@ -130,84 +140,6 @@ impl ParsedCxxQtData { Ok(Some(Item::ForeignMod(foreign_mod))) } - fn parse_foreign_mod_rust_qt(&mut self, mut foreign_mod: ItemForeignMod) -> Result<()> { - // TODO: support cfg on foreign mod blocks - let attrs = require_attributes( - &foreign_mod.attrs, - &["namespace", "auto_cxx_name", "auto_rust_name"], - )?; - - let auto_case = CaseConversion::from_attrs(&attrs)?; - - let namespace = attrs - .get("namespace") - .map(|attr| expr_to_string(&attr.meta.require_name_value()?.value)) - .transpose()? - .or_else(|| self.namespace.clone()); - - for item in foreign_mod.items.drain(..) { - match item { - ForeignItem::Fn(foreign_fn) => { - // Test if the function is a signal - if attribute_get_path(&foreign_fn.attrs, &["qsignal"]).is_some() { - let parsed_signal_method = - ParsedSignal::parse(foreign_fn.clone(), auto_case)?; - if parsed_signal_method.inherit - && foreign_fn.sig.unsafety.is_none() - && foreign_mod.unsafety.is_none() - { - 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); - - // Test if the function is an inheritance method - // - // Note that we need to test for qsignal first as qsignals have their own inherit meaning - } else if attribute_get_path(&foreign_fn.attrs, &["inherit"]).is_some() { - // We need to check that any safe functions are defined inside an unsafe block - // as with inherit we cannot fully prove the implementation and we can then - // directly copy the unsafetyness into the generated extern C++ block - if foreign_fn.sig.unsafety.is_none() && foreign_mod.unsafety.is_none() { - return Err(Error::new(foreign_fn.span(), "block must be declared `unsafe extern \"RustQt\"` if it contains any safe-to-call #[inherit] functions")); - } - - let parsed_inherited_method = - ParsedInheritedMethod::parse(foreign_fn, auto_case)?; - - self.inherited_methods.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); - } - } - ForeignItem::Verbatim(tokens) => { - let foreign_alias: ForeignTypeIdentAlias = syn::parse2(tokens.clone())?; - - // Load the QObject - let qobject = ParsedQObject::parse( - foreign_alias, - namespace.as_deref(), - &self.module_ident, - auto_case, - )?; - - // Note that we assume a compiler error will occur later - // if you had two structs with the same name - self.qobjects.push(qobject); - } - // Const Macro, Type are unsupported in extern "RustQt" for now - _ => return Err(err_unsupported_item(&item)), - } - } - Ok(()) - } - /// Parse a [syn::ItemImpl] into the qobjects if it's a CXX-Qt implementation /// otherwise return as a [syn::Item] to pass through. fn parse_impl(&mut self, imp: ItemImpl) -> Result> { @@ -223,8 +155,8 @@ impl ParsedCxxQtData { #[cfg(test)] fn find_object(&self, id: &Ident) -> Option<&ParsedQObject> { - self.qobjects - .iter() + self.qobjects() + .into_iter() .find(|obj| obj.name.rust_unqualified() == id) } } @@ -234,15 +166,20 @@ mod tests { use super::*; use crate::generator::structuring::Structures; - use crate::{naming::Name, parser::qobject::tests::create_parsed_qobject}; + use crate::parser::qobject::tests::create_parsed_qobject; use quote::format_ident; use syn::parse_quote; /// 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.extern_rustqt_blocks.push(ParsedExternRustQt { + unsafety: None, + qobjects: vec![create_parsed_qobject(), create_parsed_qobject()], + methods: vec![], + signals: vec![], + inherited_methods: vec![], + }); cxx_qt_data } @@ -253,62 +190,29 @@ mod tests { assert_parse_errors! { |item| create_parsed_cxx_qt_data().parse_cxx_qt_item(item) => + // Qenum without namespace { - // Invalid QObject - unsafe extern "RustQt" { - #[qinvokable] - fn invokable(self: &MyObject::Bad); - } - } - { - // Namespaces aren't allowed on qinvokables - unsafe extern "RustQt" { - #[qinvokable] - #[namespace = "disallowed"] - fn invokable(self: &MyObject); - } - } - { - // Block or fn must be unsafe for inherit methods - extern "RustQt" { - #[inherit] - fn invokable(self: &MyObject); - } - } - { - // Block or fn must be unsafe for inherit qsignals - extern "RustQt" { - #[inherit] - #[qsignal] - fn signal(self: Pin<&mut MyObject>); - } - } - { - // Qenum without namespace #[qenum] enum MyEnum { A, B } } + + // Unsupported name for case conversion { - // Unsupported Item - extern "RustQt" { - static COUNTER: usize; - } - } - { - // Unsupported name for case conversion #[auto_cxx_name = Foo] extern "RustQt" {} } + + // Auto case uses ident not string { - // Auto case uses ident not string #[auto_cxx_name = "Camel"] extern "RustQt" {} } + + // Unsupported format for case conversion macro { - // Unsupported format for case conversion macro #[auto_cxx_name(a, b)] extern "RustQt" {} } @@ -337,26 +241,6 @@ mod tests { assert!(result.is_some()); } - #[test] - fn test_find_and_merge_cxx_qt_item_impl_valid_qobject() { - let mut cxx_qt_data = create_parsed_cxx_qt_data(); - - let item: Item = parse_quote! { - unsafe extern "RustQt" { - #[qinvokable] - fn invokable(self: &MyObject); - - fn cpp_context(self: &MyObject); - } - }; - 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) - } - #[test] fn test_parse_unnamed_extern_mod() { let mut cxx_qt_data = create_parsed_cxx_qt_data(); @@ -381,8 +265,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(), "fooBar"); + assert_eq!(cxx_qt_data.methods().len(), 1); + assert_eq!(cxx_qt_data.methods()[0].name.cxx_unqualified(), "fooBar"); } #[test] @@ -397,9 +281,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(); + assert_eq!(methods.len(), 1); + assert_eq!(methods[0].name.cxx_unqualified(), "foo_bar"); + assert_eq!(methods[0].name.rust_unqualified(), "foo_bar"); } #[test] @@ -414,8 +299,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().len(), 1); + assert_eq!(cxx_qt_data.methods()[0].name.cxx_unqualified(), "renamed"); } #[test] @@ -505,30 +390,6 @@ mod tests { assert!(result.is_some()); } - #[test] - fn test_find_and_merge_cxx_qt_item_extern_cxx_qt() { - let mut cxx_qt_data = create_parsed_cxx_qt_data(); - - let item: Item = parse_quote! { - #[namespace = "rust"] - unsafe extern "C++Qt" { - #[qobject] - type QPushButton; - - #[qsignal] - fn clicked(self: Pin<&mut QPushButton>, checked: bool); - } - }; - let result = cxx_qt_data.parse_cxx_qt_item(item).unwrap(); - assert!(result.is_none()); - - assert_eq!(cxx_qt_data.extern_cxxqt_blocks.len(), 1); - assert!(cxx_qt_data.extern_cxxqt_blocks[0].namespace.is_some()); - assert_eq!(cxx_qt_data.extern_cxxqt_blocks[0].qobjects.len(), 1); - assert_eq!(cxx_qt_data.extern_cxxqt_blocks[0].signals.len(), 1); - assert!(cxx_qt_data.extern_cxxqt_blocks[0].unsafety.is_some()); - } - #[test] fn test_parse_inherited_methods() { let mut cxxqtdata = create_parsed_cxx_qt_data(); @@ -572,64 +433,6 @@ mod tests { assert_eq!(inherited[2].parameters[0].ident, "arg"); } - #[test] - fn test_parse_qsignals_safe() { - let mut cxxqtdata = create_parsed_cxx_qt_data(); - let block: Item = parse_quote! { - unsafe extern "RustQt" { - #[qsignal] - fn ready(self: Pin<&mut MyObject>); - - #[cxx_name="cppDataChanged"] - #[inherit] - #[qsignal] - fn data_changed(self: Pin<&mut MyObject>, data: i32); - } - }; - cxxqtdata.parse_cxx_qt_item(block).unwrap(); - let signals = &cxxqtdata.signals; - assert_eq!(signals.len(), 2); - assert!(signals[0].mutable); - assert!(signals[1].mutable); - assert!(signals[0].safe); - assert!(signals[1].safe); - assert_eq!(signals[0].parameters.len(), 0); - assert_eq!(signals[1].parameters.len(), 1); - assert_eq!(signals[1].parameters[0].ident, "data"); - assert_eq!(signals[0].name, Name::new(format_ident!("ready"))); - assert_eq!( - signals[1].name, - Name::mock_name_with_cxx("data_changed", "cppDataChanged") - ); - assert!(!signals[0].inherit); - assert!(signals[1].inherit); - } - - #[test] - fn test_parse_qsignals_unsafe() { - let mut cxxqtdata = create_parsed_cxx_qt_data(); - let block: Item = parse_quote! { - extern "RustQt" { - #[qsignal] - #[cxx_name = "unsafeSignal"] - unsafe fn unsafe_signal(self: Pin<&mut MyObject>, arg: *mut T); - } - }; - cxxqtdata.parse_cxx_qt_item(block).unwrap(); - - let signals = &cxxqtdata.signals; - assert_eq!(signals.len(), 1); - assert!(signals[0].mutable); - assert!(!signals[0].safe); - assert_eq!(signals[0].parameters.len(), 1); - assert_eq!(signals[0].parameters[0].ident, "arg"); - assert_eq!( - signals[0].name, - Name::mock_name_with_cxx("unsafe_signal", "unsafeSignal") - ); - assert!(!signals[0].inherit); - } - #[test] fn test_parse_threading() { let mut cxxqtdata = create_parsed_cxx_qt_data(); @@ -680,7 +483,7 @@ mod tests { } #[test] - fn test_qobjects() { + fn test_find_qobjects() { let mut parsed_cxxqtdata = ParsedCxxQtData::new(format_ident!("ffi"), None); let extern_rust_qt: Item = parse_quote! { extern "RustQt" { @@ -692,7 +495,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().len(), 2); assert!(parsed_cxxqtdata .find_object(&format_ident!("MyObject")) @@ -717,7 +520,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().len(), 2); assert_eq!( parsed_cxxqtdata .find_object(&format_ident!("MyObject")) diff --git a/crates/cxx-qt-gen/src/parser/externcxxqt.rs b/crates/cxx-qt-gen/src/parser/externcxxqt.rs index 2031d165e..4364c9027 100644 --- a/crates/cxx-qt-gen/src/parser/externcxxqt.rs +++ b/crates/cxx-qt-gen/src/parser/externcxxqt.rs @@ -10,7 +10,9 @@ use crate::{ }, syntax::{attribute::attribute_get_path, expr::expr_to_string}, }; -use syn::{spanned::Spanned, Error, ForeignItem, Ident, ItemForeignMod, Result, Token}; +use syn::{ + spanned::Spanned, Error, ForeignItem, ForeignItemFn, Ident, ItemForeignMod, Result, Token, +}; /// Representation of an extern "C++Qt" block #[derive(Default)] @@ -54,32 +56,13 @@ impl ParsedExternCxxQt { ..Default::default() }; + let mut qobjects = vec![]; + // Parse any signals, other items are passed through for item in foreign_mod.items.drain(..) { match item { ForeignItem::Fn(foreign_fn) => { - // We need to check that any safe functions are defined inside an unsafe block - // as with C++Qt blocks we directly copy the unsafetyness into the generated - // extern C++ block - if foreign_fn.sig.unsafety.is_none() && extern_cxx_block.unsafety.is_none() { - return Err(Error::new(foreign_fn.span(), "block must be declared `unsafe extern \"C++Qt\"` if it contains any safe-to-call C++ functions")); - } - - // Test if the function is a signal - if attribute_get_path(&foreign_fn.attrs, &["qsignal"]).is_some() { - if attribute_get_path(&foreign_fn.attrs, &["inherit"]).is_some() { - return Err(Error::new(foreign_fn.span(), "#[inherit] is not allowed or necessary in extern \"C++Qt\" blocks, as all signals are inherited by default")); - } - let mut signal = ParsedSignal::parse(foreign_fn, auto_case)?; - // extern "C++Qt" signals are always inherit = true - // as they always exist on an existing QObject - signal.inherit = true; - extern_cxx_block.signals.push(signal); - } else { - extern_cxx_block - .passthrough_items - .push(ForeignItem::Fn(foreign_fn)); - } + extern_cxx_block.parse_invokable(foreign_fn, auto_case)?; } ForeignItem::Type(foreign_ty) => { // Test that there is a #[qobject] attribute on any type @@ -89,7 +72,7 @@ impl ParsedExternCxxQt { let extern_ty = ParsedExternQObject::parse(foreign_ty, module_ident, parent_namespace)?; // Pass through types separately for generation - extern_cxx_block.qobjects.push(extern_ty); + qobjects.push(extern_ty); } else { return Err(Error::new( foreign_ty.span(), @@ -103,8 +86,39 @@ impl ParsedExternCxxQt { } } + extern_cxx_block.qobjects.extend(qobjects); + Ok(extern_cxx_block) } + + fn parse_invokable( + &mut self, + foreign_fn: ForeignItemFn, + auto_case: CaseConversion, + ) -> Result<()> { + // We need to check that any safe functions are defined inside an unsafe block + // as with C++Qt blocks we directly copy the unsafetyness into the generated + // extern C++ block + if foreign_fn.sig.unsafety.is_none() && self.unsafety.is_none() { + return Err(Error::new(foreign_fn.span(), "block must be declared `unsafe extern \"C++Qt\"` if it contains any safe-to-call C++ functions")); + } + + // Test if the function is a signal + if attribute_get_path(&foreign_fn.attrs, &["qsignal"]).is_some() { + if attribute_get_path(&foreign_fn.attrs, &["inherit"]).is_some() { + return Err(Error::new(foreign_fn.span(), "#[inherit] is not allowed or necessary in extern \"C++Qt\" blocks, as all signals are inherited by default")); + } + let mut signal = ParsedSignal::parse(foreign_fn, auto_case)?; + // extern "C++Qt" signals are always inherit = true + // as they always exist on an existing QObject + signal.inherit = true; + self.signals.push(signal); + } else { + self.passthrough_items.push(ForeignItem::Fn(foreign_fn)); + } + + Ok(()) + } } #[cfg(test)] @@ -112,6 +126,7 @@ mod tests { use super::*; use quote::format_ident; + use crate::tests::assert_parse_errors; use syn::parse_quote; #[test] @@ -141,20 +156,6 @@ mod tests { assert!(extern_cxx_qt.unsafety.is_some()); } - #[test] - fn test_extern_cxxqt_type_missing_qobject() { - let extern_cxx_qt = ParsedExternCxxQt::parse( - parse_quote! { - unsafe extern "C++Qt" { - type QPushButton; - } - }, - &format_ident!("qobject"), - None, - ); - assert!(extern_cxx_qt.is_err()); - } - #[test] fn test_extern_cxxqt_type_qobject_attr() { let extern_cxx_qt = ParsedExternCxxQt::parse( @@ -188,41 +189,38 @@ mod tests { None, ) .unwrap(); - // Check that the non Type object is detected and error assert!(extern_cxx_qt.qobjects.is_empty()); assert!(extern_cxx_qt.signals.is_empty()); assert!(extern_cxx_qt.unsafety.is_some()); } #[test] - fn test_extern_cxxqt_invalid_safe() { - let extern_cxx_qt = ParsedExternCxxQt::parse( - parse_quote! { - extern "C++Qt" { - fn myFunction(); - } - }, - &format_ident!("qobject"), - None, - ); - // Ensure that a safe - assert!(extern_cxx_qt.is_err()); - } + fn test_parse_invalid() { + assert_parse_errors!( + |item| ParsedExternCxxQt::parse(item, &format_ident!("qobject"), None) => - #[test] - fn test_extern_cxxqt_inherit_on_signal() { - let extern_cxx_qt = ParsedExternCxxQt::parse( - parse_quote! { + // Inherit is not allowed in "C++Qt" blocks + { unsafe extern "C++Qt" { #[qsignal] #[inherit] fn myFunction(self: Pin<&mut MyObject>); } - }, - &format_ident!("qobject"), - None, + } + + // "C++Qt" blocks must be unsafe + { + extern "C++Qt" { + fn myFunction(); + } + } + + // All types in "C++Qt" blocks must be marked as QObjects + { + unsafe extern "C++Qt" { + type QPushButton; + } + } ); - // Inherit is not allowed in "C++Qt" blocks - assert!(extern_cxx_qt.is_err()); } } diff --git a/crates/cxx-qt-gen/src/parser/externrustqt.rs b/crates/cxx-qt-gen/src/parser/externrustqt.rs new file mode 100644 index 000000000..6da520bdf --- /dev/null +++ b/crates/cxx-qt-gen/src/parser/externrustqt.rs @@ -0,0 +1,260 @@ +// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Ben Ford +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use crate::naming::cpp::err_unsupported_item; +use crate::parser::inherit::ParsedInheritedMethod; +use crate::parser::method::ParsedMethod; +use crate::parser::qobject::ParsedQObject; +use crate::parser::signals::ParsedSignal; +use crate::parser::{require_attributes, CaseConversion}; +use crate::syntax::attribute::attribute_get_path; +use crate::syntax::expr::expr_to_string; +use crate::syntax::foreignmod::ForeignTypeIdentAlias; +use proc_macro2::Ident; +use syn::spanned::Spanned; +use syn::{Error, ForeignItem, ForeignItemFn, ItemForeignMod, Result, Token}; + +/// Representation of an extern "RustQt" block +#[derive(Default)] +pub struct ParsedExternRustQt { + /// Whether this block has an unsafe token + pub unsafety: Option, + /// List of QObjects defined in the module + pub qobjects: Vec, + /// List of methods and Q_INVOKABLES found + pub methods: Vec, + /// List of the Q_SIGNALS found + pub signals: Vec, + /// List of the inherited methods found + pub inherited_methods: Vec, +} + +impl ParsedExternRustQt { + pub fn parse( + mut foreign_mod: ItemForeignMod, + module_ident: &Ident, + parent_namespace: Option<&str>, + ) -> Result { + // TODO: support cfg on foreign mod blocks + let attrs = require_attributes( + &foreign_mod.attrs, + &["namespace", "auto_cxx_name", "auto_rust_name"], + )?; + + let auto_case = CaseConversion::from_attrs(&attrs)?; + + let mut extern_rustqt_block = Self { + unsafety: foreign_mod.unsafety, + ..Default::default() + }; + + let namespace = attrs + .get("namespace") + .map(|attr| expr_to_string(&attr.meta.require_name_value()?.value)) + .transpose()? + .or_else(|| parent_namespace.map(String::from)); + + for item in foreign_mod.items.drain(..) { + match item { + ForeignItem::Fn(foreign_fn) => { + extern_rustqt_block.parse_invokable(foreign_fn, auto_case)?; + } + ForeignItem::Verbatim(tokens) => { + let foreign_alias: ForeignTypeIdentAlias = syn::parse2(tokens.clone())?; + + // Load the QObject + let qobject = ParsedQObject::parse( + foreign_alias, + namespace.as_deref(), + module_ident, + auto_case, + )?; + + // Note that we assume a compiler error will occur later + // if you had two structs with the same name + extern_rustqt_block.qobjects.push(qobject); + } + // Const, Macro, Type are unsupported in extern "RustQt" for now + _ => return Err(err_unsupported_item(&item)), + } + } + + Ok(extern_rustqt_block) + } + + fn parse_invokable( + &mut self, + foreign_fn: ForeignItemFn, + auto_case: CaseConversion, + ) -> Result<()> { + // Test if the function is a signal + if attribute_get_path(&foreign_fn.attrs, &["qsignal"]).is_some() { + let parsed_signal_method = ParsedSignal::parse(foreign_fn.clone(), auto_case)?; + if parsed_signal_method.inherit + && foreign_fn.sig.unsafety.is_none() + && self.unsafety.is_none() + { + 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); + + // Test if the function is an inheritance method + // + // Note that we need to test for qsignal first as qsignals have their own inherit meaning + } else if attribute_get_path(&foreign_fn.attrs, &["inherit"]).is_some() { + // We need to check that any safe functions are defined inside an unsafe block + // as with inherit we cannot fully prove the implementation and we can then + // directly copy the unsafety into the generated extern C++ block + if foreign_fn.sig.unsafety.is_none() && self.unsafety.is_none() { + return Err(Error::new(foreign_fn.span(), "block must be declared `unsafe extern \"RustQt\"` if it contains any safe-to-call #[inherit] functions")); + } + + let parsed_inherited_method = ParsedInheritedMethod::parse(foreign_fn, auto_case)?; + + self.inherited_methods.push(parsed_inherited_method); + // Remaining methods are either C++ methods or invokables + } else { + let parsed_method = + ParsedMethod::parse(foreign_fn, auto_case, self.unsafety.is_some())?; + self.methods.push(parsed_method); + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::naming::Name; + use crate::tests::assert_parse_errors; + use quote::format_ident; + use syn::parse_quote; + + #[test] + fn test_parse_qsignals_safe() { + let block: ItemForeignMod = parse_quote! { + unsafe extern "RustQt" { + #[qsignal] + fn ready(self: Pin<&mut MyObject>); + + #[cxx_name="cppDataChanged"] + #[inherit] + #[qsignal] + fn data_changed(self: Pin<&mut MyObject>, data: i32); + } + }; + let parsed_rust_qt = + ParsedExternRustQt::parse(block, &format_ident!("qobject"), None).unwrap(); + let signals = parsed_rust_qt.signals; + assert_eq!(signals.len(), 2); + assert!(signals[0].mutable); + assert!(signals[1].mutable); + assert!(signals[0].safe); + assert!(signals[1].safe); + assert_eq!(signals[0].parameters.len(), 0); + assert_eq!(signals[1].parameters.len(), 1); + assert_eq!(signals[1].parameters[0].ident, "data"); + assert_eq!(signals[0].name, Name::new(format_ident!("ready"))); + assert_eq!( + signals[1].name, + Name::mock_name_with_cxx("data_changed", "cppDataChanged") + ); + assert!(!signals[0].inherit); + assert!(signals[1].inherit); + } + + #[test] + fn test_parse_qsignals_unsafe() { + let block: ItemForeignMod = parse_quote! { + extern "RustQt" { + #[qsignal] + #[cxx_name = "unsafeSignal"] + unsafe fn unsafe_signal(self: Pin<&mut MyObject>, arg: *mut T); + } + }; + let parsed_rust_qt = + ParsedExternRustQt::parse(block, &format_ident!("qobject"), None).unwrap(); + let signals = parsed_rust_qt.signals; + assert_eq!(signals.len(), 1); + assert!(signals[0].mutable); + assert!(!signals[0].safe); + assert_eq!(signals[0].parameters.len(), 1); + assert_eq!(signals[0].parameters[0].ident, "arg"); + assert_eq!( + signals[0].name, + Name::mock_name_with_cxx("unsafe_signal", "unsafeSignal") + ); + assert!(!signals[0].inherit); + } + + #[test] + fn test_find_and_merge_cxx_qt_item_impl_valid_qobject() { + let block: ItemForeignMod = parse_quote! { + unsafe extern "RustQt" { + #[qinvokable] + fn invokable(self: &MyObject); + + fn cpp_context(self: &MyObject); + } + }; + let parsed_rust_qt = + ParsedExternRustQt::parse(block, &format_ident!("qobject"), None).unwrap(); + + let methods = parsed_rust_qt.methods; + assert_eq!(methods.len(), 2); + assert!(methods[0].is_qinvokable); + assert!(!methods[1].is_qinvokable); + } + + #[test] + fn test_parse_invalid() { + assert_parse_errors!( + |item| ParsedExternRustQt::parse(item, &format_ident!("qobject"), None) => + + // Invalid QObject + { + unsafe extern "RustQt" { + #[qinvokable] + fn invokable(self: &MyObject::Bad); + } + } + + // Namespaces aren't allowed on qinvokables + { + unsafe extern "RustQt" { + #[qinvokable] + #[namespace = "disallowed"] + fn invokable(self: &MyObject); + } + } + + // Block or fn must be unsafe for inherit methods + { + extern "RustQt" { + #[inherit] + fn invokable(self: &MyObject); + } + } + + // Block or fn must be unsafe for inherit qsignals + { + extern "RustQt" { + #[inherit] + #[qsignal] + fn signal(self: Pin<&mut MyObject>); + } + } + + // Unsupported Item + { + extern "RustQt" { + static COUNTER: usize; + } + } + ); + } +} diff --git a/crates/cxx-qt-gen/src/parser/inherit.rs b/crates/cxx-qt-gen/src/parser/inherit.rs index a09081710..9ebb5af17 100644 --- a/crates/cxx-qt-gen/src/parser/inherit.rs +++ b/crates/cxx-qt-gen/src/parser/inherit.rs @@ -8,6 +8,7 @@ use crate::parser::{ }; use core::ops::Deref; use quote::format_ident; +use std::ops::DerefMut; use syn::{Attribute, ForeignItemFn, Ident, Result}; /// Describes a method found in an extern "RustQt" with #[inherit] @@ -56,6 +57,12 @@ impl Deref for ParsedInheritedMethod { } } +impl DerefMut for ParsedInheritedMethod { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.method_fields + } +} + #[cfg(test)] mod tests { use super::*; @@ -68,7 +75,6 @@ mod tests { |item| ParsedInheritedMethod::parse(item, CaseConversion::none()) => // Missing self type - { fn test(&self); } { fn test(self: &mut T); } // Pointer types { fn test(self: *const T); } @@ -91,6 +97,14 @@ mod tests { CaseConversion::none() ) .is_ok()); + // T by ref is ok in this shorthand (provided the block has one QObject) + assert!(ParsedInheritedMethod::parse( + parse_quote! { + fn test(&self); + }, + CaseConversion::none() + ) + .is_ok()); // T by Pin assert!(ParsedInheritedMethod::parse( parse_quote! { diff --git a/crates/cxx-qt-gen/src/parser/method.rs b/crates/cxx-qt-gen/src/parser/method.rs index 7c161c4b8..89c708da5 100644 --- a/crates/cxx-qt-gen/src/parser/method.rs +++ b/crates/cxx-qt-gen/src/parser/method.rs @@ -9,7 +9,9 @@ use crate::{ syntax::{foreignmod, types}, }; use core::ops::Deref; +use quote::format_ident; use std::collections::{BTreeMap, HashSet}; +use std::ops::DerefMut; use syn::{Attribute, ForeignItemFn, Ident, Result}; /// Describes a C++ specifier for the Q_INVOKABLE @@ -146,6 +148,12 @@ impl Deref for ParsedMethod { } } +impl DerefMut for ParsedMethod { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.method_fields + } +} + /// Struct with common fields between Invokable types. /// These types are ParsedSignal, ParsedMethod and ParsedInheritedMethod #[derive(Clone)] @@ -178,4 +186,8 @@ impl MethodFields { name, }) } + + pub(crate) fn self_unresolved(&self) -> bool { + self.qobject_ident == format_ident!("Self") + } } diff --git a/crates/cxx-qt-gen/src/parser/mod.rs b/crates/cxx-qt-gen/src/parser/mod.rs index 3a0821761..97b11ec1f 100644 --- a/crates/cxx-qt-gen/src/parser/mod.rs +++ b/crates/cxx-qt-gen/src/parser/mod.rs @@ -7,6 +7,7 @@ pub mod constructor; pub mod cxxqtdata; pub mod externcxxqt; pub mod externqobject; +mod externrustqt; pub mod inherit; pub mod method; pub mod parameter; @@ -327,7 +328,7 @@ mod tests { assert_eq!(parser.passthrough_module.module_ident, "ffi"); assert_eq!(parser.passthrough_module.vis, Visibility::Inherited); assert_eq!(parser.cxx_qt_data.namespace, None); - assert_eq!(parser.cxx_qt_data.qobjects.len(), 0); + assert_eq!(parser.cxx_qt_data.qobjects().len(), 0); } #[test] @@ -369,7 +370,7 @@ mod tests { assert_eq!(parser.passthrough_module.module_ident, "ffi"); assert_eq!(parser.passthrough_module.vis, Visibility::Inherited); assert_eq!(parser.cxx_qt_data.namespace, None); - assert_eq!(parser.cxx_qt_data.qobjects.len(), 0); + assert_eq!(parser.cxx_qt_data.qobjects().len(), 0); } #[test] @@ -395,7 +396,7 @@ mod tests { assert_eq!(parser.passthrough_module.module_ident, "ffi"); assert_eq!(parser.passthrough_module.vis, Visibility::Inherited); assert_eq!(parser.cxx_qt_data.namespace, Some("cxx_qt".to_owned())); - assert_eq!(parser.cxx_qt_data.qobjects.len(), 1); + assert_eq!(parser.cxx_qt_data.qobjects().len(), 1); assert_eq!(parser.type_names.num_types(), 19); assert_eq!( parser @@ -441,7 +442,7 @@ mod tests { assert_eq!(parser.passthrough_module.module_ident, "ffi"); assert_eq!(parser.passthrough_module.vis, Visibility::Inherited); assert_eq!(parser.cxx_qt_data.namespace, None); - assert_eq!(parser.cxx_qt_data.qobjects.len(), 1); + assert_eq!(parser.cxx_qt_data.qobjects().len(), 1); } #[test] diff --git a/crates/cxx-qt-gen/src/parser/signals.rs b/crates/cxx-qt-gen/src/parser/signals.rs index 3cbf7e754..81e264ebb 100644 --- a/crates/cxx-qt-gen/src/parser/signals.rs +++ b/crates/cxx-qt-gen/src/parser/signals.rs @@ -8,6 +8,7 @@ use crate::{ syntax::path::path_compare_str, }; use core::ops::Deref; +use std::ops::DerefMut; use syn::{spanned::Spanned, Attribute, Error, ForeignItemFn, Result, Visibility}; #[derive(Clone)] @@ -74,6 +75,12 @@ impl Deref for ParsedSignal { } } +impl DerefMut for ParsedSignal { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.method_fields + } +} + #[cfg(test)] mod tests { use syn::parse_quote; diff --git a/crates/cxx-qt-gen/src/shorthand/mod.rs b/crates/cxx-qt-gen/src/shorthand/mod.rs new file mode 100644 index 000000000..f8d4aef95 --- /dev/null +++ b/crates/cxx-qt-gen/src/shorthand/mod.rs @@ -0,0 +1,7 @@ +// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Ben Ford +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +// TODO! Add our other shorthands into this module? +pub mod self_inlining; diff --git a/crates/cxx-qt-gen/src/shorthand/self_inlining.rs b/crates/cxx-qt-gen/src/shorthand/self_inlining.rs new file mode 100644 index 000000000..b7eefb2a1 --- /dev/null +++ b/crates/cxx-qt-gen/src/shorthand/self_inlining.rs @@ -0,0 +1,187 @@ +// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company +// SPDX-FileContributor: Ben Ford +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! This module contains functions which can inline a Self type to the appropriate `QObject`. +//! +//! If ***exactly one*** `QObject` exists in a particular extern block, the self shorthand can be used, +//! and this modules functions will replace this `Self` type with the appropriate name. +//! `qualify_self_types` is called at the highest level, on an already built parser + +use crate::parser::method::MethodFields; +use crate::Parser; +use proc_macro2::Ident; +use quote::format_ident; +use std::ops::DerefMut; +use syn::spanned::Spanned; +use syn::{Error, Result}; + +/// Inline any `Self` types in the methods signatures with the Ident of a qobject passed in +/// +/// If there are unresolved methods in the list, but inline is false, it will error, +/// as the self inlining is only available if there is exactly one `QObject` in the block, +/// and this indicates that no inlining can be done, but some `Self` types were present. +pub fn try_inline_self_invokables( + type_to_inline: &Option, + invokables: &mut [impl DerefMut], +) -> Result<()> { + for method in invokables.iter_mut() { + if method.self_unresolved() { + if let Some(inline_type) = type_to_inline.clone() { + method.qobject_ident = inline_type; + } else { + return Err(Error::new( + method.method.span(), + "`Self` type can only be inferred if the extern block contains exactly one `qobject`.", + )); + } + } + } + Ok(()) +} + +/// For a given parser, attempt to inline the `Self` type used in any of the blocks with that blocks unique QObject +pub fn qualify_self_types(parser: &mut Parser) -> Result<()> { + // Inlining `extern "RustQt"` blocks + for rust_block in &mut parser.cxx_qt_data.extern_rustqt_blocks { + let mut iter = rust_block.qobjects.iter(); + let mut inline_ident = iter + .next() + .map(|obj| format_ident!("{}", obj.declaration.ident_left)); + + if iter.next().is_some() { + inline_ident = None; + } + + try_inline_self_invokables(&inline_ident, &mut rust_block.methods)?; + try_inline_self_invokables(&inline_ident, &mut rust_block.signals)?; + try_inline_self_invokables(&inline_ident, &mut rust_block.inherited_methods)?; + } + + // Inlining `extern "C++Qt"` blocks + for cpp_block in &mut parser.cxx_qt_data.extern_cxxqt_blocks { + let mut iter = cpp_block.qobjects.iter(); + let mut inline_ident = iter + .next() + .map(|obj| format_ident!("{}", obj.declaration.ident)); + + if iter.next().is_some() { + inline_ident = None; + } + + try_inline_self_invokables(&inline_ident, &mut cpp_block.signals)?; + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tests::assert_parse_errors; + use syn::parse_quote; + + #[test] + fn test_self_inlining_ref() { + let module = parse_quote! { + #[cxx_qt::bridge] + mod ffi { + unsafe extern "RustQt" { + #[qobject] + type MyObject = super::T; + + fn my_method(&self); + + #[inherit] + fn my_inherited_method(&self); + } + } + }; + let mut parser = Parser::from(module).unwrap(); + assert!(qualify_self_types(&mut parser).is_ok()); + } + + #[test] + fn test_self_inlining_pin() { + let module = parse_quote! { + #[cxx_qt::bridge] + mod ffi { + unsafe extern "RustQt" { + #[qobject] + type MyObject = super::T; + + #[qsignal] + fn my_signal(self: Pin<&mut Self>); + } + + unsafe extern "C++Qt" { + #[qobject] + type MyOtherObject; + + #[qsignal] + fn my_signal(self: Pin<&mut Self>); + } + } + }; + let mut parser = Parser::from(module).unwrap(); + assert!(qualify_self_types(&mut parser).is_ok()); + } + + #[test] + fn test_self_inlining_methods_invalid() { + assert_parse_errors! { + |item| qualify_self_types(&mut Parser::from(item)?) => + // No QObject in block + { + #[cxx_qt::bridge] + mod ffi { + extern "RustQt" { + fn my_method(&self); + } + } + } + + { + #[cxx_qt::bridge] + mod ffi { + extern "RustQt" { + fn my_method(self: Pin<&mut Self>); + } + } + } + // More than 1 QObjects in block + { + #[cxx_qt::bridge] + mod ffi { + unsafe extern "C++Qt" { + #[qobject] + type MyObject = super::T; + + #[qobject] + type SecondObject = super::S; + + #[qsignal] + fn my_method(self: Pin<&mut Self>); + } + } + } + + // More than one qobject, in RustQt + { + #[cxx_qt::bridge] + mod ffi { + extern "RustQt" { + #[qobject] + type MyObject = super::T; + + #[qobject] + type SecondObject = super::S; + + fn my_method(&self); + } + } + } + } + } +} diff --git a/crates/cxx-qt-gen/src/syntax/foreignmod.rs b/crates/cxx-qt-gen/src/syntax/foreignmod.rs index 7b00734bf..be1565fef 100644 --- a/crates/cxx-qt-gen/src/syntax/foreignmod.rs +++ b/crates/cxx-qt-gen/src/syntax/foreignmod.rs @@ -174,14 +174,7 @@ pub fn self_type_from_foreign_fn(signature: &Signature) -> Result { )); } - if receiver.reference.is_some() { - return Err(Error::new( - receiver.span(), - "Reference on self (i.e. `&self`) are not supported! Use `self: &T` instead", - )); - } - - if receiver.colon_token.is_none() { + if receiver.colon_token.is_none() && receiver.reference.is_none() { return Err(Error::new( receiver.span(), "`self` is not supported as receiver! Use `self: T` to indicate a type.", @@ -254,8 +247,6 @@ mod tests { { fn foo(self); } // self with mut { fn foo(mut self: T); } - // self reference - { fn foo(&self); } // self reference with mut { fn foo(&mut self); } // attribute on self type diff --git a/crates/cxx-qt-gen/test_inputs/passthrough_and_naming.rs b/crates/cxx-qt-gen/test_inputs/passthrough_and_naming.rs index cfa3bae78..bb4fa2c2e 100644 --- a/crates/cxx-qt-gen/test_inputs/passthrough_and_naming.rs +++ b/crates/cxx-qt-gen/test_inputs/passthrough_and_naming.rs @@ -113,19 +113,17 @@ pub mod ffi { fn errorOccurred(self: Pin<&mut ExternObject>); } - extern "RustQt" { + unsafe extern "RustQt" { #[qobject] #[base = QStringListModel] #[qproperty(i32, property_name, cxx_name = "propertyName")] type MyObject = super::MyObjectRust; - } - unsafe extern "RustQt" { #[qsignal] - fn ready(self: Pin<&mut MyObject>); + fn ready(self: Pin<&mut Self>); #[qinvokable] - fn invokable_name(self: Pin<&mut MyObject>); + fn invokable_name(self: Pin<&mut Self>); } extern "RustQt" { diff --git a/crates/cxx-qt-gen/test_inputs/signals.rs b/crates/cxx-qt-gen/test_inputs/signals.rs index 3aca1f5bd..ec298cca7 100644 --- a/crates/cxx-qt-gen/test_inputs/signals.rs +++ b/crates/cxx-qt-gen/test_inputs/signals.rs @@ -16,7 +16,7 @@ mod ffi { /// When the QTimer timeout occurs #[qsignal] - pub(self) fn timeout(self: Pin<&mut QTimer>); + pub(self) fn timeout(self: Pin<&mut Self>); } unsafe extern "RustQt" { @@ -24,11 +24,11 @@ mod ffi { type MyObject = super::MyObjectRust; #[qsignal] - fn ready(self: Pin<&mut MyObject>); + fn ready(self: Pin<&mut Self>); #[qsignal] fn data_changed( - self: Pin<&mut MyObject>, + self: Pin<&mut Self>, first: i32, second: UniquePtr, third: QPoint, @@ -39,7 +39,7 @@ mod ffi { #[inherit] #[qsignal] fn base_class_new_data( - self: Pin<&mut MyObject>, + self: Pin<&mut Self>, first: i32, second: UniquePtr, third: QPoint, @@ -47,6 +47,6 @@ mod ffi { ); #[qinvokable] - fn invokable(self: Pin<&mut MyObject>); + fn invokable(self: Pin<&mut Self>); } } diff --git a/crates/cxx-qt-macro/src/lib.rs b/crates/cxx-qt-macro/src/lib.rs index fcd9c01f2..96c1a7517 100644 --- a/crates/cxx-qt-macro/src/lib.rs +++ b/crates/cxx-qt-macro/src/lib.rs @@ -15,7 +15,7 @@ use proc_macro::TokenStream; use syn::{parse_macro_input, ItemMod}; -use cxx_qt_gen::{write_rust, GeneratedRustBlocks, Parser}; +use cxx_qt_gen::{self_inlining::qualify_self_types, write_rust, GeneratedRustBlocks, Parser}; #[proc_macro_attribute] pub fn bridge(args: TokenStream, input: TokenStream) -> TokenStream { @@ -75,6 +75,10 @@ pub fn init_qml_module(args: TokenStream) -> TokenStream { // Take the module and C++ namespace and generate the rust code fn extract_and_generate(module: ItemMod) -> TokenStream { Parser::from(module) + .and_then(|mut parser| { + qualify_self_types(&mut parser)?; + Ok(parser) + }) .and_then(|parser| GeneratedRustBlocks::from(&parser)) .map(|generated_rust| write_rust(&generated_rust, None)) .unwrap_or_else(|err| err.to_compile_error()) diff --git a/examples/demo_threading/rust/src/lib.rs b/examples/demo_threading/rust/src/lib.rs index bafd88910..101d1d3b0 100644 --- a/examples/demo_threading/rust/src/lib.rs +++ b/examples/demo_threading/rust/src/lib.rs @@ -17,6 +17,9 @@ pub mod qobject { type QString = cxx_qt_lib::QString; } + // Enabling threading on the qobject + impl cxx_qt::Threading for EnergyUsage {} + extern "RustQt" { #[qobject] #[qml_element] @@ -24,31 +27,24 @@ pub mod qobject { #[qproperty(u32, sensors)] #[qproperty(f64, total_use)] type EnergyUsage = super::EnergyUsageRust; - } - - // Enabling threading on the qobject - impl cxx_qt::Threading for EnergyUsage {} - extern "RustQt" { /// A new sensor has been detected #[qsignal] #[cxx_name = "sensorAdded"] - fn sensor_added(self: Pin<&mut EnergyUsage>, uuid: QString); + fn sensor_added(self: Pin<&mut Self>, uuid: QString); /// A value on an existing sensor has changed #[qsignal] #[cxx_name = "sensorChanged"] - fn sensor_changed(self: Pin<&mut EnergyUsage>, uuid: QString); + fn sensor_changed(self: Pin<&mut Self>, uuid: QString); /// An existing sensor has been removed #[qsignal] #[cxx_name = "sensorRemoved"] - fn sensor_removed(self: Pin<&mut EnergyUsage>, uuid: QString); - } + fn sensor_removed(self: Pin<&mut Self>, uuid: QString); - extern "RustQt" { /// A Q_INVOKABLE that returns the current power usage for a given uuid #[qinvokable] #[cxx_name = "sensorPower"] - fn sensor_power(self: Pin<&mut EnergyUsage>, uuid: &QString) -> f64; + fn sensor_power(self: Pin<&mut Self>, uuid: &QString) -> f64; } impl cxx_qt::Initialize for EnergyUsage {} diff --git a/examples/qml_features/rust/src/containers.rs b/examples/qml_features/rust/src/containers.rs index cd7135b3e..a63455219 100644 --- a/examples/qml_features/rust/src/containers.rs +++ b/examples/qml_features/rust/src/containers.rs @@ -46,32 +46,32 @@ pub mod qobject { /// Reset all the containers #[qinvokable] - fn reset(self: Pin<&mut RustContainers>); + fn reset(self: Pin<&mut Self>); /// Append the given number to the vector container #[qinvokable] #[cxx_name = "appendVector"] - fn append_vector(self: Pin<&mut RustContainers>, value: i32); + fn append_vector(self: Pin<&mut Self>, value: i32); /// Append the given number to the list container #[qinvokable] #[cxx_name = "appendList"] - fn append_list(self: Pin<&mut RustContainers>, value: i32); + fn append_list(self: Pin<&mut Self>, value: i32); /// Insert the given number into the set container #[qinvokable] #[cxx_name = "insertSet"] - fn insert_set(self: Pin<&mut RustContainers>, value: i32); + fn insert_set(self: Pin<&mut Self>, value: i32); /// Insert the given string and variant to the hash container #[qinvokable] #[cxx_name = "insertHash"] - fn insert_hash(self: Pin<&mut RustContainers>, key: QString, value: QVariant); + fn insert_hash(self: Pin<&mut Self>, key: QString, value: QVariant); /// Insert the given string and variant to the map container #[qinvokable] #[cxx_name = "insertMap"] - fn insert_map(self: Pin<&mut RustContainers>, key: QString, value: QVariant); + fn insert_map(self: Pin<&mut Self>, key: QString, value: QVariant); } } diff --git a/examples/qml_features/rust/src/custom_parent_class.rs b/examples/qml_features/rust/src/custom_parent_class.rs index 3b36884ba..c0237cece 100644 --- a/examples/qml_features/rust/src/custom_parent_class.rs +++ b/examples/qml_features/rust/src/custom_parent_class.rs @@ -43,15 +43,15 @@ pub mod qobject { /// Override QQuickPaintedItem::paint to draw two rectangles in Rust using QPainter #[qinvokable] #[cxx_override] - unsafe fn paint(self: Pin<&mut CustomParentClass>, painter: *mut QPainter); + unsafe fn paint(self: Pin<&mut Self>, painter: *mut QPainter); /// Define that we need to inherit size() from the base class #[inherit] - fn size(self: &CustomParentClass) -> QSizeF; + fn size(self: &Self) -> QSizeF; /// Define that we need to inherit update() from the base class #[inherit] - fn update(self: Pin<&mut CustomParentClass>); + fn update(self: Pin<&mut Self>); } impl cxx_qt::Initialize for CustomParentClass {} diff --git a/examples/qml_features/rust/src/externcxxqt.rs b/examples/qml_features/rust/src/externcxxqt.rs index 4d2a74a46..efc9fd04b 100644 --- a/examples/qml_features/rust/src/externcxxqt.rs +++ b/examples/qml_features/rust/src/externcxxqt.rs @@ -14,17 +14,18 @@ pub mod ffi { #[qobject] type ExternalQObject; + // Since functions are just passed through the inlining isn't yet supported /// Trigger emitting the signal "amount" times fn trigger(self: Pin<&mut ExternalQObject>, amount: u32); /// Signal that is emitted when trigger is fired #[qsignal] - fn triggered(self: Pin<&mut ExternalQObject>); + fn triggered(self: Pin<&mut Self>); /// Private signal that is emitted when trigger is fired #[qsignal] #[rust_name = "triggered_private_signal"] - pub(self) fn triggeredPrivateSignal(self: Pin<&mut ExternalQObject>); + pub(self) fn triggeredPrivateSignal(self: Pin<&mut Self>); } extern "RustQt" { @@ -36,15 +37,12 @@ pub mod ffi { #[qinvokable] #[cxx_name = "connectToExternal"] - unsafe fn connect_to_external( - self: Pin<&mut ExternalCxxQtHelper>, - external: *mut ExternalQObject, - ); + unsafe fn connect_to_external(self: Pin<&mut Self>, external: *mut ExternalQObject); #[qinvokable] #[cxx_name = "triggerOnExternal"] unsafe fn trigger_on_external( - self: Pin<&mut ExternalCxxQtHelper>, + self: Pin<&mut Self>, external: *mut ExternalQObject, amount: u32, ); diff --git a/examples/qml_features/rust/src/multiple_qobjects.rs b/examples/qml_features/rust/src/multiple_qobjects.rs index f1a24ea6b..128063126 100644 --- a/examples/qml_features/rust/src/multiple_qobjects.rs +++ b/examples/qml_features/rust/src/multiple_qobjects.rs @@ -23,53 +23,45 @@ pub mod qobject { #[qproperty(i32, counter)] #[qproperty(QColor, color)] type FirstObject = super::FirstObjectRust; - } - // Enabling threading on the qobject - impl cxx_qt::Threading for FirstObject {} - - extern "RustQt" { /// Accepted Q_SIGNAL #[qsignal] - fn accepted(self: Pin<&mut FirstObject>); + fn accepted(self: Pin<&mut Self>); /// Rejected Q_SIGNAL #[qsignal] - fn rejected(self: Pin<&mut FirstObject>); - } + fn rejected(self: Pin<&mut Self>); - extern "RustQt" { /// A Q_INVOKABLE on the first QObject which increments a counter #[qinvokable] - fn increment(self: Pin<&mut FirstObject>); + fn increment(self: Pin<&mut Self>); } + // Enabling threading on the qobject + impl cxx_qt::Threading for FirstObject {} + extern "RustQt" { #[qobject] #[qml_element] #[qproperty(i32, counter)] #[qproperty(QUrl, url)] type SecondObject = super::SecondObjectRust; - } - // Enabling threading on the qobject - impl cxx_qt::Threading for SecondObject {} - - extern "RustQt" { /// Accepted Q_SIGNAL #[qsignal] - fn accepted(self: Pin<&mut SecondObject>); + fn accepted(self: Pin<&mut Self>); /// Rejected Q_SIGNAL #[qsignal] - fn rejected(self: Pin<&mut SecondObject>); - } + fn rejected(self: Pin<&mut Self>); - extern "RustQt" { /// A Q_INVOKABLE on the second QObject which increments a counter #[qinvokable] - fn increment(self: Pin<&mut SecondObject>); + fn increment(self: Pin<&mut Self>); } + + // Enabling threading on the qobject + impl cxx_qt::Threading for SecondObject {} } use core::pin::Pin; diff --git a/examples/qml_features/rust/src/naming.rs b/examples/qml_features/rust/src/naming.rs index 0f25fe278..440b58547 100644 --- a/examples/qml_features/rust/src/naming.rs +++ b/examples/qml_features/rust/src/naming.rs @@ -15,13 +15,12 @@ pub mod qobject { #[cxx_name = "RenamedObject"] #[namespace = "my_namespace"] type NamedObject = super::NamedObjectRust; - } - extern "RustQt" { #[qinvokable] #[cxx_name = "increment"] #[rust_name = "plus_one"] - fn increment_number(self: Pin<&mut NamedObject>); + fn increment_number(self: Pin<&mut Self>); + } #[auto_cxx_name] diff --git a/examples/qml_features/rust/src/nested_qobjects.rs b/examples/qml_features/rust/src/nested_qobjects.rs index 14f013f83..70e5d79b7 100644 --- a/examples/qml_features/rust/src/nested_qobjects.rs +++ b/examples/qml_features/rust/src/nested_qobjects.rs @@ -44,7 +44,7 @@ pub mod qobject { /// /// Due to a raw pointer this is considered unsafe in CXX #[qsignal] - unsafe fn called(self: Pin<&mut OuterObject>, inner: *mut InnerObject); + unsafe fn called(self: Pin<&mut Self>, inner: *mut InnerObject); } extern "RustQt" { diff --git a/examples/qml_features/rust/src/properties.rs b/examples/qml_features/rust/src/properties.rs index a1ed2330c..54cca0a86 100644 --- a/examples/qml_features/rust/src/properties.rs +++ b/examples/qml_features/rust/src/properties.rs @@ -31,15 +31,15 @@ pub mod qobject { /// Custom on changed signal, used for all the properties #[qsignal] #[cxx_name = "connectedStateChanged"] - fn connected_state_changed(self: Pin<&mut RustProperties>); + fn connected_state_changed(self: Pin<&mut Self>); /// Custom setter for connected_url, which also handles setting the other qproperties #[cxx_name = "setUrl"] - fn set_url(self: Pin<&mut RustProperties>, url: QUrl); + fn set_url(self: Pin<&mut Self>, url: QUrl); /// Resets value of connected_url to empty, as well as calling the other disconnected logic #[cxx_name = "resetUrl"] - fn reset_url(self: Pin<&mut RustProperties>); + fn reset_url(self: Pin<&mut Self>); } impl cxx_qt::Initialize for RustProperties {} diff --git a/examples/qml_features/rust/src/serialisation.rs b/examples/qml_features/rust/src/serialisation.rs index ea14a3f97..939f4bad3 100644 --- a/examples/qml_features/rust/src/serialisation.rs +++ b/examples/qml_features/rust/src/serialisation.rs @@ -28,18 +28,18 @@ pub mod qobject { /// An error signal #[qsignal] - fn error(self: Pin<&mut Serialisation>, message: QString); + fn error(self: Pin<&mut Self>, message: QString); /// Retrieve the JSON form of this QObject #[qinvokable] #[cxx_name = "asJsonStr"] - fn as_json_str(self: Pin<&mut Serialisation>) -> QString; + fn as_json_str(self: Pin<&mut Self>) -> QString; /// From a given JSON string try to load values for the Q_PROPERTYs // ANCHOR: book_grab_values #[qinvokable] #[cxx_name = "fromJsonStr"] - fn from_json_str(self: Pin<&mut Serialisation>, string: &QString); + fn from_json_str(self: Pin<&mut Self>, string: &QString); // ANCHOR_END: book_grab_values } } diff --git a/examples/qml_features/rust/src/singleton.rs b/examples/qml_features/rust/src/singleton.rs index 98fbdbc41..9c02aec0b 100644 --- a/examples/qml_features/rust/src/singleton.rs +++ b/examples/qml_features/rust/src/singleton.rs @@ -18,7 +18,7 @@ pub mod qobject { /// Increment the persistent value Q_PROPERTY of the QML_SINGLETON #[qinvokable] - fn increment(self: Pin<&mut RustSingleton>); + fn increment(self: Pin<&mut Self>); } } diff --git a/examples/qml_features/rust/src/threading.rs b/examples/qml_features/rust/src/threading.rs index 5111a5396..5e590b2db 100644 --- a/examples/qml_features/rust/src/threading.rs +++ b/examples/qml_features/rust/src/threading.rs @@ -27,24 +27,22 @@ pub mod qobject { #[qproperty(QString, title)] #[qproperty(QUrl, url)] type ThreadingWebsite = super::ThreadingWebsiteRust; - } - - // ANCHOR: book_threading_trait - // Enabling threading on the qobject - impl cxx_qt::Threading for ThreadingWebsite {} - // ANCHOR_END: book_threading_trait - extern "RustQt" { /// Swap the URL between kdab.com and github.com #[qinvokable] #[cxx_name = "changeUrl"] - fn change_url(self: Pin<&mut ThreadingWebsite>); + fn change_url(self: Pin<&mut Self>); /// Simulate delay of a network request to retrieve the title of the website #[qinvokable] #[cxx_name = "fetchTitle"] - fn fetch_title(self: Pin<&mut ThreadingWebsite>); + fn fetch_title(self: Pin<&mut Self>); } + + // ANCHOR: book_threading_trait + // Enabling threading on the qobject + impl cxx_qt::Threading for ThreadingWebsite {} + // ANCHOR_END: book_threading_trait } use core::pin::Pin; diff --git a/examples/qml_features/rust/src/types.rs b/examples/qml_features/rust/src/types.rs index 63d8ba9e6..90907c7e7 100644 --- a/examples/qml_features/rust/src/types.rs +++ b/examples/qml_features/rust/src/types.rs @@ -83,12 +83,12 @@ pub mod ffi { /// Load the value from a QVariant #[qinvokable] #[cxx_name = "loadFromVariant"] - fn load_from_variant(self: Pin<&mut Types>, variant: &QVariant); + fn load_from_variant(self: Pin<&mut Self>, variant: &QVariant); /// Toggle the boolean Q_PROPERTY #[qinvokable] #[cxx_name = "toggleBoolean"] - fn toggle_boolean(self: Pin<&mut Types>); + fn toggle_boolean(self: Pin<&mut Self>); } } diff --git a/examples/qml_minimal/rust/src/cxxqt_object.rs b/examples/qml_minimal/rust/src/cxxqt_object.rs index 84ac60084..15878e1e3 100644 --- a/examples/qml_minimal/rust/src/cxxqt_object.rs +++ b/examples/qml_minimal/rust/src/cxxqt_object.rs @@ -21,8 +21,8 @@ pub mod qobject { } // ANCHOR_END: book_qstring_import - // ANCHOR: book_rustobj_struct_signature extern "RustQt" { + // ANCHOR: book_rustobj_struct_signature // The QObject definition // We tell CXX-Qt that we want a QObject class with the name MyObject // based on the Rust struct MyObjectRust. @@ -32,21 +32,19 @@ pub mod qobject { #[qproperty(QString, string)] #[namespace = "my_object"] type MyObject = super::MyObjectRust; - } - // ANCHOR_END: book_rustobj_struct_signature + // ANCHOR_END: book_rustobj_struct_signature - // ANCHOR: book_rustobj_invokable_signature - extern "RustQt" { + // ANCHOR: book_rustobj_invokable_signature // Declare the invokable methods we want to expose on the QObject #[qinvokable] #[cxx_name = "incrementNumber"] - fn increment_number(self: Pin<&mut MyObject>); + fn increment_number(self: Pin<&mut Self>); #[qinvokable] #[cxx_name = "sayHi"] - fn say_hi(self: &MyObject, string: &QString, number: i32); + fn say_hi(&self, string: &QString, number: i32); + // ANCHOR_END: book_rustobj_invokable_signature } - // ANCHOR_END: book_rustobj_invokable_signature } // ANCHOR: book_use diff --git a/examples/qml_multi_crates/rust/main/src/main_object.rs b/examples/qml_multi_crates/rust/main/src/main_object.rs index 3fd832125..b4fb5f6a9 100644 --- a/examples/qml_multi_crates/rust/main/src/main_object.rs +++ b/examples/qml_multi_crates/rust/main/src/main_object.rs @@ -17,7 +17,7 @@ pub mod qobject { type MainObject = super::MainObjectRust; #[qinvokable] - fn increment(self: Pin<&mut MainObject>); + fn increment(self: Pin<&mut Self>); } } diff --git a/examples/qml_multi_crates/rust/sub1/src/sub1_object.rs b/examples/qml_multi_crates/rust/sub1/src/sub1_object.rs index 3b949b0db..084e17692 100644 --- a/examples/qml_multi_crates/rust/sub1/src/sub1_object.rs +++ b/examples/qml_multi_crates/rust/sub1/src/sub1_object.rs @@ -17,7 +17,7 @@ pub mod qobject { type Sub1Object = super::Sub1ObjectRust; #[qinvokable] - fn increment(self: Pin<&mut Sub1Object>); + fn increment(self: Pin<&mut Self>); } } diff --git a/examples/qml_multi_crates/rust/sub2/src/sub2_object.rs b/examples/qml_multi_crates/rust/sub2/src/sub2_object.rs index 73ba275ce..9a849f296 100644 --- a/examples/qml_multi_crates/rust/sub2/src/sub2_object.rs +++ b/examples/qml_multi_crates/rust/sub2/src/sub2_object.rs @@ -17,7 +17,7 @@ pub mod qobject { type Sub2Object = super::Sub2ObjectRust; #[qinvokable] - fn increment(self: Pin<&mut Sub2Object>); + fn increment(self: Pin<&mut Self>); } } diff --git a/examples/todo_app/src/todo_list.rs b/examples/todo_app/src/todo_list.rs index e5cfe6953..7ad696c2d 100644 --- a/examples/todo_app/src/todo_list.rs +++ b/examples/todo_app/src/todo_list.rs @@ -41,32 +41,30 @@ mod qobject { #[cxx_override] #[rust_name = "row_count"] - fn rowCount(self: &TodoList, parent: &QModelIndex) -> i32; + fn rowCount(&self, parent: &QModelIndex) -> i32; #[cxx_override] - fn data(self: &TodoList, index: &QModelIndex, role: i32) -> QVariant; + fn data(&self, index: &QModelIndex, role: i32) -> QVariant; #[cxx_override] #[rust_name = "role_names"] - fn roleNames(self: &TodoList) -> QHash_i32_QByteArray; - } + fn roleNames(&self) -> QHash_i32_QByteArray; - unsafe extern "RustQt" { #[qinvokable] #[rust_name = "set_checked"] - fn setChecked(self: Pin<&mut TodoList>, row: i32, checked: bool); + fn setChecked(self: Pin<&mut Self>, row: i32, checked: bool); #[inherit] #[rust_name = "begin_reset_model"] - fn beginResetModel(self: Pin<&mut TodoList>); + fn beginResetModel(self: Pin<&mut Self>); #[inherit] #[rust_name = "end_reset_model"] - fn endResetModel(self: Pin<&mut TodoList>); + fn endResetModel(self: Pin<&mut Self>); #[qinvokable] #[rust_name = "add_todo"] - fn addTodo(self: Pin<&mut TodoList>, todo: &QString); + fn addTodo(self: Pin<&mut Self>, todo: &QString); } }