Skip to content

Commit e1c8524

Browse files
committed
Allow #[hint(typed_signals = true|false)] to influence signals being used
1 parent e005e0f commit e1c8524

File tree

5 files changed

+92
-24
lines changed

5 files changed

+92
-24
lines changed

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

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use crate::{handle_mutually_exclusive_keys, util, ParseResult};
1919
use proc_macro2::{Delimiter, Group, Ident, TokenStream};
2020
use quote::spanned::Spanned;
2121
use quote::{format_ident, quote};
22+
use venial::Impl;
2223

2324
/// Attribute for user-declared function.
2425
enum ItemAttrType {
@@ -71,6 +72,11 @@ struct SignalAttr {
7172
pub no_builder: bool,
7273
}
7374

75+
#[derive(Default)]
76+
pub(crate) struct GodotApiHints {
77+
pub has_typed_signals: Option<bool>,
78+
}
79+
7480
// ----------------------------------------------------------------------------------------------------------------------------------------------
7581

7682
pub struct InherentImplAttr {
@@ -89,6 +95,8 @@ pub fn transform_inherent_impl(
8995
let class_name_obj = util::class_name_obj(&class_name);
9096
let prv = quote! { ::godot::private };
9197

98+
let hints = extract_hint_attribute(&mut impl_block)?;
99+
92100
// Can add extra functions to the end of the impl block.
93101
let (funcs, signals) = process_godot_fns(&class_name, &mut impl_block, meta.secondary)?;
94102
let consts = process_godot_constants(&mut impl_block)?;
@@ -108,7 +116,7 @@ pub fn transform_inherent_impl(
108116
// For each #[func] in this impl block, create one constant.
109117
let func_name_constants = make_funcs_collection_constants(&funcs, &class_name);
110118
let (signal_registrations, signal_symbol_types) =
111-
make_signal_registrations(&signals, &class_name, &class_name_obj)?;
119+
make_signal_registrations(&signals, &class_name, &class_name_obj, hints)?;
112120

113121
#[cfg(feature = "codegen-full")]
114122
let rpc_registrations = crate::class::make_rpc_registrations_fn(&class_name, &funcs);
@@ -206,6 +214,20 @@ pub fn transform_inherent_impl(
206214
}
207215
}
208216

217+
fn extract_hint_attribute(impl_block: &mut Impl) -> ParseResult<GodotApiHints> {
218+
// Could possibly be extended with #[hint(signal_vis = pub)] or so.
219+
220+
// #[hint(typed_signals)]
221+
let has_typed_signals;
222+
if let Some(mut hints) = KvParser::parse_remove(&mut impl_block.attributes, "hint")? {
223+
has_typed_signals = hints.handle_bool("typed_signals")?;
224+
} else {
225+
has_typed_signals = None;
226+
}
227+
228+
Ok(GodotApiHints { has_typed_signals })
229+
}
230+
209231
fn process_godot_fns(
210232
class_name: &Ident,
211233
impl_block: &mut venial::Impl,

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

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
// Some duplication with godot-codegen/signals.rs; see comments there.
99

10+
use crate::class::GodotApiHints;
1011
use crate::util::bail;
1112
use crate::{util, ParseResult};
1213
use proc_macro2::{Delimiter, Ident, TokenStream, TokenTree};
@@ -182,6 +183,7 @@ pub fn make_signal_registrations(
182183
signals: &[SignalDefinition],
183184
class_name: &Ident,
184185
class_name_obj: &TokenStream,
186+
hints: GodotApiHints,
185187
) -> ParseResult<(Vec<TokenStream>, Option<TokenStream>)> {
186188
let mut signal_registrations = Vec::new();
187189

@@ -211,7 +213,7 @@ pub fn make_signal_registrations(
211213
}
212214

213215
#[cfg(since_api = "4.2")]
214-
let signal_symbols = Some(make_signal_symbols(class_name, collection_api));
216+
let signal_symbols = Some(make_signal_symbols(class_name, collection_api, hints));
215217
#[cfg(before_api = "4.2")]
216218
let signal_symbols = None;
217219

@@ -387,20 +389,30 @@ fn make_signal_symbols(
387389
class_name: &Ident,
388390
collection_api: SignalCollection,
389391
// max_visibility: SignalVisibility,
392+
hints: GodotApiHints,
390393
) -> TokenStream {
391394
// If class declares no own signals, just implement the traits, no collection APIs.
392-
if collection_api.is_empty() {
395+
if hints.has_typed_signals == Some(false) || collection_api.is_empty() {
393396
let with_signals_impl = make_with_signals_impl_delegated_to_base(class_name);
394-
let base_field_macro = util::format_class_base_field_macro(class_name);
395397

396398
// base_field_macro! is a macro that expands to all input tokens if the class declares a Base<T> field, and to nothing otherwise.
397399
// This makes sure that WithSignals is only implemented for classes with a base field, and avoids compile errors about it.
398-
// Note: this doesn't work with `impl nested::MyClass` style remote impls, which can be enabled via #[hint(signals=true/false)].
399400

400-
return quote! {
401-
#base_field_macro! {
402-
#with_signals_impl
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+
}
403410
}
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! {},
404416
};
405417
}
406418

godot-macros/src/util/kv_parser.rs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,16 @@ impl KvParser {
3838
}
3939

4040
/// Create a new parser which checks for presence of an `#[expected]` attribute.
41+
///
42+
/// Returns `Ok(None)` if the attribute is not present.
4143
pub fn parse(attributes: &[venial::Attribute], expected: &str) -> ParseResult<Option<Self>> {
4244
let mut found_attr: Option<Self> = None;
4345

4446
for attr in attributes.iter() {
4547
let path = &attr.path;
4648
if path_is_single(path, expected) {
4749
if found_attr.is_some() {
48-
return bail!(attr, "only a single #[{expected}] attribute allowed",);
50+
return bail!(attr, "only a single #[{expected}] attribute allowed");
4951
}
5052

5153
let attr_name = expected.to_string();
@@ -59,6 +61,39 @@ impl KvParser {
5961
Ok(found_attr)
6062
}
6163

64+
/// Like `parse()`, but removes the attribute from the list.
65+
///
66+
/// Useful for `#[proc_macro_attributes]`, where handled attributes must not show up in resulting code.
67+
pub fn parse_remove(
68+
attributes: &mut Vec<venial::Attribute>,
69+
expected: &str,
70+
) -> ParseResult<Option<Self>> {
71+
let mut found_attr: Option<Self> = None;
72+
let mut found_index: Option<usize> = None;
73+
74+
for (i, attr) in attributes.iter().enumerate() {
75+
let path = &attr.path;
76+
if path_is_single(path, expected) {
77+
if found_attr.is_some() {
78+
return bail!(attr, "only a single #[{expected}] attribute allowed");
79+
}
80+
81+
let attr_name = expected.to_string();
82+
found_index = Some(i);
83+
found_attr = Some(Self {
84+
span: attr.tk_brackets.span,
85+
map: ParserState::parse(attr_name, &attr.value)?,
86+
});
87+
}
88+
}
89+
90+
if let Some(index) = found_index {
91+
attributes.remove(index);
92+
}
93+
94+
Ok(found_attr)
95+
}
96+
6297
pub fn span(&self) -> Span {
6398
self.span
6499
}

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

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -331,28 +331,26 @@ fn signal_symbols_engine_inherited_internal() {
331331
// Verifies the code path that only generates the traits, no dedicated signal collection.
332332
#[cfg(since_api = "4.2")]
333333
#[itest]
334-
fn signal_symbols_engine_inherited_no_own_signals(ctx: &crate::framework::TestContext) {
335-
let mut node = Receiver::new_alloc();
334+
fn signal_symbols_engine_inherited_no_own_signals() {
335+
let mut obj = Receiver::new_alloc();
336336

337-
// Add to tree, so signals are propagated.
338-
ctx.scene_tree.clone().add_child(&node);
339-
340-
let mut sig = node.signals().renamed();
341-
sig.connect_self(|this: &mut Emitter| {
342-
this.last_received_int = 887;
337+
let mut sig = obj.signals().property_list_changed();
338+
sig.connect_self(|this: &mut Receiver| {
339+
this.receive_int_mut(941);
343340
});
344341

345-
node.set_name("new name");
346-
347-
assert_eq!(node.bind().last_received_int, 887);
342+
obj.notify_property_list_changed();
343+
assert_eq!(obj.bind().last_received.get(), LastReceived::Int(941));
348344

349-
// Remove from tree for other tests.
350-
node.free();
345+
obj.free();
351346
}
352347

353348
// Test that trait is implemented even without own #[signal] declarations (to access base signals).
354349
#[cfg(since_api = "4.2")]
355350
const fn __type_check<'c>() {
351+
use godot::classes::Object;
352+
use godot::obj::WithSignals;
353+
356354
// Needs WithUserSignals, not just WithSignals, to allow self.signals().
357355
const fn has_with_user_signals<T: godot::obj::WithUserSignals>() {}
358356

@@ -369,8 +367,8 @@ const fn __type_check<'c>() {
369367
// Checks whether there is no new collection defined, but instead the base collection is reused.
370368
// This reduces the amount of proc-macro generated code.
371369
is_same_type::<
372-
Receiver::SignalCollection<'c, Receiver>,
373-
Object::SignalCollection<'c, Receiver>, //.
370+
<Receiver as WithSignals>::SignalCollection<'c, Receiver>,
371+
<Object as WithSignals>::SignalCollection<'c, Receiver>, //.
374372
>();
375373
}
376374

itest/rust/src/object_tests/object_test.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -998,6 +998,7 @@ pub mod object_test_gd {
998998
use nested::ObjectTest;
999999

10001000
#[godot_api]
1001+
#[hint(typed_signals = false)] // Allows nested::ObjectTest, which would fail otherwise due to generated decl-macro being out-of-scope.
10011002
impl nested::ObjectTest {
10021003
#[func]
10031004
fn pass_object(&self, object: Gd<Object>) -> i64 {

0 commit comments

Comments
 (0)