Skip to content

Commit 53aa677

Browse files
committed
ToGodot for Option<T> is again pass-by-ref
1 parent 31b0fc9 commit 53aa677

File tree

4 files changed

+97
-38
lines changed

4 files changed

+97
-38
lines changed

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

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use std::ffi::CStr;
99

1010
use crate::builtin::{GString, NodePath, StringName, Variant};
1111
use crate::meta::sealed::Sealed;
12-
use crate::meta::traits::GodotFfiVariant;
12+
use crate::meta::traits::{GodotFfiVariant, GodotNullableFfi};
1313
use crate::meta::{CowArg, GodotType, ObjectArg, ToGodot};
1414
use crate::obj::{bounds, Bounds, DynGd, Gd, GodotClass, Inherits};
1515

@@ -423,7 +423,9 @@ impl AsArg<NodePath> for &String {
423423
/// See [`ToGodot::Pass`].
424424
pub trait ArgPassing: Sealed {
425425
/// Return type: `T` or `&'r T`.
426-
type Output<'r, T: 'r>;
426+
type Output<'r, T: 'r>
427+
where
428+
Self: 'r;
427429

428430
/// FFI argument type: `T::Ffi` or `T::ToFfi<'f>`.
429431
#[doc(hidden)]
@@ -521,6 +523,8 @@ impl ArgPassing for ByRef {
521523

522524
/// Pass arguments to Godot by object pointer (for objects only).
523525
///
526+
/// Currently distinct from [`ByRef`] to not interfere with the blanket impl for `&T` for all `ByRef` types. Semantics are largely the same.
527+
///
524528
/// See [`ToGodot::Pass`].
525529
pub enum ByObject {}
526530
impl Sealed for ByObject {}
@@ -551,5 +555,54 @@ impl ArgPassing for ByObject {
551555
}
552556
}
553557

558+
/// Pass optional arguments by returning `Option<&T::Via>`, allowing delegation to underlying type's strategy.
559+
///
560+
/// This enables `Option<T>` to benefit from the underlying type's efficient passing without cloning. [`ByRef`] doesn't support this because it
561+
/// would transform `Option<T>` to `&Option<T>`; however, we need `Option<&T>` instead.
562+
///
563+
/// See [`ToGodot::Pass`].
564+
pub enum ByOption<Via> {
565+
// Uses `Via` generic type to work around the near-impossibility of Output<'r, T> pointing to a metafunction that transforms Option<T> to
566+
// Option<&'r T>. Such a metafunction cannot be implemented via trait (overlapping impls cause coherence issues), and we would need to
567+
// pollute also the other `By*` types by anything. Using a generic parameter on the trait rather than the associated type avoids that.
568+
_Phantom(std::marker::PhantomData<Via>),
569+
}
570+
impl<Via> Sealed for ByOption<Via> {}
571+
impl<Via> ArgPassing for ByOption<Via>
572+
where
573+
Via: GodotType,
574+
for<'f> Via::ToFfi<'f>: GodotNullableFfi,
575+
{
576+
type Output<'r, T: 'r>
577+
= Option<&'r Via>
578+
where
579+
Self: 'r;
580+
581+
type FfiOutput<'f, T>
582+
= <Via as GodotType>::ToFfi<'f>
583+
where
584+
T: GodotType + 'f;
585+
586+
// value: &Option<U>
587+
// return: T::Via = Option<U::Via>
588+
fn ref_to_owned_via<T>(value: &T) -> T::Via
589+
where
590+
T: ToGodot<Pass = Self>,
591+
T::Via: Clone,
592+
{
593+
value.to_godot_owned()
594+
}
595+
596+
fn ref_to_ffi<T>(value: &T) -> Self::FfiOutput<'_, T::Via>
597+
where
598+
T: ToGodot<Pass = Self>,
599+
T::Via: GodotType,
600+
{
601+
// Reuse pattern from impl GodotType for Option<T>:
602+
// Convert Option<&Via> to Option<Via::ToFfi> and then flatten to Via::ToFfi with null handling.
603+
GodotNullableFfi::flatten_option(value.to_godot().map(|via_ref| via_ref.to_ffi()))
604+
}
605+
}
606+
554607
#[doc(hidden)] // Easier for internal use.
555608
pub type ToArg<'r, Via, Pass> = <Pass as ArgPassing>::Output<'r, Via>;

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ mod ref_arg;
1313
// ----------------------------------------------------------------------------------------------------------------------------------------------
1414
// Public APIs
1515

16-
pub use as_arg::{owned_into_arg, ref_to_arg, ArgPassing, AsArg, ByObject, ByRef, ByValue, ToArg};
16+
pub use as_arg::{
17+
owned_into_arg, ref_to_arg, ArgPassing, AsArg, ByObject, ByOption, ByRef, ByValue, ToArg,
18+
};
1719
// ----------------------------------------------------------------------------------------------------------------------------------------------
1820
// Internal APIs
1921

godot-core/src/meta/godot_convert/impls.rs

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use godot_ffi as sys;
99

1010
use crate::builtin::{Array, Variant};
11+
use crate::meta;
1112
use crate::meta::error::{ConvertError, ErrorKind, FromFfiError, FromVariantError};
1213
use crate::meta::{
1314
ArrayElement, ClassName, FromGodot, GodotConvert, GodotNullableFfi, GodotType,
@@ -85,47 +86,47 @@ where
8586
}
8687

8788
// Only relevant for object types T.
88-
unsafe fn as_object_arg(&self) -> crate::meta::ObjectArg {
89+
unsafe fn as_object_arg(&self) -> meta::ObjectArg {
8990
match self {
9091
Some(inner) => unsafe { inner.as_object_arg() },
91-
None => crate::meta::ObjectArg::null(),
92+
None => meta::ObjectArg::null(),
9293
}
9394
}
9495
}
9596

96-
impl<T: GodotConvert> GodotConvert for Option<T>
97+
impl<T> GodotConvert for Option<T>
9798
where
99+
T: GodotConvert,
98100
Option<T::Via>: GodotType,
99101
{
100102
type Via = Option<T::Via>;
101103
}
102104

103-
impl<T: ToGodot> ToGodot for Option<T>
105+
impl<T> ToGodot for Option<T>
104106
where
105-
Option<T::Via>: GodotType,
107+
// Currently limited to holding objects -> needed to establish to_godot() relation T::to_godot() = Option<&T::Via>.
108+
T: ToGodot<Pass = meta::ByObject>,
109+
// Extra Clone bound for to_godot_owned(); might be extracted in the future.
110+
T::Via: Clone,
111+
// T::Via must be a Godot nullable type (to support the None case).
106112
for<'f> T::Via: GodotType<
107113
// Associated types need to be nullable.
108114
Ffi: GodotNullableFfi,
109115
ToFfi<'f>: GodotNullableFfi,
110116
>,
111-
T::Via: Clone,
117+
// Previously used bound, not needed right now but don't remove: Option<T::Via>: GodotType,
112118
{
113-
// Potential optimization: use underlying T::Pass instead of ByValue.
114-
// Problem is that ByRef requires to_godot() -> &Self::Via, which is &Option<T::Via>. We would however need Option<&T::Via>.
115-
// Might need a third ArgPassing impl, or different design.
116-
type Pass = crate::meta::ByValue;
119+
// Basically ByRef, but allows Option<T> -> Option<&T::Via> conversion.
120+
type Pass = meta::ByOption<T::Via>;
117121

118-
fn to_godot(&self) -> Option<T::Via> {
119-
self.as_ref().map(T::to_godot_owned)
122+
fn to_godot(&self) -> Option<&T::Via> {
123+
self.as_ref().map(T::to_godot)
120124
}
121125

122-
fn to_godot_owned(&self) -> Self::Via
126+
fn to_godot_owned(&self) -> Option<T::Via>
123127
where
124128
Self::Via: Clone,
125129
{
126-
// Default implementation calls underlying T::to_godot().clone(), which may be wrong.
127-
// Some to_godot_owned() calls are specialized/overridden, we need to honor that.
128-
129130
self.as_ref().map(T::to_godot_owned)
130131
}
131132

@@ -256,7 +257,7 @@ macro_rules! impl_godot_scalar {
256257
}
257258

258259
impl ToGodot for $T {
259-
type Pass = crate::meta::ByValue;
260+
type Pass = meta::ByValue;
260261

261262
fn to_godot(&self) -> Self::Via {
262263
*self
@@ -272,10 +273,10 @@ macro_rules! impl_godot_scalar {
272273
}
273274

274275
// `GodotType` for these three is implemented in `godot-core/src/builtin/variant/impls.rs`.
275-
crate::meta::impl_godot_as_self!(bool: ByValue);
276-
crate::meta::impl_godot_as_self!(i64: ByValue);
277-
crate::meta::impl_godot_as_self!(f64: ByValue);
278-
crate::meta::impl_godot_as_self!((): ByValue);
276+
meta::impl_godot_as_self!(bool: ByValue);
277+
meta::impl_godot_as_self!(i64: ByValue);
278+
meta::impl_godot_as_self!(f64: ByValue);
279+
meta::impl_godot_as_self!((): ByValue);
279280

280281
// Also implements ArrayElement.
281282
impl_godot_scalar!(
@@ -342,7 +343,7 @@ impl GodotConvert for u64 {
342343
}
343344

344345
impl ToGodot for u64 {
345-
type Pass = crate::meta::ByValue;
346+
type Pass = meta::ByValue;
346347

347348
fn to_godot(&self) -> Self::Via {
348349
*self
@@ -382,7 +383,7 @@ impl<T: ArrayElement> GodotConvert for Vec<T> {
382383
}
383384

384385
impl<T: ArrayElement> ToGodot for Vec<T> {
385-
type Pass = crate::meta::ByValue;
386+
type Pass = meta::ByValue;
386387

387388
fn to_godot(&self) -> Self::Via {
388389
Array::from(self.as_slice())
@@ -400,7 +401,7 @@ impl<T: ArrayElement, const LEN: usize> GodotConvert for [T; LEN] {
400401
}
401402

402403
impl<T: ArrayElement, const LEN: usize> ToGodot for [T; LEN] {
403-
type Pass = crate::meta::ByValue;
404+
type Pass = meta::ByValue;
404405

405406
fn to_godot(&self) -> Self::Via {
406407
Array::from(self)
@@ -440,7 +441,7 @@ impl<T: ArrayElement> GodotConvert for &[T] {
440441
}
441442

442443
impl<T: ArrayElement> ToGodot for &[T] {
443-
type Pass = crate::meta::ByValue;
444+
type Pass = meta::ByValue;
444445

445446
fn to_godot(&self) -> Self::Via {
446447
Array::from(*self)
@@ -461,7 +462,7 @@ macro_rules! impl_pointer_convert {
461462
}
462463

463464
impl ToGodot for $Ptr {
464-
type Pass = crate::meta::ByValue;
465+
type Pass = meta::ByValue;
465466

466467
fn to_godot(&self) -> Self::Via {
467468
*self as i64

itest/rust/src/object_tests/object_test.rs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -100,17 +100,20 @@ fn object_engine_roundtrip() {
100100
}
101101

102102
#[itest]
103-
fn object_null_argument() {
104-
// Objects currently use ObjectArg instead of RefArg, so this scenario shouldn't occur. Test can be updated if code is refactored.
103+
fn object_option_argument() {
104+
// Tests following things:
105+
// - to_godot() returns Option<&T>
106+
// - None maps to None
107+
// - Some(gd) maps to Some(&gd)
105108

106-
let null_obj = Option::<Gd<Node>>::None;
109+
let null_obj = None::<Gd<Node>>;
110+
let via: Option<&Gd<Node>> = null_obj.to_godot();
111+
assert_eq!(via, None);
107112

108-
let via = null_obj.to_godot();
109-
let ffi = via.to_ffi();
110-
111-
expect_panic("not yet implemented: pass objects through RefArg", || {
112-
ffi.to_godot();
113-
});
113+
let refc = RefCounted::new_gd();
114+
let some_obj = Some(refc.clone());
115+
let via: Option<&Gd<RefCounted>> = some_obj.to_godot();
116+
assert_eq!(via, Some(&refc));
114117
}
115118

116119
#[itest]

0 commit comments

Comments
 (0)