Skip to content

Commit 1dc6f71

Browse files
Allow immutable signals, and add one to qml_features
1 parent 48acbdb commit 1dc6f71

File tree

10 files changed

+201
-19
lines changed

10 files changed

+201
-19
lines changed

crates/cxx-qt-gen/src/generator/cpp/signal.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ fn parameter_types_and_values(
5151
parameters: &[ParsedFunctionParameter],
5252
type_names: &TypeNames,
5353
self_ty: &Name,
54+
mutable: bool,
5455
) -> Result<Parameters> {
5556
let mut parameter_named_types_with_self = vec![];
5657
let mut parameter_types_with_self = vec![];
@@ -66,10 +67,11 @@ fn parameter_types_and_values(
6667

6768
let parameter_named_types = parameter_named_types_with_self.join(", ");
6869

70+
let is_const = if mutable { "" } else { " const" };
6971
// Insert the extra argument into the closure
7072
let self_ty = self_ty.cxx_qualified();
71-
parameter_named_types_with_self.insert(0, format!("{self_ty}& self"));
72-
parameter_types_with_self.insert(0, format!("{self_ty}&"));
73+
parameter_named_types_with_self.insert(0, format!("{self_ty}{is_const}& self"));
74+
parameter_types_with_self.insert(0, format!("{self_ty}{is_const}&"));
7375
parameter_values_with_self.insert(0, "self".to_owned());
7476

7577
Ok(Parameters {
@@ -109,7 +111,8 @@ pub fn generate_cpp_signal(
109111
let free_connect_ident_cpp = idents_helper.connect_name.cxx_unqualified();
110112

111113
// Retrieve the parameters for the signal
112-
let parameters = parameter_types_and_values(&signal.parameters, type_names, qobject_name)?;
114+
let parameters =
115+
parameter_types_and_values(&signal.parameters, type_names, qobject_name, signal.mutable)?;
113116
let parameters_named_types = parameters.named_types;
114117
let parameters_named_types_with_self = parameters.named_types_with_self;
115118
let parameter_types_with_self = parameters.types_with_self;
@@ -121,6 +124,8 @@ pub fn generate_cpp_signal(
121124
let signal_handler_call = idents_helper.function_call;
122125
let signal_handler_drop = idents_helper.function_drop;
123126
let namespace = idents_helper.namespace;
127+
let reference_type = if signal.mutable { "&" } else { " const &" };
128+
let is_const = if signal.mutable { "" } else { " const" };
124129

125130
let signal_handler_type = format!("SignalHandler<::{namespace}::{param_struct} *>");
126131

@@ -135,7 +140,7 @@ pub fn generate_cpp_signal(
135140
// Generate the Q_SIGNAL if this is not an existing signal
136141
if !signal.inherit {
137142
generated.methods.push(CppFragment::Header(format!(
138-
"Q_SIGNAL void {signal_ident}({parameters_named_types});"
143+
"Q_SIGNAL void {signal_ident}({parameters_named_types}){is_const};"
139144
)));
140145
}
141146

@@ -144,7 +149,7 @@ pub fn generate_cpp_signal(
144149
r#"
145150
namespace {namespace} {{
146151
::QMetaObject::Connection
147-
{free_connect_ident_cpp}({qobject_ident_namespaced}& self, {signal_handler_alias_namespaced} closure, ::Qt::ConnectionType type);
152+
{free_connect_ident_cpp}({qobject_ident_namespaced}{reference_type} self, {signal_handler_alias_namespaced} closure, ::Qt::ConnectionType type);
148153
}} // namespace {namespace}
149154
"#
150155
},
@@ -177,7 +182,7 @@ pub fn generate_cpp_signal(
177182
178183
namespace {namespace} {{
179184
::QMetaObject::Connection
180-
{free_connect_ident_cpp}({qobject_ident_namespaced}& self, {signal_handler_alias_namespaced} closure, ::Qt::ConnectionType type)
185+
{free_connect_ident_cpp}({qobject_ident_namespaced}{reference_type} self, {signal_handler_alias_namespaced} closure, ::Qt::ConnectionType type)
181186
{{
182187
return ::QObject::connect(
183188
&self,

crates/cxx-qt-gen/src/generator/rust/signals.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,7 @@ pub fn generate_rust_signal(
8585
let self_type_cxx = if signal.mutable {
8686
parse_quote_spanned! {span => Pin<&mut #qobject_name_rust> }
8787
} else {
88-
// CODECOV_EXCLUDE_START
89-
unreachable!("Signals cannot be immutable right now so this cannot be reached")
90-
// CODECOV_EXCLUDE_STOP
88+
parse_quote_spanned! {span => &#qobject_name_rust }
9189
};
9290
let self_type_qualified = syn_type_cxx_bridge_to_qualified(&self_type_cxx, type_names)?;
9391
let qualified_impl = qobject_name.rust_qualified();

crates/cxx-qt-gen/src/parser/externcxxqt.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,8 @@ impl ParsedExternCxxQt {
108108
if attribute_get_path(&foreign_fn.attrs, &["inherit"]).is_some() {
109109
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"));
110110
}
111-
let mut signal = ParsedSignal::parse(foreign_fn, auto_case)?;
111+
println!("Parsed here");
112+
let mut signal = ParsedSignal::parse_with_mutability(foreign_fn, auto_case, true)?;
112113
// extern "C++Qt" signals are always inherit = true
113114
// as they always exist on an existing QObject
114115
signal.inherit = true;

crates/cxx-qt-gen/src/parser/signals.rs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ use crate::{
99
};
1010
use core::ops::Deref;
1111
use std::ops::DerefMut;
12-
use syn::{spanned::Spanned, Attribute, Error, ForeignItemFn, Result, Visibility};
12+
use syn::spanned::Spanned;
13+
use syn::{Attribute, Error, ForeignItemFn, Result, Visibility};
1314

1415
#[derive(Clone)]
1516
/// Describes an individual Signal
@@ -36,16 +37,21 @@ impl ParsedSignal {
3637
Self::parse(method.clone(), CaseConversion::none()).unwrap()
3738
}
3839

39-
pub fn parse(method: ForeignItemFn, auto_case: CaseConversion) -> Result<Self> {
40+
pub fn parse_with_mutability(
41+
method: ForeignItemFn,
42+
auto_case: CaseConversion,
43+
immutable_allowed: bool,
44+
) -> Result<Self> {
4045
let docs = extract_docs(&method.attrs);
4146
let cfgs = extract_cfgs(&method.attrs);
4247
let fields = MethodFields::parse(method, auto_case)?;
4348
let attrs = require_attributes(&fields.method.attrs, &Self::ALLOWED_ATTRS)?;
4449

45-
if !fields.mutable {
50+
// TODO: add proper checks
51+
if !fields.mutable && !immutable_allowed {
4652
return Err(Error::new(
4753
fields.method.span(),
48-
"signals must be mutable, use Pin<&mut T> instead of T for the self type",
54+
"immutable signals can only be used in `unsafe extern \"C++Qt\"` blocks, use Pin<&mut T> instead of T for the self type, or change the type of this extern block",
4955
));
5056
}
5157

@@ -65,6 +71,10 @@ impl ParsedSignal {
6571
cfgs,
6672
})
6773
}
74+
75+
pub fn parse(method: ForeignItemFn, auto_case: CaseConversion) -> Result<Self> {
76+
Self::parse_with_mutability(method, auto_case, false)
77+
}
6878
}
6979

7080
impl Deref for ParsedSignal {
@@ -96,18 +106,16 @@ mod tests {
96106
assert_parse_errors! {
97107
|input| ParsedSignal::parse(input, CaseConversion::none()) =>
98108

99-
// No immutable signals
100-
{ fn ready(self: &MyObject); }
109+
// No namespaces
101110
{
102-
// No namespaces
103111
#[namespace = "disallowed_namespace"]
104112
fn ready(self: Pin<&mut MyObject>);
105113
}
106114
// Missing self
107115
{ fn ready(x: f64); }
108-
// Self needs to be receiver like self: &T instead of &self
116+
// Immutable signals must be in "C++Qt" blocks
109117
{ fn ready(&self); }
110-
}
118+
};
111119
}
112120

113121
#[test]

crates/cxx-qt-gen/test_inputs/signals.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ mod ffi {
1717
/// When the QTimer timeout occurs
1818
#[qsignal]
1919
pub(self) fn timeout(self: Pin<&mut Self>);
20+
21+
/// A constant signal for when the timer is ready
22+
#[qsignal]
23+
fn const_ready(&self);
24+
2025
}
2126

2227
unsafe extern "RustQt" {

crates/cxx-qt-gen/test_outputs/signals.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,60 @@ QTimer_timeoutConnect(
5656
}
5757
} // namespace cxx_qt::my_object::rust::cxxqtgen1
5858

59+
// Define namespace otherwise we hit a GCC bug
60+
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56480
61+
namespace rust::cxxqt1 {
62+
template<>
63+
SignalHandler<::cxx_qt::my_object::rust::cxxqtgen1::
64+
QTimerCxxQtSignalParamsconst_ready*>::~SignalHandler() noexcept
65+
{
66+
if (data[0] == nullptr && data[1] == nullptr) {
67+
return;
68+
}
69+
70+
drop_QTimer_signal_handler_const_ready(::std::move(*this));
71+
}
72+
73+
template<>
74+
template<>
75+
void
76+
SignalHandler<
77+
::cxx_qt::my_object::rust::cxxqtgen1::QTimerCxxQtSignalParamsconst_ready*>::
78+
operator()<cxx_qt::my_object::QTimer const&>(
79+
cxx_qt::my_object::QTimer const& self)
80+
{
81+
call_QTimer_signal_handler_const_ready(*this, self);
82+
}
83+
84+
static_assert(alignof(SignalHandler<::cxx_qt::my_object::rust::cxxqtgen1::
85+
QTimerCxxQtSignalParamsconst_ready*>) <=
86+
alignof(::std::size_t),
87+
"unexpected aligment");
88+
static_assert(sizeof(SignalHandler<::cxx_qt::my_object::rust::cxxqtgen1::
89+
QTimerCxxQtSignalParamsconst_ready*>) ==
90+
sizeof(::std::size_t[2]),
91+
"unexpected size");
92+
} // namespace rust::cxxqt1
93+
94+
namespace cxx_qt::my_object::rust::cxxqtgen1 {
95+
::QMetaObject::Connection
96+
QTimer_const_readyConnect(
97+
cxx_qt::my_object::QTimer const& self,
98+
::cxx_qt::my_object::rust::cxxqtgen1::QTimerCxxQtSignalHandlerconst_ready
99+
closure,
100+
::Qt::ConnectionType type)
101+
{
102+
return ::QObject::connect(
103+
&self,
104+
&cxx_qt::my_object::QTimer::const_ready,
105+
&self,
106+
[&, closure = ::std::move(closure)]() mutable {
107+
closure.template operator()<cxx_qt::my_object::QTimer const&>(self);
108+
},
109+
type);
110+
}
111+
} // namespace cxx_qt::my_object::rust::cxxqtgen1
112+
59113
// Define namespace otherwise we hit a GCC bug
60114
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56480
61115
namespace rust::cxxqt1 {

crates/cxx-qt-gen/test_outputs/signals.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ using QTimerCxxQtSignalHandlertimeout =
2929
::rust::cxxqt1::SignalHandler<struct QTimerCxxQtSignalParamstimeout*>;
3030
} // namespace cxx_qt::my_object::rust::cxxqtgen1
3131

32+
namespace cxx_qt::my_object::rust::cxxqtgen1 {
33+
using QTimerCxxQtSignalHandlerconst_ready =
34+
::rust::cxxqt1::SignalHandler<struct QTimerCxxQtSignalParamsconst_ready*>;
35+
} // namespace cxx_qt::my_object::rust::cxxqtgen1
36+
3237
#include "directory/file_ident.cxx.h"
3338

3439
namespace cxx_qt::my_object::rust::cxxqtgen1 {
@@ -39,6 +44,15 @@ QTimer_timeoutConnect(
3944
::Qt::ConnectionType type);
4045
} // namespace cxx_qt::my_object::rust::cxxqtgen1
4146

47+
namespace cxx_qt::my_object::rust::cxxqtgen1 {
48+
::QMetaObject::Connection
49+
QTimer_const_readyConnect(
50+
cxx_qt::my_object::QTimer const& self,
51+
::cxx_qt::my_object::rust::cxxqtgen1::QTimerCxxQtSignalHandlerconst_ready
52+
closure,
53+
::Qt::ConnectionType type);
54+
} // namespace cxx_qt::my_object::rust::cxxqtgen1
55+
4256
namespace cxx_qt::my_object::rust::cxxqtgen1 {
4357
::QMetaObject::Connection
4458
MyObject_readyConnect(

crates/cxx-qt-gen/test_outputs/signals.rs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,38 @@ mod ffi {
224224
self_value: Pin<&mut QTimer>,
225225
);
226226
}
227+
unsafe extern "C++" {
228+
#[cxx_name = "const_ready"]
229+
#[doc = " A constant signal for when the timer is ready"]
230+
#[namespace = "cxx_qt::my_object"]
231+
fn const_ready(self: &QTimer);
232+
}
233+
unsafe extern "C++" {
234+
#[doc(hidden)]
235+
#[namespace = "cxx_qt::my_object::rust::cxxqtgen1"]
236+
type QTimerCxxQtSignalHandlerconst_ready<'a> = cxx_qt::signalhandler::CxxQtSignalHandler<
237+
'a,
238+
super::QTimerCxxQtSignalClosureconst_ready,
239+
>;
240+
#[doc(hidden)]
241+
#[namespace = "cxx_qt::my_object::rust::cxxqtgen1"]
242+
#[cxx_name = "QTimer_const_readyConnect"]
243+
fn QTimer_connect_const_ready(
244+
self_value: &QTimer,
245+
signal_handler: QTimerCxxQtSignalHandlerconst_ready,
246+
conn_type: CxxQtConnectionType,
247+
) -> CxxQtQMetaObjectConnection;
248+
}
249+
#[namespace = "cxx_qt::my_object::rust::cxxqtgen1"]
250+
extern "Rust" {
251+
#[doc(hidden)]
252+
fn drop_QTimer_signal_handler_const_ready(handler: QTimerCxxQtSignalHandlerconst_ready);
253+
#[doc(hidden)]
254+
fn call_QTimer_signal_handler_const_ready(
255+
handler: &mut QTimerCxxQtSignalHandlerconst_ready,
256+
self_value: &QTimer,
257+
);
258+
}
227259
extern "C++" {
228260
#[doc(hidden)]
229261
#[namespace = ""]
@@ -559,3 +591,62 @@ cxx_qt::static_assertions::assert_eq_size!(
559591
cxx_qt::signalhandler::CxxQtSignalHandler<QTimerCxxQtSignalClosuretimeout>,
560592
[usize; 2]
561593
);
594+
impl ffi::QTimer {
595+
#[doc = "Connect the given function pointer to the signal "]
596+
#[doc = "const_ready"]
597+
#[doc = ", so that when the signal is emitted the function pointer is executed."]
598+
pub fn connect_const_ready<'a, F: FnMut(&ffi::QTimer) + 'a + Send>(
599+
self: &ffi::QTimer,
600+
closure: F,
601+
conn_type: cxx_qt::ConnectionType,
602+
) -> cxx_qt::QScopedMetaObjectConnectionGuard<'a> {
603+
cxx_qt::QScopedMetaObjectConnectionGuard::from(ffi::QTimer_connect_const_ready(
604+
self,
605+
cxx_qt::signalhandler::CxxQtSignalHandler::<QTimerCxxQtSignalClosureconst_ready>::new(
606+
Box::new(closure),
607+
),
608+
conn_type,
609+
))
610+
}
611+
}
612+
impl ffi::QTimer {
613+
#[doc = "Connect the given function pointer to the signal "]
614+
#[doc = "const_ready"]
615+
#[doc = ", so that when the signal is emitted the function pointer is executed."]
616+
#[doc = "\n"]
617+
#[doc = "Note that this method uses a AutoConnection connection type."]
618+
pub fn on_const_ready<'a, F: FnMut(&ffi::QTimer) + 'a + Send>(
619+
self: &ffi::QTimer,
620+
closure: F,
621+
) -> cxx_qt::QScopedMetaObjectConnectionGuard<'a> {
622+
cxx_qt::QScopedMetaObjectConnectionGuard::from(ffi::QTimer_connect_const_ready(
623+
self,
624+
cxx_qt::signalhandler::CxxQtSignalHandler::<QTimerCxxQtSignalClosureconst_ready>::new(
625+
Box::new(closure),
626+
),
627+
cxx_qt::ConnectionType::AutoConnection,
628+
))
629+
}
630+
}
631+
#[doc(hidden)]
632+
pub struct QTimerCxxQtSignalClosureconst_ready {}
633+
impl cxx_qt::signalhandler::CxxQtSignalHandlerClosure for QTimerCxxQtSignalClosureconst_ready {
634+
type Id =
635+
cxx::type_id!("::cxx_qt::my_object::rust::cxxqtgen1::QTimerCxxQtSignalHandlerconst_ready");
636+
type FnType<'a> = dyn FnMut(&ffi::QTimer) + 'a + Send;
637+
}
638+
use core::mem::drop as drop_QTimer_signal_handler_const_ready;
639+
fn call_QTimer_signal_handler_const_ready(
640+
handler: &mut cxx_qt::signalhandler::CxxQtSignalHandler<QTimerCxxQtSignalClosureconst_ready>,
641+
self_value: &ffi::QTimer,
642+
) {
643+
handler.closure()(self_value);
644+
}
645+
cxx_qt::static_assertions::assert_eq_align!(
646+
cxx_qt::signalhandler::CxxQtSignalHandler<QTimerCxxQtSignalClosureconst_ready>,
647+
usize
648+
);
649+
cxx_qt::static_assertions::assert_eq_size!(
650+
cxx_qt::signalhandler::CxxQtSignalHandler<QTimerCxxQtSignalClosureconst_ready>,
651+
[usize; 2]
652+
);

examples/qml_features/cpp/external_qobject.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,5 @@ class ExternalQObject : public QObject
2222
Q_SIGNALS:
2323
void triggered();
2424
void triggeredPrivateSignal(QPrivateSignal);
25+
void triggeredConstSignal() const;
2526
};

examples/qml_features/rust/src/externcxxqt.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ pub mod ffi {
2222
#[qsignal]
2323
fn triggered(self: Pin<&mut Self>);
2424

25+
/// const signal that is emitted when trigger is fired
26+
#[qsignal]
27+
#[rust_name = "triggered_const_signal"]
28+
fn triggeredConstSignal(&self);
29+
2530
/// Private signal that is emitted when trigger is fired
2631
#[qsignal]
2732
#[rust_name = "triggered_private_signal"]

0 commit comments

Comments
 (0)