Skip to content

Commit 46003b5

Browse files
committed
Codegen for Array<T> - required to properly initialize and cache Array runtime type.
----- Methods such as `duplicate_...` were initializing and caching typed `VariantArray` as a return value for outgoing calls. We fix it by initializing proper `Array<T>` in the first place.
1 parent c3f705b commit 46003b5

File tree

9 files changed

+150
-45
lines changed

9 files changed

+150
-45
lines changed

godot-codegen/src/generator/builtins.rs

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -197,14 +197,27 @@ fn make_special_builtin_methods(class_name: &TyName, _ctx: &Context) -> TokenStr
197197

198198
/// Get the safety docs of an unsafe method, or `None` if it is safe.
199199
fn method_safety_doc(class_name: &TyName, method: &BuiltinMethod) -> Option<TokenStream> {
200-
if class_name.godot_ty == "Array"
201-
&& &method.return_value().type_tokens().to_string() == "VariantArray"
202-
{
203-
return Some(quote! {
204-
/// # Safety
205-
///
206-
/// You must ensure that the returned array fulfils the safety invariants of [`Array`](crate::builtin::Array).
207-
});
200+
if class_name.godot_ty == "Array" {
201+
if method.is_generic() {
202+
return Some(quote! {
203+
/// # Safety
204+
/// You must ensure that the returned array fulfils the safety invariants of [`Array`](crate::builtin::Array), this being:
205+
/// - Any values written to the array must match the runtime type of the array.
206+
/// - Any values read from the array must be convertible to the type `T`.
207+
///
208+
/// If the safety invariant of `Array` is intact, which it must be for any publicly accessible arrays, then `T` must match
209+
/// the runtime type of the array. This then implies that both of the conditions above hold. This means that you only need
210+
/// to keep the above conditions in mind if you are intentionally violating the safety invariant of `Array`.
211+
///
212+
/// In the current implementation, both cases will produce a panic rather than undefined behavior, but this should not be relied upon.
213+
});
214+
} else if &method.return_value().type_tokens().to_string() == "VariantArray" {
215+
return Some(quote! {
216+
/// # Safety
217+
///
218+
/// You must ensure that the returned array fulfils the safety invariants of [`Array`](crate::builtin::Array).
219+
});
220+
}
208221
}
209222

210223
None
@@ -252,11 +265,13 @@ fn make_builtin_method_definition(
252265
let receiver = functions_common::make_receiver(method.qualifier(), ffi_arg_in);
253266
let object_ptr = &receiver.ffi_arg;
254267

268+
let maybe_generic_params = method.return_value().generic_params();
269+
255270
let ptrcall_invocation = quote! {
256271
let method_bind = sys::builtin_method_table().#fptr_access;
257272

258273

259-
Signature::<CallParams, CallRet>::out_builtin_ptrcall(
274+
Signature::<CallParams, CallRet #maybe_generic_params>::out_builtin_ptrcall(
260275
method_bind,
261276
#builtin_name_str,
262277
#method_name_str,

godot-codegen/src/generator/default_parameters.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use quote::{format_ident, quote};
1111

1212
use crate::generator::functions_common;
1313
use crate::generator::functions_common::{
14-
make_arg_expr, make_param_or_field_type, FnArgExpr, FnCode, FnKind, FnParamDecl, FnParamTokens,
14+
make_arg_expr, make_param_or_field_type, FnArgExpr, FnCode, FnKind, FnParamDecl,
1515
};
1616
use crate::models::domain::{FnParam, FnQualifier, Function, RustTy, TyName};
1717
use crate::util::{ident, safe_ident};

godot-codegen/src/generator/functions_common.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,6 @@ pub fn make_function_definition(
142142
callsig_param_types: param_types,
143143
callsig_lifetime_args,
144144
arg_exprs: arg_names,
145-
func_general_lifetime: fn_lifetime,
146145
} = if sig.is_virtual() {
147146
make_params_exprs_virtual(sig.params().iter(), sig)
148147
} else {
@@ -175,11 +174,14 @@ pub fn make_function_definition(
175174
default_structs_code = TokenStream::new();
176175
};
177176

177+
let maybe_func_generic_params = sig.return_value().generic_params();
178+
let maybe_func_generic_bounds = sig.return_value().where_clause();
179+
178180
let call_sig_decl = {
179181
let return_ty = &sig.return_value().type_tokens();
180182

181183
quote! {
182-
type CallRet = #return_ty;
184+
type CallRet #maybe_func_generic_params = #return_ty;
183185
type CallParams #callsig_lifetime_args = (#(#param_types,)*);
184186
}
185187
};
@@ -279,10 +281,12 @@ pub fn make_function_definition(
279281

280282
quote! {
281283
#maybe_safety_doc
282-
#vis #maybe_unsafe fn #primary_fn_name #fn_lifetime (
284+
#vis #maybe_unsafe fn #primary_fn_name #maybe_func_generic_params (
283285
#receiver_param
284286
#( #params, )*
285-
) #return_decl {
287+
) #return_decl
288+
#maybe_func_generic_bounds
289+
{
286290
#call_sig_decl
287291

288292
let args = (#( #arg_names, )*);
@@ -489,6 +493,7 @@ pub(crate) fn make_param_or_field_type(
489493
..
490494
}
491495
| RustTy::BuiltinArray { .. }
496+
| RustTy::GenericArray
492497
| RustTy::EngineArray { .. } => {
493498
let lft = lifetimes.next();
494499
special_ty = Some(quote! { RefArg<#lft, #ty> });

godot-codegen/src/models/domain.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,28 +300,40 @@ pub trait Function: fmt::Display {
300300
fn name(&self) -> &str {
301301
&self.common().name
302302
}
303+
303304
/// Rust name as `Ident`. Might be cached in future.
304305
fn name_ident(&self) -> Ident {
305306
safe_ident(self.name())
306307
}
308+
307309
fn godot_name(&self) -> &str {
308310
&self.common().godot_name
309311
}
312+
310313
fn params(&self) -> &[FnParam] {
311314
&self.common().parameters
312315
}
316+
313317
fn return_value(&self) -> &FnReturn {
314318
&self.common().return_value
315319
}
320+
316321
fn is_vararg(&self) -> bool {
317322
self.common().is_vararg
318323
}
324+
319325
fn is_private(&self) -> bool {
320326
self.common().is_private
321327
}
328+
322329
fn is_virtual(&self) -> bool {
323330
matches!(self.direction(), FnDirection::Virtual { .. })
324331
}
332+
333+
fn is_generic(&self) -> bool {
334+
matches!(self.return_value().type_, Some(RustTy::GenericArray))
335+
}
336+
325337
fn direction(&self) -> FnDirection {
326338
self.common().direction
327339
}
@@ -621,6 +633,13 @@ impl FnReturn {
621633
Self::with_enum_replacements(return_value, &[], ctx)
622634
}
623635

636+
pub fn with_generic_builtin(generic_type: RustTy) -> Self {
637+
Self {
638+
decl: generic_type.return_decl(),
639+
type_: Some(generic_type),
640+
}
641+
}
642+
624643
pub fn with_enum_replacements(
625644
return_value: &Option<JsonMethodReturn>,
626645
replacements: EnumReplacements,
@@ -669,6 +688,14 @@ impl FnReturn {
669688
}
670689
}
671690

691+
pub fn generic_params(&self) -> Option<TokenStream> {
692+
self.type_.as_ref()?.generic_params()
693+
}
694+
695+
pub fn where_clause(&self) -> Option<TokenStream> {
696+
self.type_.as_ref()?.where_clause()
697+
}
698+
672699
pub fn call_result_decl(&self) -> TokenStream {
673700
let ret = self.type_tokens();
674701
quote! { -> Result<#ret, crate::meta::error::CallError> }
@@ -705,6 +732,11 @@ pub enum RustTy {
705732
/// Note that untyped arrays are mapped as `BuiltinIdent("Array")`.
706733
BuiltinArray { elem_type: TokenStream },
707734

735+
/// Will be included as `Array<T>` in the generated source.
736+
///
737+
/// Set by [`builtin_method_generic_ret`](crate::special_cases::builtin_method_generic_ret)
738+
GenericArray,
739+
708740
/// C-style raw pointer to a `RustTy`.
709741
RawPointer { inner: Box<RustTy>, is_const: bool },
710742

@@ -755,10 +787,30 @@ impl RustTy {
755787
pub fn return_decl(&self) -> TokenStream {
756788
match self {
757789
Self::EngineClass { tokens, .. } => quote! { -> Option<#tokens> },
790+
Self::GenericArray => quote! { -> Array<Ret> },
758791
other => quote! { -> #other },
759792
}
760793
}
761794

795+
pub fn generic_params(&self) -> Option<TokenStream> {
796+
if matches!(self, Self::GenericArray) {
797+
Some(quote! { < Ret > })
798+
} else {
799+
None
800+
}
801+
}
802+
803+
pub fn where_clause(&self) -> Option<TokenStream> {
804+
if matches!(self, Self::GenericArray) {
805+
Some(quote! {
806+
where
807+
Ret: crate::meta::ArrayElement,
808+
})
809+
} else {
810+
None
811+
}
812+
}
813+
762814
pub fn is_integer(&self) -> bool {
763815
let RustTy::BuiltinIdent { ty, .. } = self else {
764816
return false;
@@ -789,6 +841,7 @@ impl ToTokens for RustTy {
789841
RustTy::EngineEnum { tokens: path, .. } => path.to_tokens(tokens),
790842
RustTy::EngineClass { tokens: path, .. } => path.to_tokens(tokens),
791843
RustTy::ExtenderReceiver { tokens: path } => path.to_tokens(tokens),
844+
RustTy::GenericArray => quote! { Array<Ret> }.to_tokens(tokens),
792845
}
793846
}
794847
}

godot-codegen/src/models/domain_mapping.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -366,10 +366,17 @@ impl BuiltinMethod {
366366
return None;
367367
}
368368

369-
let return_value = method
370-
.return_type
371-
.as_deref()
372-
.map(JsonMethodReturn::from_type_no_meta);
369+
let return_value = if let Some(generic) =
370+
special_cases::builtin_method_generic_ret(builtin_name, method)
371+
{
372+
generic
373+
} else {
374+
let return_value = &method
375+
.return_type
376+
.as_deref()
377+
.map(JsonMethodReturn::from_type_no_meta);
378+
FnReturn::new(return_value, ctx)
379+
};
373380

374381
Some(Self {
375382
common: FunctionCommon {
@@ -381,7 +388,7 @@ impl BuiltinMethod {
381388
parameters: FnParam::builder()
382389
.no_defaults()
383390
.build_many(&method.arguments, ctx),
384-
return_value: FnReturn::new(&return_value, ctx),
391+
return_value,
385392
is_vararg: method.is_vararg,
386393
is_private: false, // See 'exposed' below. Could be special_cases::is_method_private(builtin_name, &method.name),
387394
is_virtual_required: false,

godot-codegen/src/special_cases/codegen_special_cases.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ fn is_type_excluded(ty: &str, ctx: &mut Context) -> bool {
4848
match ty {
4949
RustTy::BuiltinIdent { .. } => false,
5050
RustTy::BuiltinArray { .. } => false,
51+
RustTy::GenericArray => false,
5152
RustTy::RawPointer { inner, .. } => is_rust_type_excluded(inner),
5253
RustTy::EngineArray { elem_class, .. } => is_class_excluded(elem_class.as_str()),
5354
RustTy::EngineEnum {

godot-codegen/src/special_cases/special_cases.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ use proc_macro2::Ident;
3333

3434
use crate::conv::to_enum_type_uncached;
3535
use crate::models::domain::{
36-
ClassCodegenLevel, Enum, EnumReplacements, RustTy, TyName, VirtualMethodPresence,
36+
ClassCodegenLevel, Enum, EnumReplacements, FnReturn, RustTy, TyName, VirtualMethodPresence,
3737
};
3838
use crate::models::json::{JsonBuiltinMethod, JsonClassMethod, JsonSignal, JsonUtilityFunction};
3939
use crate::special_cases::codegen_special_cases;
@@ -778,6 +778,24 @@ pub fn is_builtin_method_deleted(_class_name: &TyName, method: &JsonBuiltinMetho
778778
codegen_special_cases::is_builtin_method_excluded(method)
779779
}
780780

781+
/// Returns some generic type – such as `GenericArray` representing `Array<T>` – if method is marked as generic, `None` otherwise.
782+
///
783+
/// Usually required to initialize the return value and cache its type (see also https://github.com/godot-rust/gdext/pull/1357).
784+
pub fn builtin_method_generic_ret(
785+
class_name: &TyName,
786+
method: &JsonBuiltinMethod,
787+
) -> Option<FnReturn> {
788+
match (
789+
class_name.rust_ty.to_string().as_str(),
790+
method.name.as_str(),
791+
) {
792+
("Array", "duplicate") | ("Array", "slice") => {
793+
Some(FnReturn::with_generic_builtin(RustTy::GenericArray))
794+
}
795+
_ => None,
796+
}
797+
}
798+
781799
/// True if signal is absent from codegen (only when surrounding class is excluded).
782800
pub fn is_signal_deleted(_class_name: &TyName, signal: &JsonSignal) -> bool {
783801
// If any argument type (a class) is excluded.

godot-core/src/builtin/collections/array.rs

Lines changed: 12 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -533,12 +533,11 @@ impl<T: ArrayElement> Array<T> {
533533
/// To create a deep copy, use [`duplicate_deep()`][Self::duplicate_deep] instead.
534534
/// To create a new reference to the same array data, use [`clone()`][Clone::clone].
535535
pub fn duplicate_shallow(&self) -> Self {
536-
// SAFETY: We never write to the duplicated array, and all values read are read as `Variant`.
537-
let duplicate: VariantArray = unsafe { self.as_inner().duplicate(false) };
536+
// SAFETY: duplicate() returns a typed array with the same type as Self, and all values are taken from `self` so have the right type
537+
let duplicate: Self = unsafe { self.as_inner().duplicate(false) };
538538

539-
// SAFETY: duplicate() returns a typed array with the same type as Self, and all values are taken from `self` so have the right type.
540-
let result = unsafe { duplicate.assume_type() };
541-
result.with_cache(self)
539+
// Note: cache is being set while initializing the duplicate as a return value for above call.
540+
duplicate
542541
}
543542

544543
/// Returns a deep copy of the array. All nested arrays and dictionaries are duplicated and
@@ -548,12 +547,11 @@ impl<T: ArrayElement> Array<T> {
548547
/// To create a shallow copy, use [`duplicate_shallow()`][Self::duplicate_shallow] instead.
549548
/// To create a new reference to the same array data, use [`clone()`][Clone::clone].
550549
pub fn duplicate_deep(&self) -> Self {
551-
// SAFETY: We never write to the duplicated array, and all values read are read as `Variant`.
552-
let duplicate: VariantArray = unsafe { self.as_inner().duplicate(true) };
550+
// SAFETY: duplicate() returns a typed array with the same type as Self, and all values are taken from `self` so have the right type
551+
let duplicate: Self = unsafe { self.as_inner().duplicate(true) };
553552

554-
// SAFETY: duplicate() returns a typed array with the same type as Self, and all values are taken from `self` so have the right type.
555-
let result = unsafe { duplicate.assume_type() };
556-
result.with_cache(self)
553+
// Note: cache is being set while initializing the duplicate as a return value for above call.
554+
duplicate
557555
}
558556

559557
/// Returns a sub-range `begin..end` as a new `Array`.
@@ -588,13 +586,10 @@ impl<T: ArrayElement> Array<T> {
588586
let (begin, end) = range.signed();
589587
let end = end.unwrap_or(i32::MAX as i64);
590588

591-
// SAFETY: The type of the array is `T` and we convert the returned array to an `Array<T>` immediately.
592-
let subarray: VariantArray =
593-
unsafe { self.as_inner().slice(begin, end, step as i64, deep) };
589+
// SAFETY: slice() returns a typed array with the same type as Self, and all values are taken from `self` so have the right type.
590+
let subarray: Self = unsafe { self.as_inner().slice(begin, end, step as i64, deep) };
594591

595-
// SAFETY: slice() returns a typed array with the same type as Self.
596-
let result = unsafe { subarray.assume_type() };
597-
result.with_cache(self)
592+
subarray
598593
}
599594

600595
/// Returns an iterator over the elements of the `Array`. Note that this takes the array
@@ -950,8 +945,7 @@ impl<T: ArrayElement> Array<T> {
950945
}
951946
}
952947

953-
/// Changes the generic type on this array, without changing its contents. Needed for API
954-
/// functions that return a variant array even though we know its type, and for API functions
948+
/// Changes the generic type on this array, without changing its contents. Needed for API functions
955949
/// that take a variant array even though we want to pass a typed one.
956950
///
957951
/// # Safety
@@ -966,13 +960,6 @@ impl<T: ArrayElement> Array<T> {
966960
/// Note also that any `GodotType` can be written to a `Variant` array.
967961
///
968962
/// In the current implementation, both cases will produce a panic rather than undefined behavior, but this should not be relied upon.
969-
unsafe fn assume_type<U: ArrayElement>(self) -> Array<U> {
970-
// The memory layout of `Array<T>` does not depend on `T`.
971-
std::mem::transmute::<Array<T>, Array<U>>(self)
972-
}
973-
974-
/// # Safety
975-
/// See [`assume_type`](Self::assume_type).
976963
unsafe fn assume_type_ref<U: ArrayElement>(&self) -> &Array<U> {
977964
// The memory layout of `Array<T>` does not depend on `T`.
978965
std::mem::transmute::<&Array<T>, &Array<U>>(self)

0 commit comments

Comments
 (0)