Skip to content

Commit 0c716ce

Browse files
committed
WithUserSignals now generates own collection even if no #[signal] is declared
Necessary due to `&mut self` forwarding, which base collections with Gd<T> can't do. See comments for details.
1 parent e1c8524 commit 0c716ce

File tree

2 files changed

+27
-87
lines changed

2 files changed

+27
-87
lines changed

godot-macros/src/class/data_models/signal.rs

Lines changed: 26 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -391,34 +391,23 @@ fn make_signal_symbols(
391391
// max_visibility: SignalVisibility,
392392
hints: GodotApiHints,
393393
) -> TokenStream {
394-
// If class declares no own signals, just implement the traits, no collection APIs.
395-
if hints.has_typed_signals == Some(false) || collection_api.is_empty() {
396-
let with_signals_impl = make_with_signals_impl_delegated_to_base(class_name);
397-
398-
// base_field_macro! is a macro that expands to all input tokens if the class declares a Base<T> field, and to nothing otherwise.
399-
// This makes sure that WithSignals is only implemented for classes with a base field, and avoids compile errors about it.
400-
401-
return match hints.has_typed_signals {
402-
// The default case: try to infer from #[derive(GodotClass)] declaration whether a Base<T> field is present.
403-
None => {
404-
let base_field_macro = util::format_class_base_field_macro(class_name);
405-
quote! {
406-
#base_field_macro! {
407-
#with_signals_impl
408-
}
409-
}
410-
}
411-
412-
// Hints are useful in cases like `impl nested::MyClass` style remote impls. Here, the decl-macro can't work due to being invisible
413-
// in other scopes (and crate-global #[macro_export] has its own problems). Thus, generate code directly without decl-macro.
414-
Some(true) => quote! { #with_signals_impl },
415-
Some(false) => quote! {},
416-
};
394+
// Return early if typed signals are explicitly disabled.
395+
if hints.has_typed_signals == Some(false) {
396+
return TokenStream::new();
417397
}
418398

419-
// For the regular flow, we *expect* that a Base<T> field is present. Having a concise error message "missing Base<T> field" is
420-
// better than magic tricks, since the user explicitly asked for #[signal] and thus should be guided to complete the missing parts.
399+
// Earlier implementation generated a simplified code when no #[signal] was declared: only WithSignals/WithUserSignals impl, but no own
400+
// collection, instead the associated type pointing to the base class. This has however some problems:
401+
// * Part of the reason for user-defined collection is to store UserSignalObject instead of Gd, which can store &mut self.
402+
// This is necessary for self.signals().some_base_signal().emit(), if such a signal is connected to Self::method_mut;
403+
// Gd would cause a borrow error.
404+
// * Once we add Rust-Rust inheritance, we'd need to differentiate case again, which can be tricky since #[godot_api] has no information
405+
// about the base class.
406+
//
407+
// As compromise, we always generate a collection struct if either at least 1 #[signal] is declared or the struct has a Base<T> field.
408+
// We also provide opt-out via #[godot_api(no_typed_signals)].
421409

410+
let declares_no_signals = collection_api.is_empty();
422411
let collection_struct_name = format_ident!("__godot_Signals_{}", class_name);
423412
let collection_struct_methods = &collection_api.provider_methods;
424413
let with_signals_impl = make_with_signals_impl(class_name, &collection_struct_name);
@@ -458,7 +447,7 @@ fn make_signal_symbols(
458447

459448
let visibility_macro = util::format_class_visibility_macro(class_name);
460449

461-
let code = quote! {
450+
let mut code = quote! {
462451
#visibility_macro! {
463452
#[allow(non_camel_case_types)]
464453
#[doc(hidden)] // Only on struct, not methods, to allow completion in IDEs.
@@ -482,6 +471,17 @@ fn make_signal_symbols(
482471
#( #individual_structs )*
483472
};
484473

474+
// base_field_macro! is a macro that expands to all input tokens if the class declares a Base<T> field, and to nothing otherwise.
475+
// This makes sure that WithSignals is only implemented for classes with a base field, and avoids compile errors about it.
476+
// Only when no #[signal] is declared -> otherwise the user explicitly requests it, and a compile error is better to guide them.
477+
if declares_no_signals {
478+
let base_field_macro = util::format_class_base_field_macro(class_name);
479+
480+
code = quote! {
481+
#base_field_macro! { #code }
482+
};
483+
}
484+
485485
code
486486
}
487487

@@ -514,39 +514,6 @@ fn make_with_signals_impl(class_name: &Ident, collection_struct_name: &Ident) ->
514514
}
515515
}
516516

517-
/// Declare `impl WithSignals` and `impl WithUserSignals`, but without own signal collection; instead delegate to base collection.
518-
fn make_with_signals_impl_delegated_to_base(class_name: &Ident) -> TokenStream {
519-
quote! {
520-
impl ::godot::obj::WithSignals for #class_name {
521-
type SignalCollection<'c, C: ::godot::obj::WithSignals> =
522-
<<Self as ::godot::obj::GodotClass>::Base as ::godot::obj::WithSignals>::SignalCollection<'c, C>;
523-
524-
#[doc(hidden)]
525-
type __SignalObj<'c> =
526-
<<Self as ::godot::obj::GodotClass>::Base as ::godot::obj::WithSignals>::__SignalObj<'c>;
527-
528-
529-
#[doc(hidden)]
530-
fn __signals_from_external(external: &mut ::godot::obj::Gd<Self>) -> Self::SignalCollection<'_, Self> {
531-
// This will need updating when allowing Rust->Rust inheritance.
532-
Self::SignalCollection {
533-
__internal_obj: Some(external.clone().upcast())
534-
}
535-
}
536-
}
537-
538-
impl ::godot::obj::WithUserSignals for #class_name {
539-
fn signals(&mut self) -> Self::SignalCollection<'_, Self> {
540-
// This will need updating when allowing Rust->Rust inheritance.
541-
let self_gd = <Self as ::godot::obj::WithBaseField>::to_gd(self);
542-
Self::SignalCollection {
543-
__internal_obj: Some(self_gd.upcast()),
544-
}
545-
}
546-
}
547-
}
548-
}
549-
550517
fn make_upcast_deref_impl(class_name: &Ident, collection_struct_name: &Ident) -> TokenStream {
551518
quote! {
552519
impl<'c, C: ::godot::obj::WithSignals> std::ops::Deref for #collection_struct_name<'c, C> {

itest/rust/src/builtin_tests/containers/signal_test.rs

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ fn signal_symbols_engine_inherited_no_own_signals() {
336336

337337
let mut sig = obj.signals().property_list_changed();
338338
sig.connect_self(|this: &mut Receiver| {
339-
this.receive_int_mut(941);
339+
this.receive_int(941);
340340
});
341341

342342
obj.notify_property_list_changed();
@@ -345,33 +345,6 @@ fn signal_symbols_engine_inherited_no_own_signals() {
345345
obj.free();
346346
}
347347

348-
// Test that trait is implemented even without own #[signal] declarations (to access base signals).
349-
#[cfg(since_api = "4.2")]
350-
const fn __type_check<'c>() {
351-
use godot::classes::Object;
352-
use godot::obj::WithSignals;
353-
354-
// Needs WithUserSignals, not just WithSignals, to allow self.signals().
355-
const fn has_with_user_signals<T: godot::obj::WithUserSignals>() {}
356-
357-
trait SameType {}
358-
impl<T> SameType for (T, T) {}
359-
const fn is_same_type<T, U>()
360-
where
361-
(T, U): SameType,
362-
{
363-
}
364-
365-
has_with_user_signals::<Receiver>();
366-
367-
// Checks whether there is no new collection defined, but instead the base collection is reused.
368-
// This reduces the amount of proc-macro generated code.
369-
is_same_type::<
370-
<Receiver as WithSignals>::SignalCollection<'c, Receiver>,
371-
<Object as WithSignals>::SignalCollection<'c, Receiver>, //.
372-
>();
373-
}
374-
375348
#[itest]
376349
fn signal_construction_and_id() {
377350
let mut object = RefCounted::new_gd();

0 commit comments

Comments
 (0)