diff --git a/itest/rust/src/object_tests/dyn_gd_test.rs b/itest/rust/src/object_tests/dyn_gd_test.rs index c5d93f0cc..0a4852420 100644 --- a/itest/rust/src/object_tests/dyn_gd_test.rs +++ b/itest/rust/src/object_tests/dyn_gd_test.rs @@ -10,25 +10,18 @@ use godot::prelude::*; #[itest] fn dyn_gd_creation_bind() { - // type can't be inferred because RefcHealth implements more than one AsDyn<...> trait. - // otherwise let _unused = … would be enough - let _unused: DynGd = Gd::from_object(RefcHealth { hp: 1 }).into_dyn(); - let _unused: DynGd> = - Gd::from_object(RefcHealth { hp: 1 }).into_dyn(); + // Type inference on this works because only 1 AsDyn<...> trait is implemented for RefcHealth. It would fail with another. + let _unused = Gd::from_object(RefcHealth { hp: 1 }).into_dyn(); let user_obj = RefcHealth { hp: 34 }; - let mut dyn_gd: DynGd = Gd::from_object(user_obj).into_dyn(); - - let user_obj = RefcHealth { hp: 34 }; - let mut assoc_dyn_gd: DynGd> = - Gd::from_object(user_obj).into_dyn(); + let mut dyn_gd = Gd::from_object(user_obj).into_dyn(); { - // Exclusive binds. + // Exclusive bind. + // Interesting: this can be type inferred because RefcHealth implements only 1 AsDyn<...> trait. + // If there were another, type inference would fail. let mut health = dyn_gd.dyn_bind_mut(); health.deal_damage(4); - let mut health = assoc_dyn_gd.dyn_bind_mut(); - health.set_report(42); } { // Test multiple shared binds. @@ -37,39 +30,45 @@ fn dyn_gd_creation_bind() { assert_eq!(health_b.get_hitpoints(), 30); assert_eq!(health_a.get_hitpoints(), 30); - - let assoc_health_a = assoc_dyn_gd.dyn_bind(); - let assoc_health_b = assoc_dyn_gd.dyn_bind(); - - assert_eq!(assoc_health_a.get_report(), 42); - assert_eq!(assoc_health_b.get_report(), 42); } { let mut health = dyn_gd.dyn_bind_mut(); health.kill(); assert_eq!(health.get_hitpoints(), 0); - - let mut assoc_health = assoc_dyn_gd.dyn_bind_mut(); - assoc_health.set_report(0); - assert_eq!(assoc_health.get_report(), 0); } } #[itest] fn dyn_gd_creation_deref() { - let node = foreign::NodeHealth::new_alloc(); - let original_id = node.instance_id(); + let obj = Gd::from_object(RefcHealth { hp: 100 }); + let original_id = obj.instance_id(); - let mut node = node.into_dyn::(); + // type can be safely inferred. + let mut obj = obj.into_dyn(); - let dyn_id = node.instance_id(); + let dyn_id = obj.instance_id(); assert_eq!(dyn_id, original_id); - deal_20_damage(&mut *node.dyn_bind_mut()); - assert_eq!(node.dyn_bind().get_hitpoints(), 80); + deal_20_damage(&mut *obj.dyn_bind_mut()); + assert_eq!(obj.dyn_bind().get_hitpoints(), 80); +} - node.free(); +#[itest] +fn dyn_gd_creation_deref_multiple_traits() { + let obj = foreign::NodeHealth::new_alloc(); + let original_id = obj.instance_id(); + + // trait must be explicitly declared if multiple AsDyn<...> trait implementations exists. + let mut obj = obj.into_dyn::(); + + let dyn_id = obj.instance_id(); + assert_eq!(dyn_id, original_id); + + deal_20_damage(&mut *obj.dyn_bind_mut()); + assert_eq!(obj.dyn_bind().get_hitpoints(), 80); + + obj.free(); } fn deal_20_damage(h: &mut dyn Health) { @@ -81,9 +80,7 @@ fn dyn_gd_upcast() { let original = foreign::NodeHealth::new_alloc(); let original_copy = original.clone(); - let concrete = original.clone().into_dyn::(); - let concrete_with_associated_type = - original.into_dyn::>(); + let concrete = original.into_dyn::(); let mut node = concrete.clone().upcast::(); let object = concrete.upcast::(); @@ -95,23 +92,16 @@ fn dyn_gd_upcast() { // Ensure applied to the object polymorphically. Concrete object can access bind(), no dyn_bind(). assert_eq!(original_copy.bind().get_hitpoints(), 75); - assert_eq!(original_copy.bind().get_report(), 42.0); // Check also another angle (via Object). Here dyn_bind(). assert_eq!(object.dyn_bind().get_hitpoints(), 75); - let object = concrete_with_associated_type.clone().upcast::(); - assert_eq!(object.dyn_bind().get_report(), 42.0); - - // via DynGd with associated type - assert_eq!(concrete_with_associated_type.dyn_bind().get_report(), 42.0); - node.free(); } #[itest] fn dyn_gd_downcast() { - let original: DynGd = Gd::from_object(RefcHealth { hp: 20 }).into_dyn(); + let original = Gd::from_object(RefcHealth { hp: 20 }).into_dyn(); let mut object = original.upcast::(); object.dyn_bind_mut().deal_damage(7); @@ -128,58 +118,32 @@ fn dyn_gd_downcast() { assert_eq!(back.bind().get_hitpoints(), 13); } -#[itest] -fn dyn_gd_with_associated_type_downcast() { - let original: DynGd> = - Gd::from_object(RefcHealth { hp: 20 }).into_dyn(); - let mut object = original.upcast::(); - - object.dyn_bind_mut().set_report(13); - - let failed = object.try_cast::(); - let object = failed.expect_err("DynGd::try_cast() succeeded, but should have failed"); - - let refc = object.cast::(); - assert_eq!(refc.dyn_bind().get_report(), 13); - - let back = refc - .try_cast::() - .expect("DynGd::try_cast() should have succeeded"); - assert_eq!(back.bind().get_report(), 13); -} - #[itest] fn dyn_gd_debug() { - let obj: DynGd = Gd::from_object(RefcHealth { hp: 20 }).into_dyn(); - let id = obj.instance_id(); - - let actual = format!(".:{obj:?}:."); - let expected = format!(".:DynGd {{ id: {id}, class: RefcHealth, trait: dyn Health }}:."); + let node = foreign::NodeHealth::new_alloc(); + let id = node.instance_id(); - assert_eq!(actual, expected); + let node = node.into_dyn::(); - let obj: DynGd> = - obj.into_gd().into_dyn(); - let actual = format!(".:{obj:?}:."); - let expected = format!(".:DynGd {{ id: {id}, class: RefcHealth, trait: dyn HealthWithAssociatedType }}:."); + let actual = format!(".:{node:?}:."); + let expected = format!(".:DynGd {{ id: {id}, class: NodeHealth, trait: dyn Health }}:."); assert_eq!(actual, expected); -} - -#[itest] -fn dyn_gd_display() { - let obj: DynGd = Gd::from_object(RefcHealth { hp: 55 }).into_dyn(); - let actual = format!("{obj}"); - let expected = "RefcHealth(hp=55)"; + let node = node + .into_gd() + .into_dyn::>(); + let actual = format!(".:{node:?}:."); + let expected = format!(".:DynGd {{ id: {id}, class: NodeHealth, trait: dyn InstanceIdProvider }}:."); assert_eq!(actual, expected); + + node.free(); } #[itest] -fn dyn_gd_with_associated_type_display() { - let obj: DynGd> = - Gd::from_object(RefcHealth { hp: 55 }).into_dyn(); +fn dyn_gd_display() { + let obj = Gd::from_object(RefcHealth { hp: 55 }).into_dyn(); let actual = format!("{obj}"); let expected = "RefcHealth(hp=55)"; @@ -190,8 +154,8 @@ fn dyn_gd_with_associated_type_display() { #[itest] fn dyn_gd_eq() { let gd = Gd::from_object(RefcHealth { hp: 55 }); - let a: DynGd = gd.clone().into_dyn(); - let b: DynGd = gd.into_dyn(); + let a = gd.clone().into_dyn(); + let b = gd.into_dyn(); let c = b.clone(); assert_eq!(a, b); @@ -208,15 +172,15 @@ fn dyn_gd_hash() { use godot::sys::hash_value; let gd = Gd::from_object(RefcHealth { hp: 55 }); - let a: DynGd = gd.clone().into_dyn(); - let b: DynGd = gd.into_dyn(); + let a = gd.clone().into_dyn(); + let b = gd.into_dyn(); let c = b.clone(); assert_eq!(hash_value(&a), hash_value(&b)); assert_eq!(hash_value(&a), hash_value(&c)); assert_eq!(hash_value(&b), hash_value(&c)); - let x: DynGd = Gd::from_object(RefcHealth { hp: 55 }).into_dyn(); + let x = Gd::from_object(RefcHealth { hp: 55 }).into_dyn(); // Not guaranteed, but exceedingly likely. assert_ne!(hash_value(&a), hash_value(&x)); @@ -280,7 +244,7 @@ fn dyn_gd_shared_guard() { #[itest] fn dyn_gd_downgrade() { - let dyn_gd: DynGd = RefcHealth::new_gd().into_dyn(); + let dyn_gd = RefcHealth::new_gd().into_dyn(); let dyn_id = dyn_gd.instance_id(); let gd = dyn_gd.into_gd(); @@ -313,48 +277,51 @@ fn dyn_gd_pass_to_godot_api() { #[itest] fn dyn_gd_variant_conversions() { - let original = Gd::from_object(RefcHealth { hp: 11 }).into_dyn::(); - let original_id = original.instance_id(); - let refc = original.into_gd().upcast::(); + let node = foreign::NodeHealth::new_alloc(); + let original_id = node.instance_id(); - let variant = refc.to_variant(); + let variant = node.to_variant(); // Convert to different levels of DynGd: - let back: DynGd = variant.to(); - assert_eq!(back.bind().get_hitpoints(), 11); + let back: DynGd = variant.to(); + assert_eq!(back.bind().get_hitpoints(), 100); assert_eq!(back.instance_id(), original_id); - let back: DynGd = variant.to(); - assert_eq!(back.dyn_bind().get_hitpoints(), 11); + let back: DynGd = variant.to(); + assert_eq!(back.dyn_bind().get_hitpoints(), 100); assert_eq!(back.instance_id(), original_id); let back: DynGd = variant.to(); - assert_eq!(back.dyn_bind().get_hitpoints(), 11); + assert_eq!(back.dyn_bind().get_hitpoints(), 100); assert_eq!(back.instance_id(), original_id); - // convert to different DynGd + // Convert to different DynGd: - let other: DynGd> = variant.to(); - assert_eq!(other.dyn_bind().get_report(), 11); - assert_eq!(other.instance_id(), original_id); + let back: DynGd> = variant.to(); + assert_eq!(back.dyn_bind().get_id_dynamic(), original_id); - // Convert to different levels of Gd: + let back: DynGd> = variant.to(); + assert_eq!(back.dyn_bind().get_id_dynamic(), original_id); - let back: Gd = variant.to(); - assert_eq!(back.bind().get_hitpoints(), 11); - assert_eq!(back.instance_id(), original_id); + let back: DynGd> = variant.to(); + assert_eq!(back.dyn_bind().get_id_dynamic(), original_id); - let back: Gd = variant.to(); + // Convert to different levels of Gd: + + let back: Gd = variant.to(); + assert_eq!(back.bind().get_hitpoints(), 100); assert_eq!(back.instance_id(), original_id); let back: Gd = variant.to(); assert_eq!(back.instance_id(), original_id); + + node.free(); } #[itest] fn dyn_gd_store_in_godot_array() { - let a = Gd::from_object(RefcHealth { hp: 33 }).into_dyn::(); + let a = Gd::from_object(RefcHealth { hp: 33 }).into_dyn(); let b = foreign::NodeHealth::new_alloc().into_dyn(); let array: Array> = array![&a.upcast(), &b.upcast()]; @@ -365,63 +332,32 @@ fn dyn_gd_store_in_godot_array() { array.at(1).free(); } -#[itest] -fn dyn_gd_with_associated_type_store_in_godot_array() { - let a: DynGd> = - Gd::from_object(RefcHealth { hp: 33 }).into_dyn(); - - let array: Array> = array![&a.upcast()]; - assert_eq!(array.at(0).dyn_bind().get_report(), 33); -} - #[itest] fn dyn_gd_error_unregistered_trait() { trait UnrelatedTrait {} + let node = foreign::NodeHealth::new_alloc().into_dyn::(); - let obj = Gd::from_object(RefcHealth { hp: 33 }).into_dyn::(); + let variant = node.to_variant(); - let variant = obj.to_variant(); - let gd = obj.into_gd(); + let back = variant.try_to::>(); - let back = variant.try_to::>(); + // The conversion fails before a DynGd is created, so Display still operates on the Gd. + let node = node.into_gd(); let err = back.expect_err("DynGd::try_to() should have failed"); - let expected_err = { - // The conversion fails before a DynGd is created, so Display still operates on the Gd. - format!("trait `dyn UnrelatedTrait` has not been registered with #[godot_dyn]: {gd:?}") - }; + let expected_err = + format!("trait `dyn UnrelatedTrait` has not been registered with #[godot_dyn]: {node:?}"); assert_eq!(err.to_string(), expected_err); - let back = - variant.try_to::>>(); + let back = variant.try_to::>>(); let err = back.expect_err("DynGd::try_to() should have failed"); - let expected_err = { - format!("trait `dyn HealthWithAssociatedType` has not been registered with #[godot_dyn]: {gd:?}") - }; + let expected_err = format!("trait `dyn InstanceIdProvider` has not been registered with #[godot_dyn]: {node:?}"); assert_eq!(err.to_string(), expected_err); -} - -#[itest] -fn dyn_gd_error_with_associated_type_unimplemented_trait() { - let obj = Gd::from_object(RefcHealth { hp: 33 }) - .into_dyn::>(); - - let variant = obj.to_variant(); - let back = - variant.try_to::>>(); - - let err = back.expect_err("DynGd::try_to() should have failed"); - let expected_err = { - // The conversion fails before a DynGd is created, so Display still operates on the Gd. - let obj = obj.into_gd(); - format!("none of the classes derived from `RefcHealth` have been linked to trait `dyn HealthWithAssociatedType` with #[godot_dyn]: {obj:?}") - }; - - assert_eq!(err.to_string(), expected_err); + node.free(); } #[itest] @@ -437,19 +373,22 @@ fn dyn_gd_error_unimplemented_trait() { format!("none of the classes derived from `RefCounted` have been linked to trait `dyn Health` with #[godot_dyn]: {obj:?}") ); - let variant = obj.to_variant(); - let back = variant.try_to::>>(); + let node = foreign::NodeHealth::new_alloc(); + let variant = node.to_variant(); + let back = variant.try_to::>>(); let err = back.expect_err("DynGd::try_to() should have failed"); assert_eq!( err.to_string(), - format!("none of the classes derived from `RefCounted` have been linked to trait `dyn HealthWithAssociatedType` with #[godot_dyn]: {obj:?}") + format!("none of the classes derived from `NodeHealth` have been linked to trait `dyn InstanceIdProvider` with #[godot_dyn]: {node:?}") ); + + node.free(); } #[itest] fn dyn_gd_free_while_dyn_bound() { - let mut obj: DynGd<_, dyn Health> = foreign::NodeHealth::new_alloc().into_dyn(); + let mut obj = foreign::NodeHealth::new_alloc().into_dyn::(); { let copy = obj.clone(); @@ -472,6 +411,20 @@ fn dyn_gd_free_while_dyn_bound() { obj.free(); } +#[itest] +fn dyn_gd_multiple_traits() { + let obj = foreign::NodeHealth::new_alloc(); + let original_id = obj.instance_id(); + + let obj = obj + .into_dyn::>() + .upcast::(); + let id = obj.dyn_bind().get_id_dynamic(); + assert_eq!(id, original_id); + + obj.free(); +} + // ---------------------------------------------------------------------------------------------------------------------------------------------- // Example symbols @@ -485,14 +438,6 @@ trait Health { } } -trait HealthWithAssociatedType { - type HealthType; - - fn get_report(&self) -> Self::HealthType; - - fn set_report(&mut self, new: Self::HealthType); -} - #[derive(GodotClass)] #[class(init)] struct RefcHealth { @@ -529,19 +474,6 @@ impl Health for RefcHealth { } } -#[godot_dyn] -impl HealthWithAssociatedType for RefcHealth { - type HealthType = u8; - - fn get_report(&self) -> Self::HealthType { - self.hp - } - - fn set_report(&mut self, new: Self::HealthType) { - self.hp = new; - } -} - #[godot_dyn] impl Health for foreign::NodeHealth { fn get_hitpoints(&self) -> u8 { @@ -558,16 +490,20 @@ impl Health for foreign::NodeHealth { } } -// Implementation created only to register the DynGd `HealthWithAssociatedType` trait. -#[godot_dyn] -impl HealthWithAssociatedType for foreign::NodeHealth { - type HealthType = f32; +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Check that one class can implement two or more traits. - fn get_report(&self) -> Self::HealthType { - 42.0 - } +trait InstanceIdProvider { + type Id; + fn get_id_dynamic(&self) -> Self::Id; +} - fn set_report(&mut self, _new: Self::HealthType) {} +#[godot_dyn] +impl InstanceIdProvider for foreign::NodeHealth { + type Id = InstanceId; + fn get_id_dynamic(&self) -> Self::Id { + self.base().instance_id() + } } // ---------------------------------------------------------------------------------------------------------------------------------------------- @@ -579,5 +515,15 @@ struct RefcDynGdExporter { #[var] first: Option>, #[export] - second: Option>>, + second: Option>>, +} + +// Implementation created only to register the DynGd `HealthWithAssociatedType` trait. +// Pointless trait, but tests proper conversion. +#[godot_dyn] +impl InstanceIdProvider for RefcDynGdExporter { + type Id = f32; + fn get_id_dynamic(&self) -> Self::Id { + 42.0 + } }