godot_core/registry/
plugin.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
8use std::any::Any;
9use std::{any, fmt};
10
11use crate::init::InitLevel;
12use crate::meta::ClassId;
13use crate::obj::{
14    bounds, cap, Bounds, DynGd, Gd, GodotClass, Inherits, NewAlloc, Singleton, UserClass,
15    UserSingleton,
16};
17use crate::registry::callbacks;
18use crate::registry::class::GodotGetVirtual;
19use crate::{classes, sys};
20
21// TODO(bromeon): some information coming from the proc-macro API is deferred through PluginItem, while others is directly
22// translated to code. Consider moving more code to the PluginItem, which allows for more dynamic registration and will
23// be easier for a future builder API.
24
25// ----------------------------------------------------------------------------------------------------------------------------------------------
26
27/// Piece of information that is gathered by the self-registration ("plugin") system.
28///
29/// You should not manually construct this struct, but rather use [`ClassPlugin::new()`].
30#[derive(Debug)]
31pub struct ClassPlugin {
32    /// The name of the class to register plugins for.
33    ///
34    /// This is used to group plugins so that all class properties for a single class can be registered at the same time.
35    /// Incorrectly setting this value should not cause any UB but will likely cause errors during registration time.
36    pub(crate) class_name: ClassId,
37
38    /// Which [`InitLevel`] this plugin should be registered at.
39    ///
40    /// Incorrectly setting this value should not cause any UB but will likely cause errors during registration time.
41    // Init-level is per ClassPlugin and not per PluginItem, because all components of all classes are mixed together in one
42    // huge linker list. There is no per-class aggregation going on, so this allows to easily filter relevant classes.
43    pub(crate) init_level: InitLevel,
44
45    /// The actual item being registered.
46    pub(crate) item: PluginItem,
47}
48
49impl ClassPlugin {
50    /// Creates a new `ClassPlugin`, automatically setting the `class_name` and `init_level` to the values defined in [`GodotClass`].
51    pub fn new<T: GodotClass>(item: PluginItem) -> Self {
52        Self {
53            class_name: T::class_id(),
54            init_level: T::INIT_LEVEL,
55            item,
56        }
57    }
58}
59
60// ----------------------------------------------------------------------------------------------------------------------------------------------
61// Type-erased values
62
63/// Type-erased function object, holding a function which should called during class registration.
64#[derive(Copy, Clone)]
65pub struct ErasedRegisterFn {
66    // A wrapper is needed here because Debug can't be derived on function pointers with reference parameters, so this won't work:
67    // pub type ErasedRegisterFn = fn(&mut dyn std::any::Any);
68    // (see https://stackoverflow.com/q/53380040)
69    /// The actual function to be called during class registration.
70    pub raw: fn(&mut dyn Any),
71}
72
73impl fmt::Debug for ErasedRegisterFn {
74    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75        write!(f, "0x{:0>16x}", self.raw as usize)
76    }
77}
78
79/// Type-erased function object, holding a function which should be called during RPC function registration.
80#[derive(Copy, Clone)]
81pub struct ErasedRegisterRpcsFn {
82    // A wrapper is needed here because Debug can't be derived on function pointers with reference parameters, so this won't work:
83    // pub type ErasedRegisterFn = fn(&mut dyn std::any::Any);
84    // (see https://stackoverflow.com/q/53380040)
85    /// The actual function to be called during RPC function registration.
86    ///
87    /// This should be called with a reference to the object that we want to register RPC functions for.
88    pub raw: fn(&mut dyn Any),
89}
90
91impl fmt::Debug for ErasedRegisterRpcsFn {
92    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93        write!(f, "0x{:0>16x}", self.raw as usize)
94    }
95}
96
97/// Type-erased function which converts a `Gd<Object>` into a `DynGd<Object, D>` for some trait object `D`.
98///
99/// See [`DynTraitImpl`] for usage.
100pub type ErasedDynifyFn = unsafe fn(Gd<classes::Object>) -> ErasedDynGd;
101
102/// Type-erased `DynGd<Object, D>` for some trait object `D`.
103///
104/// See [`DynTraitImpl`] for usage.
105pub struct ErasedDynGd {
106    pub boxed: Box<dyn Any>,
107}
108
109type GodotCreateFn = unsafe extern "C" fn(
110    _class_userdata: *mut std::ffi::c_void,
111    #[cfg(since_api = "4.4")] _notify_postinitialize: sys::GDExtensionBool,
112) -> sys::GDExtensionObjectPtr;
113
114// ----------------------------------------------------------------------------------------------------------------------------------------------
115// Plugin items
116
117/// Represents the data part of a [`ClassPlugin`] instance.
118///
119/// Each enumerator represents a different item in Rust code, which is processed by an independent proc macro (for example,
120/// `#[derive(GodotClass)]` on structs, or `#[godot_api]` on impl blocks).
121#[derive(Clone, Debug)]
122pub enum PluginItem {
123    /// Class definition itself, must always be available -- created by `#[derive(GodotClass)]`.
124    Struct(Struct),
125
126    /// Collected from `#[godot_api] impl MyClass`.
127    InherentImpl(InherentImpl),
128
129    /// Collected from `#[godot_api] impl I... for MyClass`.
130    ITraitImpl(ITraitImpl),
131
132    /// Collected from `#[godot_dyn]` macro invocations.
133    DynTraitImpl(DynTraitImpl),
134}
135
136/// Helper function which checks that the field has not been set before.
137fn set<T>(field: &mut Option<T>, value: T) {
138    assert!(field.is_none(), "attempted to set field more than once",);
139    *field = Some(value);
140}
141
142/// The data for a class definition.
143#[derive(Clone, Debug)]
144pub struct Struct {
145    /// The name of the base class in Godot.
146    ///
147    /// This must match [`GodotClass::Base`]'s class name.
148    pub(crate) base_class_name: ClassId,
149
150    /// Godot low-level `create` function, wired up to library-generated `init`.
151    ///
152    /// For `#[class(no_init)]`, behavior depends on Godot version:
153    /// - 4.5 and later: `None`
154    /// - until 4.4: a dummy function that fails, to not break hot reloading.
155    ///
156    /// This is mutually exclusive with [`ITraitImpl::user_create_fn`].
157    pub(crate) generated_create_fn: Option<GodotCreateFn>,
158
159    /// Godot low-level `recreate` function, used when hot-reloading a user class.
160    ///
161    /// This is mutually exclusive with [`ITraitImpl::user_recreate_fn`].
162    pub(crate) generated_recreate_fn: Option<
163        unsafe extern "C" fn(
164            p_class_userdata: *mut std::ffi::c_void,
165            p_object: sys::GDExtensionObjectPtr,
166        ) -> sys::GDExtensionClassInstancePtr,
167    >,
168
169    /// Callback to library-generated function which registers properties in the `struct` definition.
170    pub(crate) register_properties_fn: ErasedRegisterFn,
171
172    /// Callback on refc-increment. Only for `RefCounted` classes.
173    pub(crate) reference_fn: sys::GDExtensionClassReference,
174
175    /// Callback on refc-decrement. Only for `RefCounted` classes.
176    pub(crate) unreference_fn: sys::GDExtensionClassUnreference,
177
178    /// Function called by Godot when an object of this class is freed.
179    ///
180    /// Always implemented as [`callbacks::free`].
181    pub(crate) free_fn: unsafe extern "C" fn(
182        _class_user_data: *mut std::ffi::c_void,
183        instance: sys::GDExtensionClassInstancePtr,
184    ),
185
186    /// `#[class(singleton)]`
187    pub(crate) register_singleton_fn: Option<fn()>,
188
189    /// `#[class(singleton)]`
190    pub(crate) unregister_singleton_fn: Option<fn()>,
191
192    /// Calls `__before_ready()`, if there is at least one `OnReady` field. Used if there is no `#[godot_api] impl` block
193    /// overriding ready.
194    pub(crate) default_get_virtual_fn: Option<GodotGetVirtual>,
195
196    /// Whether `#[class(tool)]` was used.
197    pub(crate) is_tool: bool,
198
199    /// Whether the base class is an `EditorPlugin`.
200    pub(crate) is_editor_plugin: bool,
201
202    /// Whether `#[class(internal)]` was used.
203    pub(crate) is_internal: bool,
204
205    /// Whether the class has a default constructor.
206    pub(crate) is_instantiable: bool,
207}
208
209impl Struct {
210    pub fn new<T: GodotClass + cap::ImplementsGodotExports>() -> Self {
211        let refcounted = <T::Memory as bounds::Memory>::IS_REF_COUNTED;
212
213        Self {
214            base_class_name: T::Base::class_id(),
215            generated_create_fn: None,
216            generated_recreate_fn: None,
217            register_properties_fn: ErasedRegisterFn {
218                raw: callbacks::register_user_properties::<T>,
219            },
220            free_fn: callbacks::free::<T>,
221            register_singleton_fn: None,
222            unregister_singleton_fn: None,
223            default_get_virtual_fn: None,
224            is_tool: false,
225            is_editor_plugin: false,
226            is_internal: false,
227            is_instantiable: false,
228            // While Godot doesn't do anything with these callbacks for non-RefCounted classes, we can avoid instantiating them in Rust.
229            reference_fn: refcounted.then_some(callbacks::reference::<T>),
230            unreference_fn: refcounted.then_some(callbacks::unreference::<T>),
231        }
232    }
233
234    pub fn with_generated<T: GodotClass + cap::GodotDefault>(mut self) -> Self {
235        set(&mut self.generated_create_fn, callbacks::create::<T>);
236
237        set(&mut self.generated_recreate_fn, callbacks::recreate::<T>);
238        self
239    }
240
241    // Workaround for https://github.com/godot-rust/gdext/issues/874, before https://github.com/godotengine/godot/pull/99133 is merged in 4.5.
242    #[cfg(before_api = "4.5")] #[cfg_attr(published_docs, doc(cfg(before_api = "4.5")))]
243    pub fn with_generated_no_default<T: GodotClass>(mut self) -> Self {
244        set(&mut self.generated_create_fn, callbacks::create_null::<T>);
245
246        set(
247            &mut self.generated_recreate_fn,
248            callbacks::recreate_null::<T>,
249        );
250        self
251    }
252
253    pub fn with_default_get_virtual_fn<T: GodotClass + UserClass>(mut self) -> Self {
254        set(
255            &mut self.default_get_virtual_fn,
256            callbacks::default_get_virtual::<T>,
257        );
258        self
259    }
260
261    pub fn with_tool(mut self) -> Self {
262        self.is_tool = true;
263        self
264    }
265
266    pub fn with_editor_plugin(mut self) -> Self {
267        self.is_editor_plugin = true;
268        self
269    }
270
271    pub fn with_singleton<T>(mut self) -> Self
272    where
273        T: UserSingleton
274            + Bounds<Memory = bounds::MemManual, Declarer = bounds::DeclUser>
275            + NewAlloc
276            + Inherits<classes::Object>,
277    {
278        self.register_singleton_fn = Some(|| {
279            crate::classes::Engine::singleton()
280                .register_singleton(&T::class_id().to_string_name(), &T::new_alloc());
281        });
282
283        self.unregister_singleton_fn = Some(|| {
284            let singleton = T::singleton();
285            crate::classes::Engine::singleton()
286                .unregister_singleton(&T::class_id().to_string_name());
287            singleton.free();
288        });
289
290        self
291    }
292
293    pub fn with_internal(mut self) -> Self {
294        self.is_internal = true;
295        self
296    }
297
298    pub fn with_instantiable(mut self) -> Self {
299        self.is_instantiable = true;
300        self
301    }
302}
303
304/// Stores registration functions for methods, constants, and documentation from inherent `#[godot_api]` impl blocks.
305#[derive(Clone, Debug)]
306pub struct InherentImpl {
307    /// Callback to library-generated function which registers functions and constants in the `impl` block.
308    ///
309    /// Always present since that's the entire point of this `impl` block.
310    pub(crate) register_methods_constants_fn: ErasedRegisterFn,
311
312    /// Callback to library-generated function which calls [`Node::rpc_config`](crate::classes::Node::rpc_config) for each function annotated
313    /// with `#[rpc]` on the `impl` block.
314    ///
315    /// This function is called in [`UserClass::__before_ready()`](crate::obj::UserClass::__before_ready) definitions generated by the
316    /// `#[derive(GodotClass)]` macro.
317    // This field is only used during codegen-full.
318    #[cfg_attr(not(feature = "codegen-full"), expect(dead_code))]
319    pub(crate) register_rpcs_fn: Option<ErasedRegisterRpcsFn>,
320}
321
322impl InherentImpl {
323    pub fn new<T: cap::ImplementsGodotApi>() -> Self {
324        Self {
325            register_methods_constants_fn: ErasedRegisterFn {
326                raw: callbacks::register_user_methods_constants::<T>,
327            },
328            register_rpcs_fn: Some(ErasedRegisterRpcsFn {
329                raw: callbacks::register_user_rpcs::<T>,
330            }),
331        }
332    }
333}
334
335#[derive(Default, Clone, Debug)]
336pub struct ITraitImpl {
337    /// Callback to user-defined `register_class` function.
338    pub(crate) user_register_fn: Option<ErasedRegisterFn>,
339
340    /// Godot low-level `create` function, wired up to the user's `init`.
341    ///
342    /// This is mutually exclusive with [`Struct::generated_create_fn`].
343    pub(crate) user_create_fn: Option<GodotCreateFn>,
344
345    /// Godot low-level `recreate` function, used when hot-reloading a user class.
346    ///
347    /// This is mutually exclusive with [`Struct::generated_recreate_fn`].
348    pub(crate) user_recreate_fn: Option<
349        unsafe extern "C" fn(
350            p_class_userdata: *mut ::std::os::raw::c_void,
351            p_object: sys::GDExtensionObjectPtr,
352        ) -> sys::GDExtensionClassInstancePtr,
353    >,
354
355    /// User-defined `to_string` function.
356    pub(crate) user_to_string_fn: Option<
357        unsafe extern "C" fn(
358            p_instance: sys::GDExtensionClassInstancePtr,
359            r_is_valid: *mut sys::GDExtensionBool,
360            r_out: sys::GDExtensionStringPtr,
361        ),
362    >,
363
364    /// User-defined `on_notification` function.
365    pub(crate) user_on_notification_fn: Option<
366        unsafe extern "C" fn(
367            p_instance: sys::GDExtensionClassInstancePtr,
368            p_what: i32,
369            p_reversed: sys::GDExtensionBool,
370        ),
371    >,
372
373    /// User-defined `set_property` function.
374    pub(crate) user_set_fn: Option<
375        unsafe extern "C" fn(
376            p_instance: sys::GDExtensionClassInstancePtr,
377            p_name: sys::GDExtensionConstStringNamePtr,
378            p_value: sys::GDExtensionConstVariantPtr,
379        ) -> sys::GDExtensionBool,
380    >,
381
382    /// User-defined `get_property` function.
383    pub(crate) user_get_fn: Option<
384        unsafe extern "C" fn(
385            p_instance: sys::GDExtensionClassInstancePtr,
386            p_name: sys::GDExtensionConstStringNamePtr,
387            r_ret: sys::GDExtensionVariantPtr,
388        ) -> sys::GDExtensionBool,
389    >,
390
391    /// Callback for other virtual methods specific to each class.
392    pub(crate) get_virtual_fn: Option<GodotGetVirtual>,
393
394    /// User-defined `get_property_list` function.
395    pub(crate) user_get_property_list_fn: Option<
396        unsafe extern "C" fn(
397            p_instance: sys::GDExtensionClassInstancePtr,
398            r_count: *mut u32,
399        ) -> *const sys::GDExtensionPropertyInfo,
400    >,
401
402    // We do not support using this in Godot < 4.3, however it's easier to leave this in and fail elsewhere when attempting to use
403    // this in Godot < 4.3.
404    #[cfg(before_api = "4.3")] #[cfg_attr(published_docs, doc(cfg(before_api = "4.3")))]
405    pub(crate) user_free_property_list_fn: Option<
406        unsafe extern "C" fn(
407            p_instance: sys::GDExtensionClassInstancePtr,
408            p_list: *const sys::GDExtensionPropertyInfo,
409        ),
410    >,
411    /// Frees the property list created in the user-defined `get_property_list` function.
412    #[cfg(since_api = "4.3")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.3")))]
413    pub(crate) user_free_property_list_fn: Option<
414        unsafe extern "C" fn(
415            p_instance: sys::GDExtensionClassInstancePtr,
416            p_list: *const sys::GDExtensionPropertyInfo,
417            p_count: u32,
418        ),
419    >,
420
421    /// Part of user-defined `property_get_revert` function.
422    ///
423    /// This effectively just calls [`Option::is_some`] on the return value of the `property_get_revert` function.
424    pub(crate) user_property_can_revert_fn: Option<
425        unsafe extern "C" fn(
426            p_instance: sys::GDExtensionClassInstancePtr,
427            p_name: sys::GDExtensionConstStringNamePtr,
428        ) -> sys::GDExtensionBool,
429    >,
430
431    /// Part of user-defined `property_get_revert` function.
432    ///
433    /// This returns null when the return value of `property_get_revert` is `None`, and otherwise returns the value contained
434    /// within the `Some`.
435    pub(crate) user_property_get_revert_fn: Option<
436        unsafe extern "C" fn(
437            p_instance: sys::GDExtensionClassInstancePtr,
438            p_name: sys::GDExtensionConstStringNamePtr,
439            r_ret: sys::GDExtensionVariantPtr,
440        ) -> sys::GDExtensionBool,
441    >,
442    pub(crate) validate_property_fn: Option<
443        unsafe extern "C" fn(
444            p_instance: sys::GDExtensionClassInstancePtr,
445            p_property: *mut sys::GDExtensionPropertyInfo,
446        ) -> sys::GDExtensionBool,
447    >,
448}
449
450impl ITraitImpl {
451    pub fn new<T: GodotClass + cap::ImplementsGodotVirtual>() -> Self {
452        Self {
453            get_virtual_fn: Some(callbacks::get_virtual::<T>),
454            ..Default::default()
455        }
456    }
457
458    pub fn with_register<T: GodotClass + cap::GodotRegisterClass>(mut self) -> Self {
459        set(
460            &mut self.user_register_fn,
461            ErasedRegisterFn {
462                raw: callbacks::register_class_by_builder::<T>,
463            },
464        );
465
466        self
467    }
468
469    pub fn with_create<T: GodotClass + cap::GodotDefault>(mut self) -> Self {
470        set(&mut self.user_create_fn, callbacks::create::<T>);
471
472        #[cfg(since_api = "4.3")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.3")))]
473        set(&mut self.user_recreate_fn, callbacks::recreate::<T>);
474        self
475    }
476
477    pub fn with_string<T: GodotClass + cap::GodotToString>(mut self) -> Self {
478        set(&mut self.user_to_string_fn, callbacks::to_string::<T>);
479        self
480    }
481
482    pub fn with_on_notification<T: GodotClass + cap::GodotNotification>(mut self) -> Self {
483        set(
484            &mut self.user_on_notification_fn,
485            callbacks::on_notification::<T>,
486        );
487        self
488    }
489
490    pub fn with_get_property<T: GodotClass + cap::GodotGet>(mut self) -> Self {
491        set(&mut self.user_get_fn, callbacks::get_property::<T>);
492        self
493    }
494
495    pub fn with_set_property<T: GodotClass + cap::GodotSet>(mut self) -> Self {
496        set(&mut self.user_set_fn, callbacks::set_property::<T>);
497        self
498    }
499
500    pub fn with_get_property_list<T: GodotClass + cap::GodotGetPropertyList>(mut self) -> Self {
501        set(
502            &mut self.user_get_property_list_fn,
503            callbacks::get_property_list::<T>,
504        );
505
506        #[cfg(since_api = "4.3")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.3")))]
507        set(
508            &mut self.user_free_property_list_fn,
509            callbacks::free_property_list::<T>,
510        );
511        self
512    }
513
514    pub fn with_property_get_revert<T: GodotClass + cap::GodotPropertyGetRevert>(mut self) -> Self {
515        set(
516            &mut self.user_property_get_revert_fn,
517            callbacks::property_get_revert::<T>,
518        );
519        set(
520            &mut self.user_property_can_revert_fn,
521            callbacks::property_can_revert::<T>,
522        );
523        self
524    }
525
526    pub fn with_validate_property<T: GodotClass + cap::GodotValidateProperty>(mut self) -> Self {
527        set(
528            &mut self.validate_property_fn,
529            callbacks::validate_property::<T>,
530        );
531        self
532    }
533}
534
535/// Representation of a `#[godot_dyn]` invocation.
536///
537/// Stores all the information needed for `DynGd` re-enrichment.
538#[derive(Clone, Debug)]
539pub struct DynTraitImpl {
540    /// The class that this `dyn Trait` implementation corresponds to.
541    class_name: ClassId,
542
543    /// Base inherited class required for `DynGd<T, D>` exports (i.e. one specified in `#[class(base = ...)]`).
544    ///
545    /// Godot doesn't guarantee availability of all the GDExtension classes through the ClassDb while generating `PropertyHintInfo` for our exports.
546    /// Therefore, we rely on the built-in inherited base class in such cases.
547    /// Only [`class_name`][DynTraitImpl::class_name] is available at the time of adding given `DynTraitImpl` to plugin registry with `#[godot_dyn]`;
548    /// It is important to fill this information before registration.
549    ///
550    /// See also [`get_dyn_property_hint_string`][crate::registry::class::get_dyn_property_hint_string].
551    pub(crate) parent_class_name: Option<ClassId>,
552
553    /// TypeId of the `dyn Trait` object.
554    dyn_trait_typeid: any::TypeId,
555
556    /// Function used to get a `DynGd<T,D>` from a `Gd<Object>`. This is used in the [`FromGodot`](crate::meta::FromGodot) implementation
557    /// of [`DynGd`]. This function is always implemented as [`callbacks::dynify_fn::<T, D>`] where `T` is the class represented by `class_name`
558    /// and `D` is the trait object corresponding to `dyn_trait_typeid`.
559    ///
560    /// Function that converts a `Gd<Object>` to a type-erased `DynGd<Object, dyn Trait>` (with the latter erased for common storage).
561    erased_dynify_fn: ErasedDynifyFn,
562}
563
564impl DynTraitImpl {
565    pub fn new<T, D>() -> Self
566    where
567        T: GodotClass
568            + Inherits<classes::Object>
569            + crate::obj::AsDyn<D>
570            + Bounds<Declarer = bounds::DeclUser>,
571        D: ?Sized + 'static,
572    {
573        Self {
574            class_name: T::class_id(),
575            parent_class_name: None,
576            dyn_trait_typeid: std::any::TypeId::of::<D>(),
577            erased_dynify_fn: callbacks::dynify_fn::<T, D>,
578        }
579    }
580
581    /// The class that this `dyn Trait` implementation corresponds to.
582    pub fn class_name(&self) -> &ClassId {
583        &self.class_name
584    }
585
586    /// The type id of the trait object this was registered with.
587    pub fn dyn_trait_typeid(&self) -> any::TypeId {
588        self.dyn_trait_typeid
589    }
590
591    /// Convert a [`Gd<T>`] to a [`DynGd<T, D>`] using `self`.
592    ///
593    /// This will fail with `Err(object)` if the dynamic class of `object` does not match the [`ClassId`] stored in `self`.
594    pub fn get_dyn_gd<T: GodotClass, D: ?Sized + 'static>(
595        &self,
596        object: Gd<T>,
597    ) -> Result<DynGd<T, D>, Gd<T>> {
598        let dynamic_class = object.dynamic_class_string();
599
600        if dynamic_class != self.class_name.to_string_name() {
601            return Err(object);
602        }
603
604        let object = object.upcast_object();
605
606        // SAFETY: `DynTraitImpl::new` ensures that this function is safe to call when `object` is castable to `self.class_name`.
607        // Since the dynamic class of `object` is `self.class_name`, it must be castable to `self.class_name`.
608        let erased_dyn = unsafe { (self.erased_dynify_fn)(object) };
609
610        let dyn_gd_object = erased_dyn.boxed.downcast::<DynGd<classes::Object, D>>();
611
612        // SAFETY: `callbacks::dynify_fn` returns a `DynGd<Object, D>` which has been type erased. So this downcast will always succeed.
613        let dyn_gd_object = unsafe { dyn_gd_object.unwrap_unchecked() };
614
615        // SAFETY: This is effectively upcasting a value which has class equal to `self.class_name` to a `DynGd<T, D>`. Since the class of
616        // `object` is `T` and its dynamic class is `self.class_name`, this means that `T` must be a superclass of `self.class_name`. Thus
617        // this upcast is safe.
618        let dyn_gd_t = unsafe { dyn_gd_object.cast_unchecked::<T>() };
619
620        Ok(dyn_gd_t)
621    }
622}