diff --git a/godot-core/src/obj/traits.rs b/godot-core/src/obj/traits.rs index 5d3297e19..8030c47b8 100644 --- a/godot-core/src/obj/traits.rs +++ b/godot-core/src/obj/traits.rs @@ -48,13 +48,53 @@ pub trait Share { fn share(&self) -> Self; } -/// A struct `Derived` implementing `Inherits` expresses that `Derived` _strictly_ inherits `Base` in the Godot hierarchy. +/// Non-strict inheritance relationship in the Godot class hierarchy. /// -/// This trait is implemented for all Godot engine classes, even for non-direct relations (e.g. `Node3D` implements `Inherits`). Deriving [`GodotClass`] for custom classes will achieve the same: all direct and indirect base -/// classes of your extension class will be wired up using the `Inherits` relation. +/// `Derived: Inherits` means that either `Derived` is a subclass of `Base`, or the class `Base` itself (hence "non-strict"). /// -/// The trait is not reflexive: `T` never implements `Inherits`. -pub trait Inherits {} +/// This trait is automatically implemented for all Godot engine classes and user-defined classes that derive [`GodotClass`]. +/// It has `GodotClass` as a supertrait, allowing your code to have bounds solely on `Derived: Inherits` rather than +/// `Derived: Inherits + GodotClass`. +/// +/// Inheritance is transitive across indirect base classes: `Node3D` implements `Inherits` and `Inherits`. +/// +/// The trait is also reflexive: `T` always implements `Inherits`. +/// +/// # Usage +/// +/// The primary use case for this trait is polymorphism: you write a function that accepts anything that derives from a certain class +/// (including the class itself): +/// ```no_run +/// # use godot::prelude::*; +/// fn print_node(node: Gd) +/// where +/// T: Inherits, +/// { +/// let up = node.upcast(); // type Gd inferred +/// println!("Node #{} with name {}", up.instance_id(), up.get_name()); +/// up.free(); +/// } +/// +/// // Call with different types +/// print_node(Node::new_alloc()); // works on T=Node as well +/// print_node(Node2D::new_alloc()); // or derived classes +/// print_node(Node3D::new_alloc()); +/// ``` +/// +/// A variation of the above pattern works without `Inherits` or generics, if you move the `upcast()` into the call site: +/// ```no_run +/// # use godot::prelude::*; +/// fn print_node(node: Gd) { /* ... */ } +/// +/// // Call with different types +/// print_node(Node::new_alloc()); // no upcast needed +/// print_node(Node2D::new_alloc().upcast()); +/// print_node(Node3D::new_alloc().upcast()); +/// ``` +/// +pub trait Inherits: GodotClass {} + +impl Inherits for T {} /// Auto-implemented for all engine-provided classes pub trait EngineClass: GodotClass { diff --git a/godot-ffi/src/lib.rs b/godot-ffi/src/lib.rs index 07ea024a9..82bc8fdde 100644 --- a/godot-ffi/src/lib.rs +++ b/godot-ffi/src/lib.rs @@ -109,10 +109,21 @@ pub unsafe fn get_registry() -> &'static mut GlobalRegistry { &mut unwrap_ref_unchecked_mut(&mut BINDING).registry } +/// Makes sure that Godot is running, or panics. Debug mode only! +macro_rules! debug_assert_godot { + ($expr:expr) => { + debug_assert!( + $expr, + "Godot engine not available; make sure you are do not call it from unit/doc tests" + ); // previous message: "unchecked access to Option::None" + }; +} + /// Combination of `as_ref()` and `unwrap_unchecked()`, but without the case differentiation in /// the former (thus raw pointer access in release mode) unsafe fn unwrap_ref_unchecked(opt: &Option) -> &T { - debug_assert!(opt.is_some(), "unchecked access to Option::None"); + debug_assert_godot!(opt.is_some()); + match opt { Some(ref val) => val, None => std::hint::unreachable_unchecked(), @@ -120,7 +131,8 @@ unsafe fn unwrap_ref_unchecked(opt: &Option) -> &T { } unsafe fn unwrap_ref_unchecked_mut(opt: &mut Option) -> &mut T { - debug_assert!(opt.is_some(), "unchecked access to Option::None"); + debug_assert_godot!(opt.is_some()); + match opt { Some(ref mut val) => val, None => std::hint::unreachable_unchecked(), diff --git a/itest/rust/src/object_test.rs b/itest/rust/src/object_test.rs index d5f148d3a..ce8a50b8b 100644 --- a/itest/rust/src/object_test.rs +++ b/itest/rust/src/object_test.rs @@ -7,9 +7,9 @@ use crate::{expect_panic, itest}; use godot::bind::{godot_api, GodotClass, GodotExt}; use godot::builtin::{FromVariant, GodotString, StringName, ToVariant, Variant, Vector3}; -use godot::engine::{file_access, FileAccess, Node, Node3D, Object, RefCounted}; -use godot::obj::Share; +use godot::engine::{file_access, Camera3D, FileAccess, Node, Node3D, Object, RefCounted}; use godot::obj::{Base, Gd, InstanceId}; +use godot::obj::{Inherits, Share}; use godot::sys::GodotFfi; use std::cell::RefCell; @@ -42,11 +42,15 @@ pub fn run() -> bool { ok &= object_engine_up_deref(); ok &= object_engine_up_deref_mut(); ok &= object_engine_upcast(); + ok &= object_engine_upcast_reflexive(); ok &= object_engine_downcast(); + ok &= object_engine_downcast_reflexive(); ok &= object_engine_bad_downcast(); + ok &= object_engine_accept_polymorphic(); ok &= object_user_upcast(); ok &= object_user_downcast(); ok &= object_user_bad_downcast(); + ok &= object_user_accept_polymorphic(); ok &= object_engine_manual_free(); ok &= object_engine_manual_double_free(); ok &= object_engine_refcounted_free(); @@ -314,7 +318,19 @@ fn object_engine_upcast() { assert_eq!(object.instance_id(), id); assert_eq!(object.get_class(), GodotString::from("Node3D")); - // Deliberate free on upcast obj + // Deliberate free on upcast object + object.free(); +} + +#[itest] +fn object_engine_upcast_reflexive() { + let node3d: Gd = Node3D::new_alloc(); + let id = node3d.instance_id(); + + let object = node3d.upcast::(); + assert_eq!(object.instance_id(), id); + assert_eq!(object.get_class(), GodotString::from("Node3D")); + object.free(); } @@ -335,6 +351,17 @@ fn object_engine_downcast() { node3d.free(); } +#[itest] +fn object_engine_downcast_reflexive() { + let node3d: Gd = Node3D::new_alloc(); + let id = node3d.instance_id(); + + let node3d: Gd = node3d.cast::(); + assert_eq!(node3d.instance_id(), id); + + node3d.free(); +} + #[itest] fn object_engine_bad_downcast() { let object: Gd = Object::new_alloc(); @@ -345,6 +372,59 @@ fn object_engine_bad_downcast() { free_ref.free(); } +#[itest] +fn object_engine_accept_polymorphic() { + let mut node = Camera3D::new_alloc(); + let expected_name = StringName::from("Node name"); + let expected_class = GodotString::from("Camera3D"); + + node.set_name(GodotString::from(&expected_name)); + + let actual_name = accept_node(node.share()); + assert_eq!(actual_name, expected_name); + + let actual_class = accept_object(node.share()); + assert_eq!(actual_class, expected_class); + + node.free(); +} + +#[itest] +fn object_user_accept_polymorphic() { + let obj = Gd::new(ObjPayload { value: 123 }); + let expected_class = GodotString::from("ObjPayload"); + + let actual_class = accept_refcounted(obj.share()); + assert_eq!(actual_class, expected_class); + + let actual_class = accept_object(obj); + assert_eq!(actual_class, expected_class); +} + +fn accept_node(node: Gd) -> StringName +where + T: Inherits, +{ + let up = node.upcast(); + up.get_name() +} + +fn accept_refcounted(node: Gd) -> GodotString +where + T: Inherits, +{ + let up = node.upcast(); + up.get_class() +} + +fn accept_object(node: Gd) -> GodotString +where + T: Inherits, +{ + let up = node.upcast(); + up.get_class() +} + #[itest] fn object_user_upcast() { let obj = user_object(); @@ -402,7 +482,7 @@ fn object_engine_manual_double_free() { #[itest] fn object_engine_refcounted_free() { let node = RefCounted::new(); - let node2 = node.share().upcast(); + let node2 = node.share().upcast::(); expect_panic("calling free() on RefCounted obj", || node2.free()) }