godot_core/meta/shape.rs
1/*
2 * Copyright (c) godot-rust; Bromeon and contributors.
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
6 */
7
8//! Static type descriptors for Rust types towards Godot.
9//!
10//! The symbols in this module and primarily [`GodotShape`] are used to describe the _shape_ of a Rust type, which combines information about:
11//! - To which Godot type it maps.
12//! - Metadata relevant for properties (var/export).
13//! - Metadata relevant for method signatures (parameters and return values).
14//!
15//! These shapes are then transformed to lower-level GDExtension descriptors, located in [`register::info`][crate::registry::info].
16//! Godot then accepts those for registration.
17
18use std::borrow::Cow;
19use std::fmt::Display;
20
21use godot_ffi as sys;
22
23use crate::builtin::{CowStr, GString, StringName, VariantType};
24use crate::global::godot_str;
25use crate::meta::{ClassId, GodotType};
26use crate::obj::EngineEnum as _;
27use crate::registry::info::{
28 ParamMetadata, PropertyHint, PropertyHintInfo, PropertyInfo, PropertyUsageFlags,
29};
30
31/// The "shape" of a Godot type: whether it's a builtin, a class, an enum/bitfield, etc.
32///
33/// Describes a _static_ (compile-time) type as it should be registered with Godot; returned by [`GodotConvert::godot_shape()`].
34/// This is distinct from runtime introspection APIs such [`AnyArray::element_type()`].
35///
36/// Usually you need to deal with `GodotShape` only if you define custom types through manual `GodotConvert` impls.
37///
38/// # Information provided by the shape
39/// A shape description is used for three purposes:
40/// - Property registrations (`#[var]`) so that Godot has static type information of your type.
41/// - See [`to_var_property()`] and [`var_hint()`].
42/// - Exported properties (`#[export]`) so that properties show up correctly in the editor's inspector UI.
43/// - See [`to_export_property()`] and [`export_hint()`].
44/// - Method signatures (`#[func]`), so that Godot has the static type information for parameters and return values.
45/// - See [`to_method_signature_property()`].
46///
47/// # Property registration
48/// During registration of class properties, the runtime resolves hints and usage flags from the shape:
49///
50/// - For `#[var]`, it calls [`var_hint()`] and uses `NONE` as base usage.
51/// - For `#[export]`, it calls [`export_hint()`] and uses `DEFAULT` as base usage.
52/// - If the user specifies explicit overrides (e.g. `#[var(hint = ...)]` or `#[export(range = ...)]`), those replace hints from the shape.
53///
54/// The shape also contributes structural metadata -- variant type, class name, and additional usage flags (via
55/// [`usage_flags()`]). These are combined with the hint and base usage into a [`PropertyInfo`] for the Godot FFI call.
56///
57/// [`PropertyInfo`]: PropertyInfo
58/// [`GodotConvert::godot_shape()`]: crate::meta::GodotConvert::godot_shape
59/// [`AnyArray::element_type()`]: crate::builtin::AnyArray::element_type
60/// [`to_var_property()`]: Self::to_var_property
61/// [`to_export_property()`]: Self::to_export_property
62/// [`to_method_signature_property()`]: Self::to_method_signature_property
63/// [`var_hint()`]: Self::var_hint
64/// [`export_hint()`]: Self::export_hint
65/// [`usage_flags()`]: Self::usage_flags
66#[non_exhaustive]
67#[derive(Clone, Debug)]
68pub enum GodotShape {
69 /// The general [`Variant`][crate::builtin::Variant] type. Can hold any Godot value.
70 ///
71 /// Distinct from `Builtin { variant_type: NIL }`, which represents the `()` unit type (`void` in GDScript).
72 Variant,
73
74 /// A built-in Godot type (`int`, `String`, `Vector3`, `PackedByteArray`, etc.).
75 ///
76 /// The variant type used here must match the one from [`GodotConvert::Via`][crate::meta::GodotConvert::Via].
77 ///
78 /// Packed arrays, untyped arrays and untyped dictionaries are also represented as `Builtin`. \
79 /// Typed arrays, typed dictionaries, objects and variants have their own shape representation.
80 Builtin {
81 /// Godot variant type (e.g. `INT`, `FLOAT`, `STRING`, `VECTOR3`, `PACKED_BYTE_ARRAY`). Never `OBJECT`.
82 variant_type: VariantType,
83
84 /// Numeric precision metadata for method parameters/return values.
85 ///
86 /// Distinguishes sub-types like `i8` from `i64` even though both map to Godot's `INT`.
87 metadata: ParamMetadata,
88 },
89
90 /// A Godot object type (`Gd<T>`, `DynGd<T, D>`, `Option<Gd<T>>`, `OnReady<Gd<T>>`, etc.).
91 ///
92 /// Always has `VariantType::OBJECT`.
93 Class {
94 /// The Godot class of this object type (e.g. `Node`, `Resource`).
95 class_id: ClassId,
96
97 /// Whether this inherits from `Resource`, `Node`, or other object class.
98 heritage: ClassHeritage,
99
100 /// Whether the object can be null (e.g. `Option<Gd<T>>`).
101 ///
102 /// [`param_metadata()`][Self::param_metadata] returns:
103 /// - `NONE` if the object is nullable,
104 /// - `OBJECT_IS_REQUIRED` otherwise.
105 is_nullable: bool,
106 },
107
108 /// An [`Array<T>`][crate::builtin::Array] where `T` is not `Variant`.
109 ///
110 /// Untyped arrays are represented as `Builtin { variant_type: VariantType::ARRAY }`.
111 TypedArray {
112 /// Shape of the array element type.
113 element: GodotElementShape,
114 },
115
116 /// A [`Dictionary<K, V>`][crate::builtin::Dictionary] where at least one of `K`, `V` is not `Variant` (Godot 4.4+).
117 ///
118 /// Untyped dictionaries are represented as `Builtin { variant_type: VariantType::DICTIONARY }`.
119 TypedDictionary {
120 /// Shape of the dictionary key type.
121 key: GodotElementShape,
122
123 /// Shape of the dictionary value type.
124 value: GodotElementShape,
125 },
126
127 /// An enum or bitfield type (engine-defined or user-defined).
128 Enum {
129 /// Godot variant type of the underlying representation (typically `INT` for int-backed enums, `STRING` for string-backed).
130 variant_type: VariantType,
131
132 /// Display name and ordinal for each enumerator. `Borrowed` for compile-time data, `Owned` for dynamic enumerators.
133 enumerators: Cow<'static, [EnumeratorShape]>,
134
135 /// Godot-qualified enum name. `Some("Orientation")` or `Some("Node.ProcessMode")` for engine enums; `None` for user enums.
136 ///
137 /// When `Some`, the framework sets `class_name` + `CLASS_IS_ENUM` in `PropertyInfo` and uses `NONE` hint for `#[var]`
138 /// (Godot resolves the enum from class_name). When `None`, uses `ENUM`/`FLAGS` hint with hint_string directly.
139 ///
140 // TODO(v0.6): In future, user enums could set this to register constants with Godot via `classdb_register_extension_class_integer_constant`,
141 // enabling GDScript to reference them by name. The `is_bitfield` field maps to that FFI method's `p_is_bitfield` parameter.
142 // Decide if we should even *require* enums to be associated with Godot classes or globally -- meaning this would become non-optional.
143 // There would need to be a differentiator for "inside a class" or "global" (but still registered with Godot).
144 godot_name: Option<CowStr>,
145
146 /// Whether this is a bitfield:
147 /// * `true` for bitfields (`FLAGS` hint, `CLASS_IS_BITFIELD` usage).
148 /// * `false` for regular enums (`ENUM` hint, `CLASS_IS_ENUM` usage).
149 is_bitfield: bool,
150 },
151
152 /// Fully custom property metadata. Use only when the type doesn't fit any predefined shape
153 Custom {
154 /// Godot variant type.
155 variant_type: VariantType,
156
157 /// Property hint info for `#[var]` context.
158 var_hint: PropertyHintInfo,
159
160 /// Property hint info for `#[export]` context.
161 export_hint: PropertyHintInfo,
162
163 /// Stored as `CowStr`; converted to `ClassId` only at registration time to avoid eager global cache allocation.
164 class_name: Option<CowStr>,
165
166 /// Additional usage flags.
167 ///
168 /// These are bit-ORed with the base usage, see [`to_var_property()`][Self::to_var_property] and
169 /// [`to_export_property()`][Self::to_export_property].
170 ///
171 /// Typically you can use `NONE` if the shape doesn't need extra flags.
172 usage_flags: PropertyUsageFlags,
173
174 /// Numeric precision metadata for method parameters/return values.
175 metadata: ParamMetadata,
176 },
177}
178
179impl GodotShape {
180 /// Creates `GodotShape::Builtin` for a type `T` directly representable as a Godot builtin, including packed arrays.
181 ///
182 /// Returns either of:
183 /// * Variant, if `T` is `Variant`.
184 /// * `GodotShape::Builtin { variant_type: ffi_variant_type::<T>().variant_as_nil() }` for other builtins.
185 ///
186 /// Do not use for objects, typed arrays/dictionaries or enums; those have their own shape variants.
187 pub fn of_builtin<T: GodotType>() -> Self {
188 match crate::meta::ffi_variant_type::<T>() {
189 sys::ExtVariantType::Variant => Self::Variant,
190 ext => Self::Builtin {
191 variant_type: ext.variant_as_nil(),
192 metadata: T::default_metadata(),
193 },
194 }
195 }
196
197 /// Returns the parameter metadata for this shape, used in method signature registration.
198 ///
199 /// - `Builtin` and `Custom`: returns the stored `metadata` field.
200 /// - `Class` with `is_nullable: false`: returns [`ParamMetadata::OBJECT_IS_REQUIRED`].
201 /// - All other shapes: returns [`ParamMetadata::NONE`].
202 pub fn param_metadata(&self) -> ParamMetadata {
203 match self {
204 Self::Builtin { metadata, .. } | Self::Custom { metadata, .. } => *metadata,
205
206 Self::Class {
207 is_nullable: false, ..
208 } => ParamMetadata::OBJECT_IS_REQUIRED,
209
210 _ => ParamMetadata::NONE,
211 }
212 }
213
214 /// Returns the Godot `VariantType` for this shape.
215 pub fn variant_type(&self) -> VariantType {
216 match self {
217 Self::Variant => VariantType::NIL,
218 Self::Builtin { variant_type, .. } => *variant_type,
219 Self::Class { .. } => VariantType::OBJECT,
220 Self::Enum { variant_type, .. } => *variant_type,
221 Self::Custom { variant_type, .. } => *variant_type,
222 Self::TypedArray { .. } => VariantType::ARRAY,
223 Self::TypedDictionary { .. } => VariantType::DICTIONARY,
224 }
225 }
226
227 /// Property hint for `#[var]` context.
228 pub fn var_hint(&self) -> PropertyHintInfo {
229 match self {
230 Self::Variant | Self::Builtin { .. } | Self::Class { .. } => PropertyHintInfo::none(),
231 Self::Enum {
232 godot_name,
233 enumerators,
234 is_bitfield,
235 ..
236 } => enum_hint_info(enumerators, *is_bitfield, godot_name.is_some()),
237
238 Self::Custom { var_hint, .. } => var_hint.clone(),
239
240 Self::TypedArray {
241 element: element_shape,
242 } => PropertyHintInfo {
243 hint: PropertyHint::ARRAY_TYPE,
244 hint_string: GString::from(&element_shape.element_godot_type_name()),
245 },
246
247 Self::TypedDictionary {
248 key: key_shape,
249 value: value_shape,
250 } => {
251 // PropertyHint::DICTIONARY_TYPE, only available since Godot 4.4 -- so the `if` is essentially a version check.
252 if let Some(hint) = PropertyHint::try_from_ord(38) {
253 PropertyHintInfo {
254 hint,
255 hint_string: godot_str!(
256 "{};{}",
257 key_shape.element_godot_type_name(),
258 value_shape.element_godot_type_name()
259 ),
260 }
261 } else {
262 let _unused = (key_shape, value_shape);
263 PropertyHintInfo::none()
264 }
265 }
266 }
267 }
268
269 /// Property hint for `#[export]` context.
270 ///
271 /// For enums, always uses `ENUM`/`FLAGS` + hint_string (even engine enums need explicit hints for export).
272 pub fn export_hint(&self) -> PropertyHintInfo {
273 match self {
274 Self::Variant => PropertyHintInfo::none(),
275
276 Self::Builtin { variant_type, .. } => {
277 // In 4.3+, packed arrays use a TYPE_STRING hint with their element type.
278 // See https://github.com/godotengine/godot/pull/82952.
279 if sys::GdextBuild::since_api("4.3") {
280 if let Some(elem_vtype) = packed_element_variant_type(*variant_type) {
281 return PropertyHintInfo {
282 hint: PropertyHint::TYPE_STRING,
283 hint_string: GString::from(&format_elements_untyped(elem_vtype)),
284 };
285 }
286 PropertyHintInfo::none()
287 } else {
288 // Pre-4.3 Godot uses the type name in hint_string even with NONE hint.
289 PropertyHintInfo {
290 hint: PropertyHint::NONE,
291 hint_string: GString::from(variant_type.godot_type_name()),
292 }
293 }
294 }
295
296 Self::Class {
297 class_id, heritage, ..
298 } => match heritage {
299 ClassHeritage::Node => PropertyHintInfo {
300 hint: PropertyHint::NODE_TYPE,
301 hint_string: class_id.to_gstring(),
302 },
303 ClassHeritage::Resource => PropertyHintInfo {
304 hint: PropertyHint::RESOURCE_TYPE,
305 hint_string: class_id.to_gstring(),
306 },
307 ClassHeritage::DynResource { implementors } => PropertyHintInfo {
308 hint: PropertyHint::RESOURCE_TYPE,
309 hint_string: GString::from(&join_class_ids(implementors)),
310 },
311 ClassHeritage::Other => PropertyHintInfo::none(),
312 },
313
314 Self::TypedArray {
315 element: element_shape,
316 } => PropertyHintInfo {
317 hint: PropertyHint::TYPE_STRING,
318 hint_string: GString::from(&element_shape.element_type_string()),
319 },
320
321 Self::TypedDictionary {
322 key: key_shape,
323 value: value_shape,
324 } => {
325 if sys::GdextBuild::since_api("4.4") {
326 PropertyHintInfo {
327 hint: PropertyHint::TYPE_STRING,
328 hint_string: godot_str!(
329 "{};{}",
330 key_shape.element_type_string(),
331 value_shape.element_type_string()
332 ),
333 }
334 } else {
335 PropertyHintInfo::none()
336 }
337 }
338
339 Self::Enum {
340 enumerators,
341 is_bitfield,
342 ..
343 } => enum_hint_info(enumerators, *is_bitfield, false),
344
345 Self::Custom { export_hint, .. } => export_hint.clone(),
346 }
347 }
348
349 /// Converts `godot_name`/`class_name` to a `StringName`. Only called during registration.
350 ///
351 /// For engine enums, returns the enum's qualified name (e.g. `"Node.ProcessMode"`).
352 /// For classes, returns the class name.
353 /// For other types, returns an empty `StringName`.
354 pub(crate) fn class_name_or_none(&self) -> StringName {
355 match self {
356 Self::Variant
357 | Self::Builtin { .. }
358 | Self::TypedArray { .. }
359 | Self::TypedDictionary { .. } => StringName::default(),
360 Self::Class { class_id, .. } => class_id.to_string_name(),
361 Self::Enum { godot_name, .. } => match godot_name {
362 Some(name) => StringName::from(name.as_ref()),
363 None => StringName::default(),
364 },
365 Self::Custom { class_name, .. } => match class_name {
366 Some(name) => StringName::from(name.as_ref()),
367 None => StringName::default(),
368 },
369 }
370 }
371
372 /// Shape-specific usage flags for property registration.
373 ///
374 /// These are combined with the base usage, which is `NONE` for `#[var]` and `DEFAULT` for `#[export]`.
375 // Only engine enums (those with `godot_name`) get `CLASS_IS_ENUM`. User enums don't set this flag: `CLASS_IS_ENUM` tells Godot to resolve
376 // the enum's enumerators via `class_name` in ClassDB -- but user enums aren't registered there yet. Setting the flag without a valid
377 // `class_name` would cause Godot to look up a nonexistent name. Once we call `classdb_register_extension_class_integer_constant` for user
378 // enums (making them visible to GDScript by name), they can set `godot_name` and get `CLASS_IS_ENUM` automatically.
379 pub fn usage_flags(&self) -> PropertyUsageFlags {
380 match self {
381 Self::Variant => PropertyUsageFlags::NIL_IS_VARIANT,
382
383 Self::Builtin { .. }
384 | Self::Class { .. }
385 | Self::TypedArray { .. }
386 | Self::TypedDictionary { .. } => PropertyUsageFlags::NONE,
387
388 Self::Enum {
389 godot_name,
390 is_bitfield,
391 ..
392 } => match (godot_name, *is_bitfield) {
393 (Some(_), true) => PropertyUsageFlags::CLASS_IS_BITFIELD,
394 (Some(_), false) => PropertyUsageFlags::CLASS_IS_ENUM,
395 (None, _) => PropertyUsageFlags::NONE, // User enums are currently not yet registered.
396 },
397
398 Self::Custom { usage_flags, .. } => *usage_flags,
399 }
400 }
401
402 /// Builds the low-level Godot property info for method parameter/return type registration.
403 ///
404 /// Uses:
405 /// - Hint and hint string: [`var_hint()`][Self::var_hint].
406 /// - Usage: [`DEFAULT`][PropertyUsageFlags::DEFAULT] (required for method params; not shown in editor).
407 pub fn to_method_signature_property(&self, property_name: &str) -> PropertyInfo {
408 let hint_info = self.var_hint();
409 self.to_property(property_name, hint_info, PropertyUsageFlags::DEFAULT)
410 }
411
412 /// Builds the low-level Godot property info for `#[var]` context.
413 ///
414 /// Uses:
415 /// - Hint and hint string: [`var_hint()`][Self::var_hint].
416 /// - Base usage: [`NONE`][PropertyUsageFlags::NONE], combined with specific [`usage_flags()`][Self::usage_flags] from this shape.
417 /// Property is accessible from GDScript, but not shown in editor or saved.
418 ///
419 /// See also [`PropertyInfo::new_var()`].
420 pub fn to_var_property(&self, property_name: &str) -> PropertyInfo {
421 let hint_info = self.var_hint();
422 self.to_property(property_name, hint_info, PropertyUsageFlags::NONE)
423 }
424
425 /// Builds the low-level Godot property info for `#[export]` context.
426 ///
427 /// Uses:
428 /// - Hint and hint string: [`export_hint()`][Self::export_hint].
429 /// - Usage: [`DEFAULT`][PropertyUsageFlags::DEFAULT], combined with specific [`usage_flags()`][Self::usage_flags] from this shape.
430 /// Property is shown in editor and saved.
431 ///
432 /// See also [`PropertyInfo::new_export()`].
433 pub fn to_export_property(&self, property_name: &str) -> PropertyInfo {
434 let hint_info = self.export_hint();
435 self.to_property(property_name, hint_info, PropertyUsageFlags::DEFAULT)
436 }
437
438 /// Low-level builder for [`PropertyInfo`]. Derives `class_name`, `variant_type`, and shape-specific usage flags from
439 /// `self`, but takes `hint_info` and `base_usage` as parameters because they depend on context (`#[var]` vs
440 /// `#[export]`) and may be overridden by the user (e.g. `#[var(hint = ..., hint_string = "...")]`).
441 ///
442 /// Prefer [`to_var_property()`](Self::to_var_property) or [`to_export_property()`](Self::to_export_property)
443 /// when no user override is involved.
444 fn to_property(
445 &self,
446 property_name: &str,
447 hint_info: PropertyHintInfo,
448 base_usage: PropertyUsageFlags,
449 ) -> PropertyInfo {
450 use crate::builtin::StringName;
451
452 let class_name = self.class_name_or_none();
453 let variant_type = self.variant_type();
454 let usage = base_usage | self.usage_flags();
455
456 PropertyInfo {
457 variant_type,
458 class_name,
459 property_name: StringName::from(property_name),
460 hint_info,
461 usage,
462 }
463 }
464}
465
466// ----------------------------------------------------------------------------------------------------------------------------------------------
467// ClassAncestor
468
469/// Which tree in the Godot hierarchy a class belongs to; determines how it appears in property hints.
470///
471/// Used inside [`GodotShape::Class`].
472#[derive(Clone, Debug)]
473pub enum ClassHeritage {
474 /// A class inheriting from [`Node`][crate::classes::Node] (uses `NODE_TYPE` hint for `#[export]`).
475 Node,
476
477 /// A class inheriting from [`Resource`][crate::classes::Resource] (uses `RESOURCE_TYPE` hint for `#[export]`).
478 Resource,
479
480 /// A `DynGd<T, D>` where `T` inherits `Resource`. Stores the concrete implementor `ClassId`s from the `#[godot_dyn]` registry.
481 DynResource {
482 /// Class IDs of all concrete implementors registered via `#[godot_dyn]` or [`AsDyn`][crate::obj::AsDyn] for the trait.
483 implementors: Vec<ClassId>,
484 },
485
486 /// Any other class that doesn't inherit from `Node` or `Resource`. No special hint for `#[export]`.
487 Other,
488}
489
490impl ClassHeritage {
491 /// Returns the `PropertyHint` for `#[export]` context.
492 pub fn export_property_hint(&self) -> PropertyHint {
493 match self {
494 Self::Resource | Self::DynResource { .. } => PropertyHint::RESOURCE_TYPE,
495 Self::Node => PropertyHint::NODE_TYPE,
496 Self::Other => PropertyHint::NONE,
497 }
498 }
499}
500
501// ----------------------------------------------------------------------------------------------------------------------------------------------
502// Inner/nested shape
503
504/// Same as [`GodotShape`], but for element types nested in typed arrays/dictionaries.
505///
506/// Matches the same layout as `GodotShape`, exists to avoid recursive definition (and also `Box` allocations). Also constrains the possible
507/// shapes (elements cannot be typed arrays/dictionaries themselves).
508///
509/// In contrast to [`ElementType`][crate::meta::inspect::ElementType], this is a _static_ type description for Godot registration purposes.
510///
511/// Use [`into_outer()`][Self::into_outer] to convert into a full `GodotShape`.
512#[non_exhaustive]
513#[derive(Clone, Debug)]
514pub enum GodotElementShape {
515 // Inner types are not structs like Builtin(BuiltinShape), to avoid type proliferation in niche APIs. to_outer() is good enough.
516 Variant,
517
518 Builtin {
519 variant_type: VariantType,
520 },
521
522 Class {
523 class_id: ClassId,
524 heritage: ClassHeritage,
525 },
526
527 Enum {
528 variant_type: VariantType,
529 enumerators: Cow<'static, [EnumeratorShape]>,
530 godot_name: Option<CowStr>,
531 is_bitfield: bool,
532 },
533
534 Custom {
535 variant_type: VariantType,
536 var_hint: PropertyHintInfo,
537 export_hint: PropertyHintInfo,
538 class_name: Option<CowStr>,
539 usage_flags: PropertyUsageFlags,
540 },
541}
542
543impl GodotElementShape {
544 #[rustfmt::skip]
545 pub(crate) fn new(outer: GodotShape) -> Self {
546 type GShape = GodotShape;
547
548 match outer {
549 GShape::Variant
550 => Self::Variant,
551
552 // Strip metadata: element shapes don't carry param metadata.
553 GShape::Builtin { variant_type, .. }
554 => Self::Builtin { variant_type },
555
556 // Strip is_nullable: element shapes don't carry object-nullability metadata.
557 GShape::Class { class_id, heritage, .. }
558 => Self::Class { class_id, heritage },
559
560 GShape::Enum { variant_type, enumerators, godot_name, is_bitfield }
561 => Self::Enum { variant_type, enumerators, godot_name, is_bitfield },
562
563 // Strip metadata.
564 GShape::Custom { variant_type, var_hint, export_hint, class_name, usage_flags, .. }
565 => Self::Custom { variant_type, var_hint, export_hint, class_name, usage_flags },
566
567 GShape::TypedArray { .. } |
568 GShape::TypedDictionary { .. } => panic!("nested shapes cannot be typed arrays/dictionaries")
569 }
570 }
571
572 /// Converts this nested shape into a full `GodotShape`. Infallible.
573 #[rustfmt::skip]
574 pub fn into_outer(self) -> GodotShape {
575 type G = GodotShape;
576
577 match self {
578 Self::Variant
579 => G::Variant,
580
581 // Fill default metadata on conversion out.
582 Self::Builtin { variant_type }
583 => G::Builtin { variant_type, metadata: ParamMetadata::NONE },
584
585 // Fill is_nullable: true (nullable by default for element shapes).
586 Self::Class { class_id, heritage }
587 => G::Class { class_id, heritage, is_nullable: true },
588
589 Self::Enum { variant_type, enumerators, godot_name, is_bitfield }
590 => G::Enum { variant_type, enumerators, godot_name, is_bitfield },
591
592 // Fill NONE metadata (arrays/dictionaries don't store param metadata).
593 Self::Custom { variant_type, var_hint, export_hint, class_name, usage_flags}
594 => G::Custom { variant_type, var_hint, export_hint, class_name, usage_flags, metadata: ParamMetadata::NONE },
595 }
596 }
597
598 /// Returns the Godot type name for use in `#[var]` array/dictionary type hints.
599 ///
600 /// Defaults to the `Via` type's name (e.g. `"int"` for `i32`). Engine enums override this to return their qualified class name
601 /// (e.g. `"Node.ProcessMode"`).
602 pub(crate) fn element_godot_type_name(&self) -> String {
603 match self {
604 Self::Variant => VariantType::NIL.godot_type_name().to_string(),
605 Self::Builtin { variant_type } => variant_type.godot_type_name().to_string(),
606 Self::Class { class_id, .. } => class_id.to_cow_str().to_string(),
607 Self::Enum {
608 godot_name,
609 variant_type,
610 ..
611 } => match godot_name {
612 Some(name) => name.to_string(),
613 None => variant_type.godot_type_name().to_string(),
614 },
615 Self::Custom { variant_type, .. } => variant_type.godot_type_name().to_string(),
616 }
617 }
618
619 /// Returns the representation of this type as an element inside an array, e.g. `"4:"` for string, or `"24:34/MyClass"` for objects.
620 ///
621 /// `4` and `24` are variant type ords; `34` is `PropertyHint::NODE_TYPE` ord.
622 ///
623 /// See [`PropertyHint::TYPE_STRING`] and
624 /// [upstream docs](https://docs.godotengine.org/en/stable/classes/class_%40globalscope.html#enum-globalscope-propertyhint).
625 pub(crate) fn element_type_string(&self) -> String {
626 match self {
627 Self::Variant
628 | Self::Builtin {
629 variant_type: VariantType::NIL,
630 } => {
631 // Variant (or void) as element: untyped, no hint.
632 format_elements_untyped(VariantType::NIL)
633 }
634
635 Self::Builtin { variant_type } => {
636 if sys::GdextBuild::since_api("4.3") {
637 format_elements_untyped(*variant_type)
638 } else {
639 format!("{}:{}", variant_type.ord(), variant_type.godot_type_name())
640 }
641 }
642
643 Self::Class { class_id, heritage } => {
644 let export_hint = heritage.export_property_hint();
645 sys::strict_assert_ne!(
646 export_hint,
647 PropertyHint::NONE,
648 "element_type_string() should only be called for exportable object classes (Resource or Node), \
649 but got ClassAncestor::Other for class `{}`",
650 class_id.to_cow_str()
651 );
652
653 let hint_string = match heritage {
654 ClassHeritage::DynResource { implementors } => join_class_ids(implementors),
655 _ => class_id.to_cow_str().to_string(),
656 };
657 format_elements_typed(VariantType::OBJECT, export_hint, &hint_string)
658 }
659
660 Self::Enum { .. } => {
661 let outer = self.clone().into_outer(); // slightly expensive
662 let variant_type = outer.variant_type();
663 let info = outer.export_hint();
664 format_elements_typed(variant_type, info.hint, &info.hint_string)
665 }
666
667 Self::Custom {
668 variant_type,
669 var_hint,
670 ..
671 } => format_elements_typed(*variant_type, var_hint.hint, &var_hint.hint_string),
672 }
673 }
674}
675
676// ----------------------------------------------------------------------------------------------------------------------------------------------
677
678/// Describes a single enumerator entry: display name and ordinal value. Used in [`GodotShape::Enum`].
679#[derive(Clone, Debug)]
680pub struct EnumeratorShape {
681 pub name: CowStr,
682 /// Ordinal value. `None` for string-backed enums (hint string omits ordinals: `"Grass,Rock,Water"`).
683 pub value: Option<i64>,
684}
685
686impl EnumeratorShape {
687 /// Creates a new int-backed enumerator.
688 pub const fn new_int(name: &'static str, value: i64) -> Self {
689 Self {
690 name: Cow::Borrowed(name),
691 value: Some(value),
692 }
693 }
694
695 /// Creates a new string-backed enumerator (no ordinal in hint string).
696 pub const fn new_string(name: &'static str) -> Self {
697 Self {
698 name: Cow::Borrowed(name),
699 value: None,
700 }
701 }
702}
703
704// ----------------------------------------------------------------------------------------------------------------------------------------------
705// Global helper functions
706
707/// Builds the element type string used in Godot's `TYPE_STRING` hint for typed collections.
708///
709/// Format: `"{vtype}:"` if `hint` is `NONE`, otherwise `"{vtype}/{hint}:{hint_string}"`.
710pub(crate) fn format_elements_typed(
711 variant_type: VariantType,
712 hint: PropertyHint,
713 hint_string: impl std::fmt::Display,
714) -> String {
715 if hint == PropertyHint::NONE {
716 format!("{}:", variant_type.ord())
717 } else {
718 format!("{}/{}:{}", variant_type.ord(), hint.ord(), hint_string)
719 }
720}
721
722/// Formats the element type string for untyped collections (e.g. `Array` without `TYPE_STRING` hint), which only includes the variant type.
723///
724/// Format: `"{vtype}:"`.
725fn format_elements_untyped(variant_type: VariantType) -> String {
726 format!("{}:", variant_type.ord())
727}
728
729/// Returns `PropertyHintInfo` for an enum or bitfield: `ENUM`/`FLAGS` hint with formatted hint string.
730fn enum_hint_info(
731 enumerators: &[EnumeratorShape],
732 is_bitfield: bool,
733 is_engine_enum: bool,
734) -> PropertyHintInfo {
735 // For engine enums used as `#[var]`: before Godot 4.7, GDScript registers these with `NONE` hint, relying on
736 // `class_name` + `CLASS_IS_ENUM` usage flag. From 4.7 onward, GDScript provides the full `ENUM` hint with enumerator list.
737 if is_engine_enum && sys::GdextBuild::before_api("4.7") {
738 return PropertyHintInfo::none();
739 }
740
741 let hint = if is_bitfield {
742 PropertyHint::FLAGS
743 } else {
744 PropertyHint::ENUM
745 };
746
747 PropertyHintInfo {
748 hint,
749 hint_string: GString::from(&format_hint_string(enumerators)),
750 }
751}
752
753/// Builds `"Name:0,Name2:1"` or `"Name,Name2"` hint string from enumerators.
754fn format_hint_string(enumerators: &[EnumeratorShape]) -> String {
755 enumerators
756 .iter()
757 .map(|e| format_hint_entry(&e.name, e.value))
758 .collect::<Vec<_>>()
759 .join(",")
760}
761
762/// Formats a single hint as `"Name:value"` if value is `Some`, otherwise `"Name"`.
763pub(crate) fn format_hint_entry(name: &str, value: Option<impl Display>) -> String {
764 match value {
765 Some(v) => format!("{name}:{v}"),
766 None => name.to_string(),
767 }
768}
769
770/// Maps a packed array's variant type to its element's variant type, if applicable.
771///
772/// Returns `Some(element_type)` for packed array variant types (e.g. `PACKED_BYTE_ARRAY` → `INT`,
773/// `PACKED_STRING_ARRAY` → `STRING`), or `None` for all other variant types.
774#[rustfmt::skip]
775pub(crate) fn packed_element_variant_type(packed_vtype: VariantType) -> Option<VariantType> {
776 match packed_vtype {
777 | VariantType::PACKED_BYTE_ARRAY
778 | VariantType::PACKED_INT32_ARRAY
779 | VariantType::PACKED_INT64_ARRAY => Some(VariantType::INT),
780 | VariantType::PACKED_FLOAT32_ARRAY
781 | VariantType::PACKED_FLOAT64_ARRAY => Some(VariantType::FLOAT),
782 | VariantType::PACKED_STRING_ARRAY => Some(VariantType::STRING),
783 | VariantType::PACKED_VECTOR2_ARRAY => Some(VariantType::VECTOR2),
784 | VariantType::PACKED_VECTOR3_ARRAY => Some(VariantType::VECTOR3),
785 #[cfg(since_api = "4.3")]
786 | VariantType::PACKED_VECTOR4_ARRAY => Some(VariantType::VECTOR4),
787 | VariantType::PACKED_COLOR_ARRAY => Some(VariantType::COLOR),
788 _ => None,
789 }
790}
791
792/// Joins class IDs into a comma-separated string for use in DynGd property hints.
793fn join_class_ids(class_ids: &[ClassId]) -> String {
794 class_ids
795 .iter()
796 .map(|id| id.to_cow_str().to_string())
797 .collect::<Vec<_>>()
798 .join(",")
799}