Skip to content

Commit

Permalink
cxx-qt-gen: support for marking signals in the base class
Browse files Browse the repository at this point in the history
Closes #286
  • Loading branch information
ahayzen-kdab committed Jan 16, 2023
1 parent 1d09e7b commit 48dcc52
Show file tree
Hide file tree
Showing 13 changed files with 264 additions and 23 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Support for container types: `QSet<T>`, `QHash<K, V>`, `QList<T>`, `QMap<K, V>`, `QVector<T>`
- Support for further types: `QByteArray`, `QModelIndex`, `QPersistentModelIndex`, `QStringList`, `QVector2D`, `QVector3D`, `QVector4D`
- Support for nesting objects in properties, invokables, and signals with `*mut T`
- Allow for marking signals as existing in the base class

### Changed

Expand Down
8 changes: 8 additions & 0 deletions book/src/qobject/signals_enum.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ If the enum variant has members, they will become the parameters for the corresp
Because CXX-Qt needs to know the names of each parameter, only enum variants with named members are supported.
The signal parameters are generated in order of appearance in the enum variant.

If a signal is defined on the base class of the QObject then `#[inherit]` can be used to indicate to CXX-Qt that the `Q_SIGNAL` does not need to be created in C++.

```rust,ignore,noplayground
{{#include ../../../examples/qml_features/rust/src/custom_base_class.rs:book_qsignals_inherit}}
```

Note that `#[cxx_name = "..."]` can also be used on a signal to declare a different name in C++ to Rust.

## Emitting a signal

For every generated QObject [`qobject::T`](./generated-qobject.md) that has a signals enum, CXX-Qt will generate an `emit` function:
Expand Down
50 changes: 44 additions & 6 deletions crates/cxx-qt-gen/src/generator/cpp/signal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,14 @@ pub fn generate_cpp_signals(
let emit_ident = idents.emit_name.cpp.to_string();
let signal_ident = idents.name.cpp.to_string();

// Generate the Q_SIGNAL
generated.methods.push(CppFragment::Header(format!(
"Q_SIGNAL void {ident}({parameters});",
ident = signal_ident,
parameters = parameter_types.join(", "),
)));
// Generate the Q_SIGNAL if this is not an existing signal
if !signal.inherit {
generated.methods.push(CppFragment::Header(format!(
"Q_SIGNAL void {ident}({parameters});",
ident = signal_ident,
parameters = parameter_types.join(", "),
)));
}

// Generate the emitters
generated.methods.push(CppFragment::Pair {
Expand Down Expand Up @@ -106,6 +108,8 @@ mod tests {
fn test_generate_cpp_signals() {
let signals = vec![ParsedSignal {
ident: format_ident!("data_changed"),
cxx_name: None,
inherit: false,
parameters: vec![
ParsedFunctionParameter {
ident: format_ident!("trivial"),
Expand Down Expand Up @@ -160,6 +164,8 @@ mod tests {
fn test_generate_cpp_signals_mapped_cxx_name() {
let signals = vec![ParsedSignal {
ident: format_ident!("data_changed"),
cxx_name: None,
inherit: false,
parameters: vec![ParsedFunctionParameter {
ident: format_ident!("mapped"),
ty: tokens_to_syn(quote! { A1 }),
Expand Down Expand Up @@ -200,4 +206,36 @@ mod tests {
"#}
);
}

#[test]
fn test_generate_cpp_signals_existing_cxx_name() {
let signals = vec![ParsedSignal {
ident: format_ident!("ExistingSignal"),
cxx_name: Some("baseName".to_owned()),
inherit: true,
parameters: vec![],
}];
let qobject_idents = create_qobjectname();

let generated =
generate_cpp_signals(&signals, &qobject_idents, &ParsedCxxMappings::default()).unwrap();

assert_eq!(generated.methods.len(), 1);
let (header, source) = if let CppFragment::Pair { header, source } = &generated.methods[0] {
(header, source)
} else {
panic!("Expected Pair")
};
assert_str_eq!(header, "void emitBaseName();");
assert_str_eq!(
source,
indoc! {r#"
void
MyObject::emitBaseName()
{
Q_EMIT baseName();
}
"#}
);
}
}
38 changes: 29 additions & 9 deletions crates/cxx-qt-gen/src/generator/naming/signals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,17 @@ pub struct QSignalName {

impl From<&ParsedSignal> for QSignalName {
fn from(signal: &ParsedSignal) -> Self {
Self::from(&signal.ident)
}
}
// Check if there is a cxx ident that should be used
let cxx_ident = if let Some(cxx_name) = &signal.cxx_name {
format_ident!("{}", cxx_name)
} else {
signal.ident.clone()
};

impl From<&Ident> for QSignalName {
fn from(ident: &Ident) -> Self {
Self {
enum_name: ident.clone(),
name: name_from_ident(ident),
emit_name: emit_name_from_ident(ident),
enum_name: signal.ident.clone(),
name: name_from_ident(&cxx_ident),
emit_name: emit_name_from_ident(&cxx_ident),
}
}
}
Expand Down Expand Up @@ -55,10 +56,12 @@ mod tests {
use super::*;

#[test]
fn test_parsed_property() {
fn test_parsed_signal() {
let qsignal = ParsedSignal {
ident: format_ident!("DataChanged"),
parameters: vec![],
cxx_name: None,
inherit: false,
};

let names = QSignalName::from(&qsignal);
Expand All @@ -68,4 +71,21 @@ mod tests {
assert_eq!(names.emit_name.cpp, format_ident!("emitDataChanged"));
assert_eq!(names.emit_name.rust, format_ident!("emit_data_changed"));
}

#[test]
fn test_parsed_signal_existing_cxx_name() {
let qsignal = ParsedSignal {
ident: format_ident!("ExistingSignal"),
parameters: vec![],
cxx_name: Some("baseName".to_owned()),
inherit: true,
};

let names = QSignalName::from(&qsignal);
assert_eq!(names.enum_name, format_ident!("ExistingSignal"));
assert_eq!(names.name.cpp, format_ident!("baseName"));
assert_eq!(names.name.rust, format_ident!("base_name"));
assert_eq!(names.emit_name.cpp, format_ident!("emitBaseName"));
assert_eq!(names.emit_name.rust, format_ident!("emit_base_name"));
}
}
20 changes: 18 additions & 2 deletions crates/cxx-qt-gen/src/generator/rust/signals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,14 +136,17 @@ mod tests {
UnsafeSignal {
param: *mut T,
},
#[cxx_name = "baseName"]
#[inherit]
ExistingSignal,
}
});
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(), 3);
assert_eq!(generated.cxx_mod_contents.len(), 4);
assert_eq!(generated.cxx_qt_mod_contents.len(), 2);

// Ready
Expand Down Expand Up @@ -179,6 +182,17 @@ mod tests {
},
);

// ExistingSignal
assert_tokens_eq(
&generated.cxx_mod_contents[3],
quote! {
unsafe extern "C++" {
#[rust_name = "emit_base_name"]
fn emitBaseName(self: Pin<&mut MyObjectQt>);
}
},
);

// enum
assert_tokens_eq(
&generated.cxx_qt_mod_contents[0],
Expand All @@ -192,6 +206,7 @@ mod tests {
UnsafeSignal {
param: *mut T,
},
ExistingSignal,
}
},
);
Expand All @@ -203,7 +218,8 @@ mod tests {
match signal {
MySignals::Ready {} => { self.emit_ready() },
MySignals::DataChanged { trivial, opaque } => { self.emit_data_changed(trivial, opaque) },
MySignals::UnsafeSignal { param } => unsafe { self.emit_unsafe_signal(param) }
MySignals::UnsafeSignal { param } => unsafe { self.emit_unsafe_signal(param) },
MySignals::ExistingSignal {} => { self.emit_base_name() }
}
}
}
Expand Down
44 changes: 38 additions & 6 deletions crates/cxx-qt-gen/src/parser/signals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,33 @@ pub struct ParsedSignal {
pub ident: Ident,
/// The parameters of the signal
pub parameters: Vec<ParsedFunctionParameter>,
// TODO: later this will describe if the signal has an attribute
// stating that the signal exists in the base class
/// The name of the signal in C++
pub cxx_name: Option<String>,
/// If the signal is defined in the base class
pub inherit: bool,
}

impl ParsedSignal {
pub fn from(variant: &mut Variant) -> Result<Self> {
// Find cxx_name and inherit
let inherit = if let Some(index) = attribute_find_path(&variant.attrs, &["inherit"]) {
// Remove the attribute from the original enum
// so that it doesn't end up in the Rust generation
variant.attrs.remove(index);
true
} else {
false
};
let cxx_name = if let Some(index) = attribute_find_path(&variant.attrs, &["cxx_name"]) {
let str = attribute_tokens_to_value::<LitStr>(&variant.attrs[index])?.value();
// Remove the attribute from the original enum
// so that it doesn't end up in the Rust generation
variant.attrs.remove(index);
Some(str)
} else {
None
};

// Read the fields into parameter blocks
let parameters = fields_to_named_fields_mut(&mut variant.fields)?
.into_iter()
Expand All @@ -47,11 +68,10 @@ impl ParsedSignal {
.collect::<Result<Vec<ParsedFunctionParameter>>>()?;

Ok(ParsedSignal {
// TODO: later we might have attributes on the signal
// to state if they exist in the base class, these could be
// extracted and stored in ParsedSignal here
ident: variant.ident.clone(),
parameters,
cxx_name,
inherit,
})
}
}
Expand Down Expand Up @@ -136,14 +156,21 @@ mod tests {
#[cxx_type = "f32"]
y: f64
},
#[cxx_name = "baseName"]
#[inherit]
ExistingSignal,
}
});
let signals = ParsedSignalsEnum::from(&e, 0).unwrap();
assert_eq!(signals.ident, "MySignals");
assert_eq!(signals.item.attrs.len(), 0);
assert_eq!(signals.signals.len(), 2);
assert_eq!(signals.signals.len(), 3);
assert!(!signals.signals[0].inherit);
assert!(signals.signals[0].cxx_name.is_none());
assert_eq!(signals.signals[0].ident, "Ready");
assert_eq!(signals.signals[0].parameters.len(), 0);
assert!(!signals.signals[1].inherit);
assert!(signals.signals[1].cxx_name.is_none());
assert_eq!(signals.signals[1].ident, "PointChanged");
assert_eq!(signals.signals[1].parameters.len(), 2);
assert!(signals.signals[1].parameters[0].cxx_type.is_none());
Expand All @@ -155,6 +182,11 @@ mod tests {
);
assert_eq!(signals.signals[1].parameters[1].ident, "y");
assert_eq!(signals.signals[1].parameters[1].ty, f64_type());
assert!(signals.signals[2].inherit);
assert!(signals.signals[2].cxx_name.is_some());
assert_eq!(signals.signals[2].cxx_name.as_ref().unwrap(), "baseName");
assert_eq!(signals.signals[2].ident, "ExistingSignal");
assert_eq!(signals.signals[2].parameters.len(), 0);
}

#[test]
Expand Down
10 changes: 10 additions & 0 deletions crates/cxx-qt-gen/test_inputs/signals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ mod ffi {
third: QPoint,
fourth: &'a QPoint,
},
#[cxx_name = "newData"]
#[inherit]
BaseClassNewData {
first: i32,
// Value and Opaque are not real types that would compile; these are only testing the code generation
#[cxx_type = "Value"]
second: UniquePtr<Opaque>,
third: QPoint,
fourth: &'a QPoint,
},
}

#[cxx_qt::qobject]
Expand Down
16 changes: 16 additions & 0 deletions crates/cxx-qt-gen/test_outputs/signals.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,22 @@ MyObject::emitDataChanged(::std::int32_t first,
::std::move(fourth)));
}

void
MyObject::emitNewData(::std::int32_t first,
::std::unique_ptr<Opaque> second,
QPoint third,
QPoint const& fourth)
{
Q_EMIT newData(
::rust::cxxqtlib1::cxx_qt_convert<::std::int32_t, ::std::int32_t>{}(
::std::move(first)),
::rust::cxxqtlib1::cxx_qt_convert<Value, ::std::unique_ptr<Opaque>>{}(
::std::move(second)),
::rust::cxxqtlib1::cxx_qt_convert<QPoint, QPoint>{}(::std::move(third)),
::rust::cxxqtlib1::cxx_qt_convert<QPoint const&, QPoint const&>{}(
::std::move(fourth)));
}

} // namespace cxx_qt::my_object

namespace cxx_qt::my_object::cxx_qt_my_object {
Expand Down
4 changes: 4 additions & 0 deletions crates/cxx-qt-gen/test_outputs/signals.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ class MyObject : public QObject
::std::unique_ptr<Opaque> second,
QPoint third,
QPoint const& fourth);
void emitNewData(::std::int32_t first,
::std::unique_ptr<Opaque> second,
QPoint third,
QPoint const& fourth);

private:
::rust::Box<MyObjectRust> m_rustObj;
Expand Down
23 changes: 23 additions & 0 deletions crates/cxx-qt-gen/test_outputs/signals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@ mod ffi {
);
}

unsafe extern "C++" {
#[rust_name = "emit_new_data"]
fn emitNewData(
self: Pin<&mut MyObjectQt>,
first: i32,
second: UniquePtr<Opaque>,
third: QPoint,
fourth: &QPoint,
);
}

unsafe extern "C++" {
type MyObjectCxxQtThread;

Expand Down Expand Up @@ -119,6 +130,12 @@ mod cxx_qt_ffi {
third: QPoint,
fourth: &'a QPoint,
},
BaseClassNewData {
first: i32,
second: UniquePtr<Opaque>,
third: QPoint,
fourth: &'a QPoint,
},
}

impl MyObjectQt {
Expand All @@ -131,6 +148,12 @@ mod cxx_qt_ffi {
third,
fourth,
} => self.emit_data_changed(first, second, third, fourth),
MySignals::BaseClassNewData {
first,
second,
third,
fourth,
} => self.emit_new_data(first, second, third, fourth),
}
}
}
Expand Down
Loading

0 comments on commit 48dcc52

Please sign in to comment.