Skip to content

Commit aed6925

Browse files
authored
Merge pull request #1150 from godot-rust/qol/emit-asarg
Generated `emit()` functions now take `impl AsArg<T>`
2 parents 2bc0872 + 686918f commit aed6925

File tree

11 files changed

+165
-6
lines changed

11 files changed

+165
-6
lines changed

godot-core/src/builtin/collections/array.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1137,6 +1137,10 @@ impl<T: ArrayElement> ParamType for Array<T> {
11371137
fn arg_to_ref<'r>(arg: &'r Self::Arg<'_>) -> &'r Self {
11381138
arg.cow_as_ref()
11391139
}
1140+
1141+
fn arg_into_owned(arg: Self::Arg<'_>) -> Self {
1142+
arg.cow_into_owned()
1143+
}
11401144
}
11411145

11421146
impl<T: ArrayElement> GodotConvert for Array<T> {

godot-core/src/meta/args/as_arg.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,17 @@ macro_rules! arg_into_ref {
9090
#[macro_export]
9191
macro_rules! arg_into_owned {
9292
($arg_variable:ident) => {
93+
// Non-generic version allows type inference. Only applicable for CowArg types.
9394
let $arg_variable = $arg_variable.into_arg();
9495
let $arg_variable = $arg_variable.cow_into_owned();
95-
// cow_into_owned() is not yet used generically; could be abstracted in ParamType::arg_to_owned() as well.
96+
};
97+
($arg_variable:ident: $T:ty) => {
98+
let $arg_variable = $arg_variable.into_arg();
99+
let $arg_variable: $T = $crate::meta::ParamType::arg_into_owned($arg_variable);
100+
};
101+
(infer $arg_variable:ident) => {
102+
let $arg_variable = $arg_variable.into_arg();
103+
let $arg_variable = $crate::meta::ParamType::arg_into_owned($arg_variable);
96104
};
97105
}
98106

@@ -116,6 +124,10 @@ macro_rules! impl_asarg_by_value {
116124
fn arg_to_ref<'r>(arg: &'r Self::Arg<'_>) -> &'r Self {
117125
arg
118126
}
127+
128+
fn arg_into_owned(arg: Self::Arg<'_>) -> Self {
129+
arg
130+
}
119131
}
120132
};
121133
}
@@ -149,6 +161,10 @@ macro_rules! impl_asarg_by_ref {
149161
fn arg_to_ref<'r>(arg: &'r Self::Arg<'_>) -> &'r Self {
150162
arg.cow_as_ref()
151163
}
164+
165+
fn arg_into_owned(arg: Self::Arg<'_>) -> Self {
166+
arg.cow_into_owned()
167+
}
152168
}
153169
};
154170
}
@@ -280,4 +296,10 @@ pub trait ParamType: sealed::Sealed + Sized + 'static
280296
/// Useful in generic contexts where you need to extract a reference of an argument, independently of how it is passed.
281297
#[doc(hidden)] // for now, users are encouraged to use only call-site of impl AsArg; declaration-site may still develop.
282298
fn arg_to_ref<'r>(arg: &'r Self::Arg<'_>) -> &'r Self;
299+
300+
/// Clones an argument into an owned value.
301+
///
302+
/// Useful in generic contexts where you need to extract a value of an argument, independently of how it is passed.
303+
#[doc(hidden)] // for now, users are encouraged to use only call-site of impl AsArg; declaration-site may still develop.
304+
fn arg_into_owned(arg: Self::Arg<'_>) -> Self;
283305
}

godot-core/src/meta/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,10 @@ pub(crate) use traits::{
7272
use crate::registry::method::MethodParamOrReturnInfo;
7373

7474
pub(crate) use crate::{
75-
arg_into_owned, arg_into_ref, declare_arg_method, impl_asarg_by_ref, impl_asarg_by_value,
76-
impl_godot_as_self,
75+
arg_into_ref, declare_arg_method, impl_asarg_by_ref, impl_asarg_by_value, impl_godot_as_self,
7776
};
77+
// Public due to signals emit() needing it. Should be made pub(crate) again if that changes.
78+
pub use crate::arg_into_owned;
7879

7980
#[doc(hidden)]
8081
pub use signature::*;

godot-core/src/obj/dyn_gd.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,23 @@ where
543543
}
544544
}
545545

546+
/*
547+
// See `impl AsArg for Gd<T>` for why this isn't yet implemented.
548+
impl<'r, T, TBase, D> meta::AsArg<DynGd<TBase, D>> for &'r DynGd<T, D>
549+
where
550+
T: Inherits<TBase>,
551+
TBase: GodotClass,
552+
D: ?Sized + 'static,
553+
{
554+
fn into_arg<'cow>(self) -> meta::CowArg<'cow, DynGd<TBase, D>>
555+
where
556+
'r: 'cow,
557+
{
558+
meta::CowArg::Owned(self.clone().upcast::<TBase>())
559+
}
560+
}
561+
*/
562+
546563
impl<T, D> meta::ParamType for DynGd<T, D>
547564
where
548565
T: GodotClass,
@@ -557,6 +574,10 @@ where
557574
fn arg_to_ref<'r>(arg: &'r Self::Arg<'_>) -> &'r Self {
558575
arg.cow_as_ref()
559576
}
577+
578+
fn arg_into_owned(arg: Self::Arg<'_>) -> Self {
579+
arg.cow_into_owned()
580+
}
560581
}
561582

562583
impl<T, D> meta::ArrayElement for DynGd<T, D>

godot-core/src/obj/gd.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -852,6 +852,30 @@ impl<'r, T: GodotClass> AsArg<Gd<T>> for &'r Gd<T> {
852852
}
853853
}
854854

855+
/*
856+
// TODO find a way to generalize AsArg to derived->base conversions without breaking type inference in array![].
857+
// Possibly we could use a "canonical type" with unambiguous mapping (&Gd<T> -> &Gd<T>, not &Gd<T> -> &Gd<TBase>).
858+
// See also regression test in array_test.rs.
859+
860+
impl<'r, T, TBase> AsArg<Gd<TBase>> for &'r Gd<T>
861+
where
862+
T: Inherits<TBase>,
863+
TBase: GodotClass,
864+
{
865+
#[doc(hidden)] // Repeated despite already hidden in trait; some IDEs suggest this otherwise.
866+
fn into_arg<'cow>(self) -> CowArg<'cow, Gd<TBase>>
867+
where
868+
'r: 'cow, // Original reference must be valid for at least as long as the returned cow.
869+
{
870+
// Performance: clones unnecessarily, which has overhead for ref-counted objects.
871+
// A result of being generic over base objects and allowing T: Inherits<Base> rather than just T == Base.
872+
// Was previously `CowArg::Borrowed(self)`. Borrowed() can maybe be specialized for objects, or combined with AsObjectArg.
873+
874+
CowArg::Owned(self.clone().upcast::<TBase>())
875+
}
876+
}
877+
*/
878+
855879
impl<T: GodotClass> ParamType for Gd<T> {
856880
type Arg<'v> = CowArg<'v, Gd<T>>;
857881

@@ -862,6 +886,10 @@ impl<T: GodotClass> ParamType for Gd<T> {
862886
fn arg_to_ref<'r>(arg: &'r Self::Arg<'_>) -> &'r Self {
863887
arg.cow_as_ref()
864888
}
889+
890+
fn arg_into_owned(arg: Self::Arg<'_>) -> Self {
891+
arg.cow_into_owned()
892+
}
865893
}
866894

867895
impl<T: GodotClass> AsArg<Option<Gd<T>>> for Option<&Gd<T>> {
@@ -884,6 +912,10 @@ impl<T: GodotClass> ParamType for Option<Gd<T>> {
884912
fn arg_to_ref<'r>(arg: &'r Self::Arg<'_>) -> &'r Self {
885913
arg.cow_as_ref()
886914
}
915+
916+
fn arg_into_owned(arg: Self::Arg<'_>) -> Self {
917+
arg.cow_into_owned()
918+
}
887919
}
888920

889921
impl<T> Default for Gd<T>

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

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,8 +320,35 @@ impl SignalCollection {
320320
}
321321
}
322322

323+
fn make_asarg_params(params: &venial::Punctuated<venial::FnParam>) -> TokenStream {
324+
// Could be specialized by trying to parse types, but won't be 100% accurate due to lack of semantics (AsArg could be a safe fallback). E.g.:
325+
// if ty.tokens.iter().any(|tk| matches!(tk, TokenTree::Ident(ident) if ident == "Gd")) {
326+
// quote! { impl ::godot::meta::AsObjectArg<#some_inner_ty> }
327+
// }
328+
329+
let mut tokens = TokenStream::new();
330+
331+
for (param, _punct) in params.iter() {
332+
match param {
333+
venial::FnParam::Typed(param) => {
334+
let param_name = &param.name;
335+
let param_type = &param.ty;
336+
337+
tokens.extend(quote! {
338+
#param_name: impl ::godot::meta::AsArg<#param_type>,
339+
});
340+
}
341+
venial::FnParam::Receiver(_) => {
342+
unreachable!("signals have no receivers; already checked")
343+
}
344+
};
345+
}
346+
347+
tokens
348+
}
349+
323350
fn make_signal_individual_struct(details: &SignalDetails) -> TokenStream {
324-
let emit_params = &details.fn_signature.params;
351+
let emit_params = make_asarg_params(&details.fn_signature.params);
325352

326353
let SignalDetails {
327354
// class_name,
@@ -360,6 +387,12 @@ fn make_signal_individual_struct(details: &SignalDetails) -> TokenStream {
360387
#(#signal_cfg_attrs)*
361388
impl<C: ::godot::obj::WithSignals> #individual_struct_name<'_, C> {
362389
pub fn emit(&mut self, #emit_params) {
390+
use ::godot::meta::AsArg;
391+
#(
392+
::godot::meta::arg_into_owned!(infer #param_names);
393+
//let #param_names = #param_names.into_arg();
394+
)*
395+
363396
self.__typed.emit_tuple((#( #param_names, )*));
364397
}
365398
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,18 @@ fn array_resize() {
570570
assert_eq!(a, Array::new());
571571
}
572572

573+
// Tests that arrays of objects can be declared without explicit type annotations. A similar test exists for DynGd in dyn_gd_test.rs.
574+
// This has deliberately been added to guard against regressions in case `AsArg` is extended (T: Inherits<Base> support broke this).
575+
fn __array_type_inference() {
576+
let a = Node::new_alloc();
577+
let b = Node::new_alloc();
578+
let _array = array![&a, &b];
579+
580+
let c = ArrayTest::new_gd();
581+
let d = ArrayTest::new_gd();
582+
let _array = array![&c, &d];
583+
}
584+
573585
#[derive(GodotClass, Debug)]
574586
#[class(init, base=RefCounted)]
575587
struct ArrayTest;

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

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,28 @@ fn signal_symbols_external() {
122122
emitter.free();
123123
}
124124

125+
// "External" means connect/emit happens from outside the class, via Gd::signals().
126+
#[cfg(since_api = "4.2")]
127+
#[itest]
128+
fn signal_symbols_complex_emit() {
129+
let mut emitter = Emitter::new_alloc();
130+
let arg = emitter.clone();
131+
let mut sig = emitter.signals().signal_obj();
132+
133+
let tracker = Rc::new(RefCell::new(None));
134+
{
135+
let tracker = tracker.clone();
136+
sig.connect(move |obj: Gd<Object>, name: GString| {
137+
*tracker.borrow_mut() = Some((obj, name));
138+
});
139+
}
140+
141+
// Forward compat: .upcast() here becomes a breaking change if we generalize AsArg to include derived->base conversions.
142+
sig.emit(&arg.upcast(), "hello");
143+
144+
emitter.free();
145+
}
146+
125147
// "External" means connect/emit happens from outside the class, via Gd::signals().
126148
#[cfg(since_api = "4.2")]
127149
#[itest]
@@ -396,7 +418,7 @@ mod emitter {
396418
pub fn signal_int(arg1: i64);
397419

398420
#[signal]
399-
fn signal_obj(arg1: Gd<Object>, arg2: GString);
421+
pub(super) fn signal_obj(arg1: Gd<Object>, arg2: GString);
400422

401423
#[func]
402424
pub fn self_receive(&mut self, arg1: i64) {

itest/rust/src/engine_tests/async_test.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,10 @@ fn async_typed_signal_with_array() -> TaskHandle {
229229
assert_eq!(result, array![1, 2, 3]);
230230
});
231231

232-
object.signals().custom_signal_array().emit(array![1, 2, 3]);
232+
object
233+
.signals()
234+
.custom_signal_array()
235+
.emit(&array![1, 2, 3]);
233236

234237
task_handle
235238
}

itest/rust/src/object_tests/dyn_gd_test.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* License, v. 2.0. If a copy of the MPL was not distributed with this
55
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
66
*/
7+
78
use crate::framework::{expect_panic, itest};
89
// Test that all important dyn-related symbols are in the prelude.
910
use godot::prelude::*;
@@ -346,12 +347,19 @@ fn dyn_gd_store_in_godot_array() {
346347
let a = Gd::from_object(RefcHealth { hp: 33 }).into_dyn();
347348
let b = foreign::NodeHealth::new_alloc().into_dyn();
348349

350+
// Forward compat: .upcast() here becomes a breaking change if we generalize AsArg to include derived->base conversions.
349351
let array: Array<DynGd<Object, _>> = array![&a.upcast(), &b.upcast()];
350352

351353
assert_eq!(array.at(0).dyn_bind().get_hitpoints(), 33);
352354
assert_eq!(array.at(1).dyn_bind().get_hitpoints(), 100);
353355

354356
array.at(1).free();
357+
358+
// Tests also type inference of array![]. Independent variable c.
359+
let c: DynGd<RefcHealth, dyn Health> = Gd::from_object(RefcHealth { hp: 33 }).into_dyn();
360+
let c = c.upcast::<RefCounted>();
361+
let array_inferred /*: Array<DynGd<RefCounted, _>>*/ = array![&c];
362+
assert_eq!(array_inferred.at(0).dyn_bind().get_hitpoints(), 33);
355363
}
356364

357365
#[itest]

0 commit comments

Comments
 (0)