diff --git a/godot-core/src/builtin/collections/array.rs b/godot-core/src/builtin/collections/array.rs index 14fe486c3..db24f4291 100644 --- a/godot-core/src/builtin/collections/array.rs +++ b/godot-core/src/builtin/collections/array.rs @@ -12,8 +12,9 @@ use crate::builtin::*; use crate::meta; use crate::meta::error::{ConvertError, FromGodotError, FromVariantError}; use crate::meta::{ - ArrayElement, ArrayTypeInfo, AsArg, CowArg, FromGodot, GodotConvert, GodotFfiVariant, - GodotType, ParamType, PropertyHintInfo, RefArg, ToGodot, + element_godot_type_name, element_variant_type, ArrayElement, ArrayTypeInfo, AsArg, CowArg, + FromGodot, GodotConvert, GodotFfiVariant, GodotType, ParamType, PropertyHintInfo, RefArg, + ToGodot, }; use crate::registry::property::{Export, Var}; use godot_ffi as sys; @@ -886,10 +887,16 @@ impl Array { /// Used as `if` statement in trait impls. Avoids defining yet another trait or non-local overridden function just for this case; /// `Variant` is the only Godot type that has variant type NIL and can be used as an array element. fn has_variant_t() -> bool { - T::Ffi::variant_type() == VariantType::NIL + element_variant_type::() == VariantType::NIL } } +#[test] +fn correct_variant_t() { + assert!(Array::::has_variant_t()); + assert!(!Array::::has_variant_t()); +} + impl VariantArray { /// # Safety /// - Variant must have type `VariantType::ARRAY`. @@ -1133,7 +1140,7 @@ impl GodotType for Array { // Typed arrays use type hint. PropertyHintInfo { hint: crate::global::PropertyHint::ARRAY_TYPE, - hint_string: T::godot_type_name().into(), + hint_string: GString::from(element_godot_type_name::()), } } } diff --git a/godot-core/src/meta/args/as_arg.rs b/godot-core/src/meta/args/as_arg.rs index 9943f98bb..306b44b89 100644 --- a/godot-core/src/meta/args/as_arg.rs +++ b/godot-core/src/meta/args/as_arg.rs @@ -6,7 +6,7 @@ */ use crate::builtin::{GString, NodePath, StringName}; -use crate::meta::{CowArg, GodotType}; +use crate::meta::{sealed, CowArg}; use std::ffi::CStr; /// Implicit conversions for arguments passed to Godot APIs. @@ -253,7 +253,9 @@ impl AsArg for &String { // ---------------------------------------------------------------------------------------------------------------------------------------------- /// Implemented for all parameter types `T` that are allowed to receive [impl `AsArg`][AsArg]. -pub trait ParamType: GodotType +// ParamType used to be a subtrait of GodotType, but this can be too restrictive. For example, DynGd is not a "Godot canonical type" +// (GodotType), however it's still useful to store it in arrays -- which requires AsArg and subsequently ParamType. +pub trait ParamType: sealed::Sealed + Sized + 'static // GodotType bound not required right now, but conceptually should always be the case. { /// Canonical argument passing type, either `T` or an internally-used CoW type. diff --git a/godot-core/src/meta/array_type_info.rs b/godot-core/src/meta/array_type_info.rs index 4404d0eb7..a28b617ce 100644 --- a/godot-core/src/meta/array_type_info.rs +++ b/godot-core/src/meta/array_type_info.rs @@ -6,8 +6,8 @@ */ use crate::builtin::{StringName, VariantType}; -use crate::meta::traits::GodotFfi; -use crate::meta::GodotType; +use crate::meta::traits::element_variant_type; +use crate::meta::{ArrayElement, GodotType}; use std::fmt; /// Represents the type information of a Godot array. See @@ -26,8 +26,8 @@ pub(crate) struct ArrayTypeInfo { } impl ArrayTypeInfo { - pub fn of() -> Self { - let variant_type = ::Ffi::variant_type(); + pub fn of() -> Self { + let variant_type = element_variant_type::(); let class_name = if variant_type == VariantType::OBJECT { Some(T::Via::class_name().to_string_name()) } else { diff --git a/godot-core/src/meta/mod.rs b/godot-core/src/meta/mod.rs index 035df1fc6..e516df64f 100644 --- a/godot-core/src/meta/mod.rs +++ b/godot-core/src/meta/mod.rs @@ -61,7 +61,9 @@ pub use godot_convert::{FromGodot, GodotConvert, ToGodot}; pub use traits::{ArrayElement, GodotType, PackedArrayElement}; pub(crate) use array_type_info::ArrayTypeInfo; -pub(crate) use traits::{GodotFfiVariant, GodotNullableFfi}; +pub(crate) use traits::{ + element_godot_type_name, element_variant_type, GodotFfiVariant, GodotNullableFfi, +}; use crate::registry::method::MethodParamOrReturnInfo; diff --git a/godot-core/src/meta/property_info.rs b/godot-core/src/meta/property_info.rs index a6a1dcec4..f14e63b7d 100644 --- a/godot-core/src/meta/property_info.rs +++ b/godot-core/src/meta/property_info.rs @@ -7,7 +7,9 @@ use crate::builtin::{GString, StringName}; use crate::global::{PropertyHint, PropertyUsageFlags}; -use crate::meta::{ArrayElement, ClassName, GodotType, PackedArrayElement}; +use crate::meta::{ + element_godot_type_name, ArrayElement, ClassName, GodotType, PackedArrayElement, +}; use crate::registry::property::{Export, Var}; use crate::sys; use godot_ffi::VariantType; @@ -234,7 +236,7 @@ impl PropertyHintInfo { pub fn var_array_element() -> Self { Self { hint: PropertyHint::ARRAY_TYPE, - hint_string: GString::from(T::godot_type_name()), + hint_string: GString::from(element_godot_type_name::()), } } diff --git a/godot-core/src/meta/traits.rs b/godot-core/src/meta/traits.rs index 8f7d2168c..fa18d9259 100644 --- a/godot-core/src/meta/traits.rs +++ b/godot-core/src/meta/traits.rs @@ -5,15 +5,14 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use godot_ffi as sys; - -use crate::builtin::Variant; +use crate::builtin::{Variant, VariantType}; use crate::global::PropertyUsageFlags; use crate::meta::error::ConvertError; use crate::meta::{ sealed, ClassName, FromGodot, GodotConvert, PropertyHintInfo, PropertyInfo, ToGodot, }; use crate::registry::method::MethodParamOrReturnInfo; +use godot_ffi as sys; // Re-export sys traits in this module, so all are in one place. use crate::registry::property::builtin_type_string; @@ -162,7 +161,10 @@ pub trait GodotType: GodotConvert + sealed::Sealed + Sized + 'static message = "`Array` can only store element types supported in Godot arrays (no nesting).", label = "has invalid element type" )] -pub trait ArrayElement: GodotType + ToGodot + FromGodot + sealed::Sealed + meta::ParamType { +pub trait ArrayElement: ToGodot + FromGodot + sealed::Sealed + meta::ParamType { + // Note: several indirections in ArrayElement and the global `element_*` functions go through `GodotConvert::Via`, + // to not require Self: GodotType. What matters is how array elements map to Godot on the FFI level (GodotType trait). + /// Returns the representation of this type as a type string. /// /// Used for elements in arrays (the latter despite `ArrayElement` not having a direct relation). @@ -172,7 +174,7 @@ pub trait ArrayElement: GodotType + ToGodot + FromGodot + sealed::Sealed + meta: #[doc(hidden)] fn element_type_string() -> String { // Most array elements and all packed array elements are builtin types, so this is a good default. - builtin_type_string::() + builtin_type_string::() } #[doc(hidden)] @@ -182,6 +184,23 @@ pub trait ArrayElement: GodotType + ToGodot + FromGodot + sealed::Sealed + meta: } } +// Non-polymorphic helper functions, to avoid constant `::` in the code. + +#[doc(hidden)] +pub(crate) fn element_variant_type() -> VariantType { + ::Ffi::variant_type() +} + +#[doc(hidden)] +pub(crate) fn element_godot_type_name() -> String { + ::godot_type_name() +} + +// #[doc(hidden)] +// pub(crate) fn element_godot_type_name() -> String { +// ::godot_type_name() +// } + /// Marker trait to identify types that can be stored in `Packed*Array` types. #[diagnostic::on_unimplemented( message = "`Packed*Array` can only store element types supported in Godot packed arrays.", diff --git a/godot-core/src/obj/gd.rs b/godot-core/src/obj/gd.rs index b68160f0f..eaf5a0e65 100644 --- a/godot-core/src/obj/gd.rs +++ b/godot-core/src/obj/gd.rs @@ -692,6 +692,7 @@ impl GodotConvert for Gd { } impl ToGodot for Gd { + // TODO return RefArg here? type ToVia<'v> = Gd; fn to_godot(&self) -> Self::ToVia<'_> { diff --git a/godot-ffi/src/conv.rs b/godot-ffi/src/conv.rs index 882a7d34e..94a901680 100644 --- a/godot-ffi/src/conv.rs +++ b/godot-ffi/src/conv.rs @@ -25,11 +25,23 @@ pub fn u32_to_usize(i: u32) -> usize { unsafe { i.try_into().unwrap_unchecked() } } -/// Converts a rust-bool into a sys-bool. +/// Converts a Rust-bool into a sys-bool. pub const fn bool_to_sys(value: bool) -> sys::GDExtensionBool { value as sys::GDExtensionBool } +/// Converts a sys-bool to Rust-bool. +/// +/// # Panics +/// If the value is not a valid sys-bool (0 or 1). +pub fn bool_from_sys(value: sys::GDExtensionBool) -> bool { + match value { + SYS_TRUE => true, + SYS_FALSE => false, + _ => panic!("Invalid GDExtensionBool value: {}", value), + } +} + /// Convert a list into a pointer + length pair. Should be used together with [`ptr_list_from_sys`]. /// /// If `list_from_sys` is not called on this list then that will cause a memory leak. diff --git a/godot-ffi/src/toolbox.rs b/godot-ffi/src/toolbox.rs index 645b3f054..9ac02f157 100644 --- a/godot-ffi/src/toolbox.rs +++ b/godot-ffi/src/toolbox.rs @@ -187,6 +187,67 @@ pub fn unqualified_type_name() -> &'static str { } */ +/// Like [`std::any::type_name`], but returns a short type name without module paths. +pub fn short_type_name() -> String { + let full_name = std::any::type_name::(); + strip_module_paths(full_name) +} + +/// Like [`std::any::type_name_of_val`], but returns a short type name without module paths. +pub fn short_type_name_of_val(val: &T) -> String { + let full_name = std::any::type_name_of_val(val); + strip_module_paths(full_name) +} + +/// Helper function to strip module paths from a fully qualified type name. +fn strip_module_paths(full_name: &str) -> String { + let mut result = String::new(); + let mut identifier = String::new(); + + let mut chars = full_name.chars().peekable(); + + while let Some(c) = chars.next() { + match c { + '<' | '>' | ',' | ' ' | '&' | '(' | ')' | '[' | ']' => { + // Process the current identifier. + if !identifier.is_empty() { + let short_name = identifier.split("::").last().unwrap_or(&identifier); + result.push_str(short_name); + identifier.clear(); + } + result.push(c); + + // Handle spaces after commas for readability. + if c == ',' && chars.peek().map_or(false, |&next_c| next_c != ' ') { + result.push(' '); + } + } + ':' => { + // Check for '::' indicating module path separator. + if chars.peek() == Some(&':') { + // Skip the second ':' + chars.next(); + identifier.push_str("::"); + } else { + identifier.push(c); + } + } + _ => { + // Part of an identifier. + identifier.push(c); + } + } + } + + // Process any remaining identifier. + if !identifier.is_empty() { + let short_name = identifier.split("::").last().unwrap_or(&identifier); + result.push_str(short_name); + } + + result +} + // ---------------------------------------------------------------------------------------------------------------------------------------------- // Private helpers @@ -457,3 +518,53 @@ mod manual_init_cell { } pub(crate) use manual_init_cell::ManualInitCell; + +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Unit tests + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_short_type_name() { + assert_eq!(short_type_name::(), "i32"); + assert_eq!(short_type_name::>(), "Option"); + assert_eq!( + short_type_name::, String>>(), + "Result, String>" + ); + assert_eq!( + short_type_name::, String>>>(), + "Vec, String>>" + ); + assert_eq!( + short_type_name::>>(), + "HashMap>" + ); + assert_eq!( + short_type_name::, String>>(), + "Result, String>" + ); + assert_eq!(short_type_name::(), "i32"); + assert_eq!(short_type_name::>(), "Vec"); + } + + #[test] + fn test_short_type_name_of_val() { + let value = Some(42); + assert_eq!(short_type_name_of_val(&value), "Option"); + + let result: Result<_, String> = Ok(Some(42)); + assert_eq!( + short_type_name_of_val(&result), + "Result, String>" + ); + + let vec = vec![result]; + assert_eq!( + short_type_name_of_val(&vec), + "Vec, String>>" + ); + } +}