diff --git a/godot-codegen/src/context.rs b/godot-codegen/src/context.rs index 1fc3f9b50..c95a7b577 100644 --- a/godot-codegen/src/context.rs +++ b/godot-codegen/src/context.rs @@ -246,17 +246,6 @@ impl<'a> Context<'a> { self.singletons.contains(class_name) } - pub fn is_exportable(&self, class_name: &TyName) -> bool { - if class_name.godot_ty == "Resource" || class_name.godot_ty == "Node" { - return true; - } - - self.inheritance_tree - .collect_all_bases(class_name) - .iter() - .any(|ty| ty.godot_ty == "Resource" || ty.godot_ty == "Node") - } - pub fn inheritance_tree(&self) -> &InheritanceTree { &self.inheritance_tree } diff --git a/godot-codegen/src/generator/builtins.rs b/godot-codegen/src/generator/builtins.rs index 96f25a496..9fc67c6a6 100644 --- a/godot-codegen/src/generator/builtins.rs +++ b/godot-codegen/src/generator/builtins.rs @@ -163,6 +163,21 @@ fn make_special_builtin_methods(class_name: &TyName, _ctx: &Context) -> TokenStr } } +/// Get the safety docs of an unsafe method, or `None` if it is safe. +fn method_safety_doc(class_name: &TyName, method: &BuiltinMethod) -> Option { + if class_name.godot_ty == "Array" + && &method.return_value().type_tokens().to_string() == "VariantArray" + { + return Some(quote! { + /// # Safety + /// + /// You must ensure that the returned array fulfils the safety invariants of [`Array`](crate::builtin::Array). + }); + } + + None +} + fn make_builtin_method_definition( builtin_class: &BuiltinClass, method: &BuiltinMethod, @@ -220,6 +235,8 @@ fn make_builtin_method_definition( )*/ }; + let safety_doc = method_safety_doc(builtin_class.name(), method); + functions_common::make_function_definition( method, &FnCode { @@ -227,5 +244,6 @@ fn make_builtin_method_definition( varcall_invocation, ptrcall_invocation, }, + safety_doc, ) } diff --git a/godot-codegen/src/generator/classes.rs b/godot-codegen/src/generator/classes.rs index 9cb5d2b17..7cfbecc99 100644 --- a/godot-codegen/src/generator/classes.rs +++ b/godot-codegen/src/generator/classes.rs @@ -86,7 +86,7 @@ fn make_class(class: &Class, ctx: &mut Context, view: &ApiView) -> GeneratedClas let base = ident(&conv::to_pascal_case(base)); (quote! { crate::engine::#base }, Some(base)) } - None => (quote! { () }, None), + None => (quote! { crate::obj::NoBase }, None), }; let (constructor, godot_default_impl) = make_constructor_and_default(class, ctx); @@ -100,8 +100,7 @@ fn make_class(class: &Class, ctx: &mut Context, view: &ApiView) -> GeneratedClas let enums = enums::make_enums(&class.enums); let constants = constants::make_constants(&class.constants); - let inherits_macro = format_ident!("inherits_transitive_{}", class_name.rust_ty); - let (exportable_impl, exportable_macro_impl) = make_exportable_impl(class_name, ctx); + let inherits_macro = format_ident!("unsafe_inherits_transitive_{}", class_name.rust_ty); let deref_impl = make_deref_impl(class_name, &base_ty); let all_bases = ctx.inheritance_tree().collect_all_bases(class_name); @@ -140,8 +139,18 @@ fn make_class(class: &Class, ctx: &mut Context, view: &ApiView) -> GeneratedClas let instance_id = rtti.check_type::(); Some(instance_id) } + + #[doc(hidden)] + pub fn __object_ptr(&self) -> sys::GDExtensionObjectPtr { + self.object_ptr + } }; + let inherits_macro_safety_doc = format!( + "The provided class must be a subclass of all the superclasses of [`{}`]", + class_name.rust_ty + ); + // mod re_export needed, because class should not appear inside the file module, and we can't re-export private struct as pub. let imports = util::make_imports(); let tokens = quote! { @@ -187,31 +196,26 @@ fn make_class(class: &Class, ctx: &mut Context, view: &ApiView) -> GeneratedClas type DynMemory = crate::obj::bounds::#assoc_dyn_memory; type Declarer = crate::obj::bounds::DeclEngine; } - impl crate::obj::EngineClass for #class_name { - fn as_object_ptr(&self) -> sys::GDExtensionObjectPtr { - self.object_ptr - } - fn as_type_ptr(&self) -> sys::GDExtensionTypePtr { - std::ptr::addr_of!(self.object_ptr) as sys::GDExtensionTypePtr - } - } + #( - impl crate::obj::Inherits for #class_name {} + // SAFETY: #all_bases is a list of classes provided by Godot such that #class_name is guaranteed a subclass of all of them. + unsafe impl crate::obj::Inherits for #class_name {} )* - #exportable_impl #godot_default_impl #deref_impl + /// # Safety + /// + #[doc = #inherits_macro_safety_doc] #[macro_export] #[allow(non_snake_case)] macro_rules! #inherits_macro { ($Class:ident) => { - impl ::godot::obj::Inherits<::godot::engine::#class_name> for $Class {} + unsafe impl ::godot::obj::Inherits<::godot::engine::#class_name> for $Class {} #( - impl ::godot::obj::Inherits<::godot::engine::#all_bases> for $Class {} + unsafe impl ::godot::obj::Inherits<::godot::engine::#all_bases> for $Class {} )* - #exportable_macro_impl } } } @@ -342,26 +346,8 @@ fn make_constructor_and_default(class: &Class, ctx: &Context) -> (TokenStream, T (constructor, godot_default_impl) } -fn make_exportable_impl(class_name: &TyName, ctx: &mut Context) -> (TokenStream, TokenStream) { - let (exportable_impl, exportable_macro_impl); - - if ctx.is_exportable(class_name) { - exportable_impl = quote! { - impl crate::obj::ExportableObject for #class_name {} - }; - exportable_macro_impl = quote! { - impl ::godot::obj::ExportableObject for $Class {} - }; - } else { - exportable_impl = TokenStream::new(); - exportable_macro_impl = TokenStream::new(); - }; - - (exportable_impl, exportable_macro_impl) -} - fn make_deref_impl(class_name: &TyName, base_ty: &TokenStream) -> TokenStream { - // The base_ty of `Object` is `()`, and we dont want every engine class to deref to `()`. + // The base_ty of `Object` is `NoBase`, and we dont want every engine class to deref to `NoBase`. if class_name.rust_ty == "Object" { return TokenStream::new(); } @@ -484,5 +470,6 @@ fn make_class_method_definition( varcall_invocation, ptrcall_invocation, }, + None, ) } diff --git a/godot-codegen/src/generator/functions_common.rs b/godot-codegen/src/generator/functions_common.rs index 24c4c5e6d..055c5164a 100644 --- a/godot-codegen/src/generator/functions_common.rs +++ b/godot-codegen/src/generator/functions_common.rs @@ -82,7 +82,11 @@ impl FnDefinitions { } } -pub fn make_function_definition(sig: &dyn Function, code: &FnCode) -> FnDefinition { +pub fn make_function_definition( + sig: &dyn Function, + code: &FnCode, + safety_doc: Option, +) -> FnDefinition { let has_default_params = default_parameters::function_uses_default_params(sig); let vis = if has_default_params { // Public API mapped by separate function. @@ -92,7 +96,9 @@ pub fn make_function_definition(sig: &dyn Function, code: &FnCode) -> FnDefiniti make_vis(sig.is_private()) }; - let (maybe_unsafe, safety_doc) = if function_uses_pointers(sig) { + let (maybe_unsafe, safety_doc) = if let Some(safety_doc) = safety_doc { + (quote! { unsafe }, safety_doc) + } else if function_uses_pointers(sig) { ( quote! { unsafe }, quote! { diff --git a/godot-codegen/src/generator/utility_functions.rs b/godot-codegen/src/generator/utility_functions.rs index 29632d184..b5eff70b0 100644 --- a/godot-codegen/src/generator/utility_functions.rs +++ b/godot-codegen/src/generator/utility_functions.rs @@ -78,6 +78,7 @@ pub(crate) fn make_utility_function_definition(function: &UtilityFunction) -> To varcall_invocation, ptrcall_invocation, }, + None, ); // Utility functions have no builders. diff --git a/godot-codegen/src/generator/virtual_traits.rs b/godot-codegen/src/generator/virtual_traits.rs index ba2b084d6..3db45cc35 100644 --- a/godot-codegen/src/generator/virtual_traits.rs +++ b/godot-codegen/src/generator/virtual_traits.rs @@ -124,6 +124,7 @@ fn make_virtual_method(method: &ClassMethod) -> Option { varcall_invocation: TokenStream::new(), ptrcall_invocation: TokenStream::new(), }, + None, ); // Virtual methods have no builders. diff --git a/godot-core/src/builtin/array.rs b/godot-core/src/builtin/array.rs index 755fa0b79..05291f317 100644 --- a/godot-core/src/builtin/array.rs +++ b/godot-core/src/builtin/array.rs @@ -56,10 +56,24 @@ use super::meta::{ // GodotType`. Whew. This could be fixed by splitting up `GodotFfi` if desired. #[repr(C)] pub struct Array { + // Safety Invariant: The type of all values in `opaque` matches the type `T`. opaque: sys::types::OpaqueArray, _phantom: PhantomData, } +/// Guard that can only call immutable methods on the array. +struct ImmutableInnerArray<'a> { + inner: inner::InnerArray<'a>, +} + +impl<'a> std::ops::Deref for ImmutableInnerArray<'a> { + type Target = inner::InnerArray<'a>; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + /// A Godot `Array` without an assigned type. pub type VariantArray = Array; @@ -115,19 +129,14 @@ impl Array { /// Clears the array, removing all elements. pub fn clear(&mut self) { - self.as_inner().clear(); - } - - /// Resizes the array to contain a different number of elements. If the new size is smaller, - /// elements are removed from the end. If the new size is larger, new elements are set to - /// [`Variant::nil()`]. - pub fn resize(&mut self, size: usize) { - self.as_inner().resize(to_i64(size)); + // SAFETY: No new values are written to the array, we only remove values from the array. + unsafe { self.as_inner_mut() }.clear(); } /// Reverses the order of the elements in the array. pub fn reverse(&mut self) { - self.as_inner().reverse(); + // SAFETY: We do not write any values that dont already exist in the array, so all values have the correct type. + unsafe { self.as_inner_mut() }.reverse(); } /// Sorts the array. @@ -137,7 +146,8 @@ impl Array { /// considered equal may have their order changed when using `sort_unstable`. #[doc(alias = "sort")] pub fn sort_unstable(&mut self) { - self.as_inner().sort(); + // SAFETY: We do not write any values that dont already exist in the array, so all values have the correct type. + unsafe { self.as_inner_mut() }.sort(); } /// Sorts the array. @@ -149,14 +159,52 @@ impl Array { /// `sort_unstable`. #[doc(alias = "sort_custom")] pub fn sort_unstable_custom(&mut self, func: Callable) { - self.as_inner().sort_custom(func); + // SAFETY: We do not write any values that dont already exist in the array, so all values have the correct type. + unsafe { self.as_inner_mut() }.sort_custom(func); } /// Shuffles the array such that the items will have a random order. This method uses the /// global random number generator common to methods such as `randi`. Call `randomize` to /// ensure that a new seed will be used each time if you want non-reproducible shuffling. pub fn shuffle(&mut self) { - self.as_inner().shuffle(); + // SAFETY: We do not write any values that dont already exist in the array, so all values have the correct type. + unsafe { self.as_inner_mut() }.shuffle(); + } + + /// Shrinks the array down to `new_size`. + /// + /// This will only change the size of the array if `new_size` is smaller than the current size. Returns `true` if the array was shrunk. + /// + /// If you want to increase the size of the array, use [`resize_with`](Array::resize_with) instead. + #[doc(alias = "resize")] + pub fn shrink(&mut self, new_size: usize) -> bool { + if new_size >= self.len() { + return false; + } + + // SAFETY: Since `new_size` is less than the current size, we'll only be removing elements from the array. + unsafe { self.as_inner_mut() }.resize(to_i64(new_size)); + + true + } + + /// Resizes the array to contain a different number of elements. + /// + /// If the new size is smaller than the current size, then it removes elements from the end. If the new size is bigger than the current one + /// then the new elements are set to `value`. + /// + /// If you know that the new size is smaller, then consider using [`shrink`](Array::shrink) instead. + pub fn resize(&mut self, new_size: usize, value: &T) { + let original_size = self.len(); + + // SAFETY: While we do insert `Variant::nil()` if the new size is larger, we then fill it with `value` ensuring that all values in the + // array are of type `T` still. + unsafe { self.as_inner_mut() }.resize(to_i64(new_size)); + + // If new_size < original_size then this is an empty iterator and does nothing. + for i in original_size..new_size { + self.set(i, value.to_godot()); + } } /// Asserts that the given index refers to an existing element. @@ -226,26 +274,41 @@ impl Array { Variant::ptr_from_sys_mut(variant_ptr) } + /// # Safety + /// + /// This has the same safety issues as doing `self.assume_type::()` and so the relevant safety invariants from + /// [`assume_type`](Self::assume_type) must be upheld. + /// + /// In particular this means that all reads are fine, since all values can be converted to `Variant`. However writes are only ok + /// if they match the type `T`. #[doc(hidden)] - pub fn as_inner(&self) -> inner::InnerArray { - // SAFETY: The memory layout of `Array` does not depend on `T`. + pub unsafe fn as_inner_mut(&self) -> inner::InnerArray { + // The memory layout of `Array` does not depend on `T`. inner::InnerArray::from_outer_typed(self) } + fn as_inner(&self) -> ImmutableInnerArray { + ImmutableInnerArray { + // SAFETY: We can only read from the array. + inner: unsafe { self.as_inner_mut() }, + } + } + /// Changes the generic type on this array, without changing its contents. Needed for API /// functions that return a variant array even though we know its type, and for API functions /// that take a variant array even though we want to pass a typed one. /// - /// This is marked `unsafe` since it can be used to break the invariant that a `Array` - /// always holds a Godot array whose runtime type is `T`. - /// /// # Safety /// - /// In and of itself, calling this does not result in undefined behavior. However: - /// - If `T` is not `Variant`, the returned array should not be written to, because the runtime - /// type check may fail. - /// - If `U` is not `Variant`, the returned array should not be read from, because conversion - /// from variants may fail. + /// - Any values written to the array must match the runtime type of the array. + /// - Any values read from the array must be convertible to the type `U`. + /// + /// If the safety invariant of `Array` is intact, which it must be for any publicly accessible arrays, then `U` must match + /// the runtime type of the array. This then implies that both of the conditions above hold. This means that you only need + /// to keep the above conditions in mind if you are intentionally violating the safety invariant of `Array`. + /// + /// Note also that any `GodotType` can be written to a `Variant` array. + /// /// In the current implementation, both cases will produce a panic rather than undefined /// behavior, but this should not be relied upon. unsafe fn assume_type(self) -> Array { @@ -266,8 +329,10 @@ impl Array { /// To create a deep copy, use [`duplicate_deep()`][Self::duplicate_deep] instead. /// To create a new reference to the same array data, use [`clone()`][Clone::clone]. pub fn duplicate_shallow(&self) -> Self { - let duplicate: VariantArray = self.as_inner().duplicate(false); - // SAFETY: duplicate() returns a typed array with the same type as Self + // SAFETY: We never write to the duplicated array, and all values read are read as `Variant`. + let duplicate: VariantArray = unsafe { self.as_inner().duplicate(false) }; + + // SAFETY: duplicate() returns a typed array with the same type as Self, and all values are taken from `self` so have the right type. unsafe { duplicate.assume_type() } } @@ -278,8 +343,10 @@ impl Array { /// To create a shallow copy, use [`duplicate_shallow()`][Self::duplicate_shallow] instead. /// To create a new reference to the same array data, use [`clone()`][Clone::clone]. pub fn duplicate_deep(&self) -> Self { - let duplicate: VariantArray = self.as_inner().duplicate(true); - // SAFETY: duplicate() returns a typed array with the same type as Self + // SAFETY: We never write to the duplicated array, and all values read are read as `Variant`. + let duplicate: VariantArray = unsafe { self.as_inner().duplicate(true) }; + + // SAFETY: duplicate() returns a typed array with the same type as Self, and all values are taken from `self` so have the right type. unsafe { duplicate.assume_type() } } @@ -323,9 +390,11 @@ impl Array { let end = end.min(len); let step = step.unwrap_or(1); - let subarray: VariantArray = + // SAFETY: The type of the array is `T` and we convert the returned array to an `Array` immediately. + let subarray: VariantArray = unsafe { self.as_inner() - .slice(to_i64(begin), to_i64(end), step.try_into().unwrap(), deep); + .slice(to_i64(begin), to_i64(end), step.try_into().unwrap(), deep) + }; // SAFETY: slice() returns a typed array with the same type as Self unsafe { subarray.assume_type() } @@ -333,10 +402,13 @@ impl Array { /// Appends another array at the end of this array. Equivalent of `append_array` in GDScript. pub fn extend_array(&mut self, other: Array) { - // SAFETY: Read-only arrays are covariant: conversion to a variant array is fine as long as - // we don't insert values into it afterwards, and `append_array()` doesn't do that. + // SAFETY: `append_array` will only read values from `other`, and all types can be converted to `Variant`. let other: VariantArray = unsafe { other.assume_type::() }; - self.as_inner().append_array(other); + + // SAFETY: `append_array` will only write values gotten from `other` into `self`, and all values in `other` are guaranteed + // to be of type `T`. + let mut inner_self = unsafe { self.as_inner_mut() }; + inner_self.append_array(other); } /// Returns the runtime type info of this array. @@ -368,14 +440,20 @@ impl Array { } } - /// Sets the type of the inner array. Can only be called once, directly after creation. - fn init_inner_type(&mut self) { + /// Sets the type of the inner array. + /// + /// # Safety + /// + /// Must only be called once, directly after creation. + unsafe fn init_inner_type(&mut self) { debug_assert!(self.is_empty()); debug_assert!(!self.type_info().is_typed()); let type_info = TypeInfo::of::(); if type_info.is_typed() { let script = Variant::nil(); + + // SAFETY: The array is a newly created empty untyped array. unsafe { interface_fn!(array_set_typed)( self.sys(), @@ -473,7 +551,8 @@ impl Array { /// Equivalent of `pop_back` in GDScript. pub fn pop(&mut self) -> Option { (!self.is_empty()).then(|| { - let variant = self.as_inner().pop_back(); + // SAFETY: We do not write any values to the array, we just remove one. + let variant = unsafe { self.as_inner_mut() }.pop_back(); T::from_variant(&variant) }) } @@ -484,7 +563,8 @@ impl Array { /// array's elements. The larger the array, the slower `pop_front` will be. pub fn pop_front(&mut self) -> Option { (!self.is_empty()).then(|| { - let variant = self.as_inner().pop_front(); + // SAFETY: We do not write any values to the array, we just remove one. + let variant = unsafe { self.as_inner_mut() }.pop_front(); T::from_variant(&variant) }) } @@ -499,7 +579,9 @@ impl Array { /// If `index` is out of bounds. pub fn remove(&mut self, index: usize) -> T { self.check_bounds(index); - let variant = self.as_inner().pop_at(to_i64(index)); + + // SAFETY: We do not write any values to the array, we just remove one. + let variant = unsafe { self.as_inner_mut() }.pop_at(to_i64(index)); T::from_variant(&variant) } } @@ -578,6 +660,7 @@ impl Array { /// If `index` is out of bounds. pub fn set(&mut self, index: usize, value: T) { let ptr_mut = self.ptr_mut(index); + // SAFETY: `ptr_mut` just checked that the index is not out of bounds. unsafe { *ptr_mut = value.to_variant(); @@ -587,7 +670,8 @@ impl Array { /// Appends an element to the end of the array. Equivalent of `append` and `push_back` in /// GDScript. pub fn push(&mut self, value: T) { - self.as_inner().push_back(value.to_variant()); + // SAFETY: The array has type `T` and we're writing a value of type `T` to it. + unsafe { self.as_inner_mut() }.push_back(value.to_variant()); } /// Adds an element at the beginning of the array. See also `push`. @@ -595,7 +679,8 @@ impl Array { /// Note: On large arrays, this method is much slower than `push` as it will move all the /// array's elements. The larger the array, the slower `push_front` will be. pub fn push_front(&mut self, value: T) { - self.as_inner().push_front(value.to_variant()); + // SAFETY: The array has type `T` and we're writing a value of type `T` to it. + unsafe { self.as_inner_mut() }.push_front(value.to_variant()); } /// Inserts a new element at a given index in the array. The index must be valid, or at the end @@ -610,7 +695,9 @@ impl Array { index <= len, "Array insertion index {index} is out of bounds: length is {len}", ); - self.as_inner().insert(to_i64(index), value.to_variant()); + + // SAFETY: The array has type `T` and we're writing a value of type `T` to it. + unsafe { self.as_inner_mut() }.insert(to_i64(index), value.to_variant()); } /// Removes the first occurrence of a value from the array. If the value does not exist in the @@ -619,13 +706,15 @@ impl Array { /// On large arrays, this method is much slower than `pop_back` as it will move all the array's /// elements after the removed element. The larger the array, the slower `remove` will be. pub fn erase(&mut self, value: &T) { - self.as_inner().erase(value.to_variant()); + // SAFETY: We don't write anything to the array. + unsafe { self.as_inner_mut() }.erase(value.to_variant()); } /// Assigns the given value to all elements in the array. This can be used together with /// `resize` to create an array with a given size and initialized elements. pub fn fill(&mut self, value: &T) { - self.as_inner().fill(value.to_variant()); + // SAFETY: The array has type `T` and we're writing values of type `T` to it. + unsafe { self.as_inner_mut() }.fill(value.to_variant()); } } @@ -807,7 +896,9 @@ impl Default for Array { ctor(self_ptr, std::ptr::null_mut()) }) }; - array.init_inner_type(); + + // SAFETY: We just created this array, and haven't called `init_inner_type` before. + unsafe { array.init_inner_type() }; array } } @@ -886,12 +977,15 @@ impl From<&[T; N]> for Array { /// Creates a `Array` from the given slice. impl From<&[T]> for Array { fn from(slice: &[T]) -> Self { - let mut array = Self::new(); + let array = Self::new(); let len = slice.len(); if len == 0 { return array; } - array.resize(len); + + // SAFETY: We fill the array with `Variant::nil()`, however since we're resizing to the size of the slice we'll end up rewriting all + // the nulls with values of type `T`. + unsafe { array.as_inner_mut() }.resize(to_i64(len)); let ptr = array.ptr_mut_or_null(0); for (i, element) in slice.iter().enumerate() { diff --git a/godot-core/src/obj/traits.rs b/godot-core/src/obj/traits.rs index c004a79dc..f85411e41 100644 --- a/godot-core/src/obj/traits.rs +++ b/godot-core/src/obj/traits.rs @@ -46,7 +46,7 @@ where fn inherits() -> bool { if Self::class_name() == U::class_name() { true - } else if Self::Base::class_name() == <()>::class_name() { + } else if Self::Base::class_name() == ::class_name() { false } else { Self::Base::inherits::() @@ -54,9 +54,15 @@ where } } -/// Unit impl only exists to represent "no base", and is used for exactly one class: `Object`. -impl GodotClass for () { - type Base = (); +/// Type representing the absence of a base class, at the root of the hierarchy. +/// +/// `NoBase` is used as the base class for exactly one class: [`Object`][crate::engine::Object]. +/// +/// This is an enum without any variants, as we should never construct an instance of this class. +pub enum NoBase {} + +impl GodotClass for NoBase { + type Base = NoBase; fn class_name() -> ClassName { ClassName::none() @@ -65,8 +71,7 @@ impl GodotClass for () { const INIT_LEVEL: InitLevel = InitLevel::Core; // arbitrary; never read. } -/// Unit impl only exists to represent "no base", and is used for exactly one class: `Object`. -unsafe impl Bounds for () { +unsafe impl Bounds for NoBase { type Memory = bounds::MemManual; type DynMemory = bounds::MemManual; type Declarer = bounds::DeclEngine; @@ -116,14 +121,15 @@ unsafe impl Bounds for () { /// print_node(Node3D::new_alloc().upcast()); /// ``` /// -pub trait Inherits: GodotClass {} - -impl Inherits for T {} - -/// Trait implemented for all objects that inherit from `Resource` or `Node`. +/// # Safety /// -/// Those are the only objects you can export to the editor. -pub trait ExportableObject: GodotClass {} +/// This trait must only be implemented for subclasses of `Base`. +/// +/// Importantly, this means it is always safe to upcast a value of type `Gd` to `Gd`. +pub unsafe trait Inherits: GodotClass {} + +// SAFETY: Every class is a subclass of itself. +unsafe impl Inherits for T {} /// Implemented for all user-defined classes, providing extensions on the raw object to interact with `Gd`. pub trait UserClass: Bounds { @@ -139,12 +145,6 @@ pub trait UserClass: Bounds { } } -/// Auto-implemented for all engine-provided classes. -pub trait EngineClass: GodotClass { - fn as_object_ptr(&self) -> sys::GDExtensionObjectPtr; - fn as_type_ptr(&self) -> sys::GDExtensionTypePtr; -} - /// Auto-implemented for all engine-provided enums. pub trait EngineEnum: Copy { fn try_from_ord(ord: i32) -> Option; diff --git a/godot-macros/src/class/derive_godot_class.rs b/godot-macros/src/class/derive_godot_class.rs index 5745d5829..0786921ce 100644 --- a/godot-macros/src/class/derive_godot_class.rs +++ b/godot-macros/src/class/derive_godot_class.rs @@ -37,7 +37,7 @@ pub fn derive_godot_class(decl: Declaration) -> ParseResult { let base_ty = &struct_cfg.base_ty; let base_class = quote! { ::godot::engine::#base_ty }; let base_class_name_obj = util::class_name_obj(&base_class); - let inherits_macro = format_ident!("inherits_transitive_{}", base_ty); + let inherits_macro = format_ident!("unsafe_inherits_transitive_{}", base_ty); let prv = quote! { ::godot::private }; let godot_exports_impl = make_property_impl(class_name, &fields); diff --git a/itest/rust/src/builtin_tests/containers/array_test.rs b/itest/rust/src/builtin_tests/containers/array_test.rs index 94c2d0760..80467ac3c 100644 --- a/itest/rust/src/builtin_tests/containers/array_test.rs +++ b/itest/rust/src/builtin_tests/containers/array_test.rs @@ -497,6 +497,53 @@ fn array_binary_search_custom() { assert_eq!(a.bsearch_custom(&3, func), 2); } +#[itest] +fn array_shrink() { + let mut a = array![1, 5, 4, 3, 8]; + + assert!(!a.shrink(10)); + assert_eq!(a.len(), 5); + + assert!(a.shrink(3)); + assert_eq!(a.len(), 3); + assert_eq!(a, array![1, 5, 4]); +} + +#[itest] +fn array_resize() { + let mut a = array![ + GString::from("hello"), + GString::from("bar"), + GString::from("mixed"), + GString::from("baz"), + GString::from("meow") + ]; + + let new = GString::from("new!"); + + a.resize(10, &new); + assert_eq!(a.len(), 10); + assert_eq!( + a, + array![ + GString::from("hello"), + GString::from("bar"), + GString::from("mixed"), + GString::from("baz"), + GString::from("meow"), + new.clone(), + new.clone(), + new.clone(), + new.clone(), + new.clone(), + ] + ); + + a.resize(2, &new); + + assert_eq!(a, array![GString::from("hello"), GString::from("bar"),]); +} + #[derive(GodotClass, Debug)] #[class(init, base=RefCounted)] struct ArrayTest; diff --git a/itest/rust/src/object_tests/object_test.rs b/itest/rust/src/object_tests/object_test.rs index c811763d5..21a119601 100644 --- a/itest/rust/src/object_tests/object_test.rs +++ b/itest/rust/src/object_tests/object_test.rs @@ -527,11 +527,8 @@ fn object_engine_upcast() { } fn ref_instance_id(obj: &Object) -> InstanceId { + let obj_ptr = obj.__object_ptr(); // SAFETY: raw FFI call since we can't access get_instance_id() of a raw Object anymore, and call() needs &mut. - use godot::obj::EngineClass as _; - - let obj_ptr = obj.as_object_ptr(); - let raw_id = unsafe { interface_fn!(object_get_instance_id)(obj_ptr) }; InstanceId::try_from_i64(raw_id as i64).unwrap() }