Skip to content

Commit ceac60a

Browse files
committed
Implement OutArray
1 parent 6b3a65a commit ceac60a

File tree

18 files changed

+964
-257
lines changed

18 files changed

+964
-257
lines changed

godot-codegen/src/conv/type_conversions.rs

Lines changed: 57 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use quote::{quote, ToTokens};
1414

1515
use crate::context::Context;
1616
use crate::conv;
17+
use crate::generator::functions_common::FnParamDecl;
1718
use crate::models::domain::{ArgPassing, GodotTy, ModName, RustTy, TyName};
1819
use crate::special_cases::is_builtin_type_scalar;
1920
use crate::util::ident;
@@ -53,7 +54,6 @@ fn to_hardcoded_rust_ident(full_ty: &GodotTy) -> Option<&str> {
5354
// Others
5455
("bool", None) => "bool",
5556
("String", None) => "GString",
56-
("Array", None) => "VariantArray",
5757

5858
// Types needed for native structures mapping
5959
("uint8_t", None) => "u8",
@@ -124,7 +124,7 @@ pub(crate) fn to_rust_type_abi(ty: &str, ctx: &mut Context) -> (RustTy, bool) {
124124
ty: ident("f64"),
125125
arg_passing: ArgPassing::ByValue,
126126
},
127-
_ => to_rust_type(ty, None, ctx),
127+
_ => to_rust_type(ty, None, FnParamDecl::FnPublic, ctx),
128128
};
129129

130130
(ty, is_obj)
@@ -134,24 +134,37 @@ pub(crate) fn to_rust_type_abi(ty: &str, ctx: &mut Context) -> (RustTy, bool) {
134134
///
135135
/// Uses an internal cache (via `ctx`), as several types are ubiquitous.
136136
// TODO take TyName as input
137-
pub(crate) fn to_rust_type<'a>(ty: &'a str, meta: Option<&'a String>, ctx: &mut Context) -> RustTy {
137+
pub(crate) fn to_rust_type<'a>(
138+
ty: &'a str,
139+
meta: Option<&'a String>,
140+
decl: FnParamDecl,
141+
ctx: &mut Context,
142+
) -> RustTy {
138143
let full_ty = GodotTy {
139144
ty: ty.to_string(),
140145
meta: meta.cloned(),
141146
};
142147

143-
// Separate find + insert slightly slower, but much easier with lifetimes
144-
// The insert path will be hit less often and thus doesn't matter
145-
if let Some(rust_ty) = ctx.find_rust_type(&full_ty) {
146-
rust_ty.clone()
148+
// Don't cache untyped Array, as it depends on context (decl parameter)
149+
let is_context_dependent = ty == "Array" && full_ty.meta.is_none();
150+
151+
if is_context_dependent {
152+
// Skip cache for context-dependent types
153+
to_rust_type_uncached(&full_ty, decl, ctx)
147154
} else {
148-
let rust_ty = to_rust_type_uncached(&full_ty, ctx);
149-
ctx.insert_rust_type(full_ty, rust_ty.clone());
150-
rust_ty
155+
// Separate find + insert slightly slower, but much easier with lifetimes
156+
// The insert path will be hit less often and thus doesn't matter
157+
if let Some(rust_ty) = ctx.find_rust_type(&full_ty) {
158+
rust_ty.clone()
159+
} else {
160+
let rust_ty = to_rust_type_uncached(&full_ty, decl, ctx);
161+
ctx.insert_rust_type(full_ty, rust_ty.clone());
162+
rust_ty
163+
}
151164
}
152165
}
153166

154-
fn to_rust_type_uncached(full_ty: &GodotTy, ctx: &mut Context) -> RustTy {
167+
fn to_rust_type_uncached(full_ty: &GodotTy, decl: FnParamDecl, ctx: &mut Context) -> RustTy {
155168
let ty = full_ty.ty.as_str();
156169

157170
/// Transforms a Godot class/builtin/enum IDENT (without `::` or other syntax) to a Rust one
@@ -164,6 +177,34 @@ fn to_rust_type_uncached(full_ty: &GodotTy, ctx: &mut Context) -> RustTy {
164177
}
165178
}
166179

180+
// Special case: untyped Array depends on context
181+
if ty == "Array" && full_ty.meta.is_none() {
182+
return match decl {
183+
FnParamDecl::FnVirtual => {
184+
// Virtual method parameters (Godot → Rust): Array<Variant>.
185+
conv::to_rust_type_abi("Array", ctx).0
186+
}
187+
FnParamDecl::FnReturn => {
188+
// Outbound returns (Godot → Rust): Array<Variant>.
189+
conv::to_rust_type_abi("Array", ctx).0
190+
}
191+
FnParamDecl::FnReturnVirtual => {
192+
// Virtual returns (Rust → Godot): OutArray for covariance.
193+
RustTy::BuiltinIdent {
194+
ty: ident("OutArray"),
195+
arg_passing: ctx.get_builtin_arg_passing(full_ty),
196+
}
197+
}
198+
_ => {
199+
// All other contexts (outbound parameters, internal, fields): OutArray.
200+
RustTy::BuiltinIdent {
201+
ty: ident("OutArray"),
202+
arg_passing: ctx.get_builtin_arg_passing(full_ty),
203+
}
204+
}
205+
};
206+
}
207+
167208
if ty.ends_with('*') {
168209
// Pointer type; strip '*', see if const, and then resolve the inner type.
169210
let mut ty = ty[0..ty.len() - 1].to_string();
@@ -188,7 +229,7 @@ fn to_rust_type_uncached(full_ty: &GodotTy, ctx: &mut Context) -> RustTy {
188229

189230
// .trim() is necessary here, as Godot places a space between a type and the stars when representing a double pointer.
190231
// Example: "int*" but "int **".
191-
let inner_type = to_rust_type(ty.trim(), None, ctx);
232+
let inner_type = to_rust_type(ty.trim(), None, FnParamDecl::FnPublic, ctx);
192233
return RustTy::RawPointer {
193234
inner: Box::new(inner_type),
194235
is_const,
@@ -226,7 +267,7 @@ fn to_rust_type_uncached(full_ty: &GodotTy, ctx: &mut Context) -> RustTy {
226267
};
227268
}
228269
} else if let Some(elem_ty) = ty.strip_prefix("typedarray::") {
229-
let rust_elem_ty = to_rust_type(elem_ty, full_ty.meta.as_ref(), ctx);
270+
let rust_elem_ty = to_rust_type(elem_ty, full_ty.meta.as_ref(), decl, ctx);
230271
return if ctx.is_builtin(elem_ty) {
231272
RustTy::BuiltinArray {
232273
elem_type: quote! { Array<#rust_elem_ty> },
@@ -331,6 +372,9 @@ fn to_rust_expr_inner(expr: &str, ty: &RustTy, is_inner: bool) -> TokenStream {
331372
"true" => return quote! { true },
332373
"false" => return quote! { false },
333374
"[]" | "{}" if is_inner => return quote! {},
375+
"[]" if matches!(ty, RustTy::BuiltinIdent { ty, .. } if ty == "OutArray") => {
376+
return quote! { OutArray::new_untyped() }
377+
}
334378
"[]" => return quote! { Array::new() }, // VariantArray or Array<T>
335379
"{}" => return quote! { Dictionary::new() },
336380
"null" => {

godot-codegen/src/generator/builtins.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use proc_macro2::{Ident, TokenStream};
1111
use quote::quote;
1212

1313
use crate::context::Context;
14-
use crate::generator::functions_common::{FnCode, FnDefinition, FnDefinitions};
14+
use crate::generator::functions_common::{FnCode, FnDefinition, FnDefinitions, FnParamDecl};
1515
use crate::generator::method_tables::MethodTableKey;
1616
use crate::generator::{enums, functions_common};
1717
use crate::models::domain::{
@@ -99,7 +99,7 @@ fn make_builtin_class(
9999

100100
let RustTy::BuiltinIdent {
101101
ty: outer_class, ..
102-
} = conv::to_rust_type(godot_name, None, ctx)
102+
} = conv::to_rust_type(godot_name, None, FnParamDecl::FnPublic, ctx)
103103
else {
104104
panic!("Rust type `{godot_name}` categorized wrong")
105105
};

godot-codegen/src/generator/central_files.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use quote::{format_ident, quote, ToTokens};
1010

1111
use crate::context::Context;
1212
use crate::conv;
13+
use crate::generator::functions_common::FnParamDecl;
1314
use crate::generator::sys_pointer_types::make_godotconvert_for_systypes;
1415
use crate::generator::{enums, gdext_build_struct};
1516
use crate::models::domain::ExtensionApi;
@@ -179,7 +180,7 @@ fn make_variant_enums(api: &ExtensionApi, ctx: &mut Context) -> VariantEnums {
179180
for builtin in api.builtins.iter() {
180181
let original_name = builtin.godot_original_name();
181182
let shout_case = builtin.godot_shout_name();
182-
let rust_ty = conv::to_rust_type(original_name, None, ctx);
183+
let rust_ty = conv::to_rust_type(original_name, None, FnParamDecl::FnPublic, ctx);
183184
let pascal_case = conv::to_pascal_case(original_name);
184185

185186
result

godot-codegen/src/generator/functions_common.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,21 @@ pub(crate) enum FnParamDecl {
403403

404404
/// Store in a field, i.e. `v` or `CowArg<T>`.
405405
Field,
406+
407+
/// Parameters in virtual methods (callbacks from Godot to Rust).
408+
///
409+
/// Uses direct types (no `impl AsArg`) and VariantArray` for untyped arrays.
410+
FnVirtual,
411+
412+
/// Return values from outbound functions (Godot -> Rust).
413+
///
414+
/// Uses `VariantArray` for untyped arrays.
415+
FnReturn,
416+
417+
/// Return values from virtual methods (Rust -> Godot).
418+
///
419+
/// Uses `OutArray` for untyped arrays, to allow covariant returns.
420+
FnReturnVirtual,
406421
}
407422

408423
pub(crate) struct LifetimeGen {
@@ -464,6 +479,9 @@ pub(crate) fn make_param_or_field_type(
464479
FnParamDecl::Field => {
465480
quote! { CowArg<'a, #ty> }
466481
}
482+
FnParamDecl::FnVirtual | FnParamDecl::FnReturn | FnParamDecl::FnReturnVirtual => {
483+
quote! { #ty }
484+
}
467485
}
468486
}
469487

@@ -480,6 +498,9 @@ pub(crate) fn make_param_or_field_type(
480498
FnParamDecl::FnPublicLifetime => quote! { impl AsArg<#ty> + 'a },
481499
FnParamDecl::FnInternal => quote! { CowArg<#ty> },
482500
FnParamDecl::Field => quote! { CowArg<'a, #ty> },
501+
FnParamDecl::FnVirtual | FnParamDecl::FnReturn | FnParamDecl::FnReturnVirtual => {
502+
quote! { #ty }
503+
}
483504
}
484505
}
485506

@@ -499,6 +520,9 @@ pub(crate) fn make_param_or_field_type(
499520
FnParamDecl::FnPublicLifetime => quote! { &'a #ty },
500521
FnParamDecl::FnInternal => quote! { RefArg<#ty> },
501522
FnParamDecl::Field => quote! { CowArg<'a, #ty> },
523+
FnParamDecl::FnVirtual | FnParamDecl::FnReturn | FnParamDecl::FnReturnVirtual => {
524+
quote! { #ty }
525+
}
502526
}
503527
}
504528

godot-codegen/src/models/domain.rs

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use quote::{format_ident, quote, ToTokens};
1818

1919
use crate::context::Context;
2020
use crate::conv;
21+
use crate::generator::functions_common::FnParamDecl;
2122
use crate::models::json::{JsonMethodArg, JsonMethodReturn};
2223
use crate::util::{ident, option_as_slice, safe_ident};
2324

@@ -555,26 +556,37 @@ impl FnParamBuilder {
555556
}
556557

557558
/// Builds a single function parameter from the provided JSON method argument.
558-
pub fn build_single(self, method_arg: &JsonMethodArg, ctx: &mut Context) -> FnParam {
559-
self.build_single_impl(method_arg, ctx)
559+
pub fn build_single(
560+
self,
561+
method_arg: &JsonMethodArg,
562+
decl: FnParamDecl,
563+
ctx: &mut Context,
564+
) -> FnParam {
565+
self.build_single_impl(method_arg, decl, ctx)
560566
}
561567

562568
/// Builds a vector of function parameters from the provided JSON method arguments.
563569
pub fn build_many(
564570
self,
565571
method_args: &Option<Vec<JsonMethodArg>>,
572+
decl: FnParamDecl,
566573
ctx: &mut Context,
567574
) -> Vec<FnParam> {
568575
option_as_slice(method_args)
569576
.iter()
570-
.map(|arg| self.build_single_impl(arg, ctx))
577+
.map(|arg| self.build_single_impl(arg, decl, ctx))
571578
.collect()
572579
}
573580

574581
/// Core implementation for processing a single JSON method argument into a `FnParam`.
575-
fn build_single_impl(&self, method_arg: &JsonMethodArg, ctx: &mut Context) -> FnParam {
582+
fn build_single_impl(
583+
&self,
584+
method_arg: &JsonMethodArg,
585+
decl: FnParamDecl,
586+
ctx: &mut Context,
587+
) -> FnParam {
576588
let name = safe_ident(&method_arg.name);
577-
let type_ = conv::to_rust_type(&method_arg.type_, method_arg.meta.as_ref(), ctx);
589+
let type_ = conv::to_rust_type(&method_arg.type_, method_arg.meta.as_ref(), decl, ctx);
578590

579591
// Apply enum replacement if one exists for this parameter
580592
let matching_replacement = self
@@ -630,7 +642,7 @@ pub struct FnReturn {
630642

631643
impl FnReturn {
632644
pub fn new(return_value: &Option<JsonMethodReturn>, ctx: &mut Context) -> Self {
633-
Self::with_enum_replacements(return_value, &[], ctx)
645+
Self::with_enum_replacements(return_value, &[], FnParamDecl::FnReturn, ctx)
634646
}
635647

636648
pub fn with_generic_builtin(generic_type: RustTy) -> Self {
@@ -643,10 +655,11 @@ impl FnReturn {
643655
pub fn with_enum_replacements(
644656
return_value: &Option<JsonMethodReturn>,
645657
replacements: EnumReplacements,
658+
decl: FnParamDecl,
646659
ctx: &mut Context,
647660
) -> Self {
648661
if let Some(ret) = return_value {
649-
let ty = conv::to_rust_type(&ret.type_, ret.meta.as_ref(), ctx);
662+
let ty = conv::to_rust_type(&ret.type_, ret.meta.as_ref(), decl, ctx);
650663

651664
// Apply enum replacement if one exists for return type (indicated by empty string)
652665
let matching_replacement = replacements.iter().find(|(p, ..)| p.is_empty());
@@ -726,7 +739,7 @@ pub enum RustTy {
726739

727740
/// `Array<i32>`
728741
///
729-
/// Note that untyped arrays are mapped as `BuiltinIdent("Array")`.
742+
/// Untyped arrays are either `BuiltinIdent("OutArray")` for outbound methods, or `BuiltinIdent("Array")` for virtual methods.
730743
BuiltinArray { elem_type: TokenStream },
731744

732745
/// Will be included as `Array<T>` in the generated source.

godot-codegen/src/models/domain_mapping.rs

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use std::collections::HashMap;
1010
use proc_macro2::Ident;
1111

1212
use crate::context::Context;
13+
use crate::generator::functions_common::FnParamDecl;
1314
use crate::models::domain::{
1415
BuildConfiguration, BuiltinClass, BuiltinMethod, BuiltinSize, BuiltinVariant, Class,
1516
ClassCommons, ClassConstant, ClassConstantValue, ClassMethod, ClassSignal, Constructor, Enum,
@@ -385,9 +386,11 @@ impl BuiltinMethod {
385386
godot_name: method.name.clone(),
386387
// Disable default parameters for builtin classes.
387388
// They are not public-facing and need more involved implementation (lifetimes etc.). Also reduces number of symbols in API.
388-
parameters: FnParam::builder()
389-
.no_defaults()
390-
.build_many(&method.arguments, ctx),
389+
parameters: FnParam::builder().no_defaults().build_many(
390+
&method.arguments,
391+
FnParamDecl::FnPublic,
392+
ctx,
393+
),
391394
return_value,
392395
is_vararg: method.is_vararg,
393396
is_private: false, // See 'exposed' below. Could be special_cases::is_method_private(builtin_name, &method.name),
@@ -536,12 +539,26 @@ impl ClassMethod {
536539
method.return_value.is_some(),
537540
);
538541

542+
let param_decl = match direction {
543+
FnDirection::Virtual { .. } => FnParamDecl::FnVirtual,
544+
_ => FnParamDecl::FnPublic,
545+
};
546+
539547
let parameters = FnParam::builder()
540548
.enum_replacements(enum_replacements)
541-
.build_many(&method.arguments, ctx);
549+
.build_many(&method.arguments, param_decl, ctx);
550+
551+
let return_decl = match direction {
552+
FnDirection::Virtual { .. } => FnParamDecl::FnReturnVirtual,
553+
_ => FnParamDecl::FnReturn,
554+
};
542555

543-
let return_value =
544-
FnReturn::with_enum_replacements(&method.return_value, enum_replacements, ctx);
556+
let return_value = FnReturn::with_enum_replacements(
557+
&method.return_value,
558+
enum_replacements,
559+
return_decl,
560+
ctx,
561+
);
545562

546563
let is_unsafe = Self::function_uses_pointers(&parameters, &return_value);
547564

@@ -609,7 +626,11 @@ impl ClassSignal {
609626

610627
Some(Self {
611628
name: json_signal.name.clone(),
612-
parameters: FnParam::builder().build_many(&json_signal.arguments, ctx),
629+
parameters: FnParam::builder().build_many(
630+
&json_signal.arguments,
631+
FnParamDecl::FnPublic,
632+
ctx,
633+
),
613634
surrounding_class: surrounding_class.clone(),
614635
})
615636
}
@@ -628,7 +649,7 @@ impl UtilityFunction {
628649
let parameters = if function.is_vararg && args.len() == 1 && args[0].name == "arg1" {
629650
vec![]
630651
} else {
631-
FnParam::builder().build_many(&function.arguments, ctx)
652+
FnParam::builder().build_many(&function.arguments, FnParamDecl::FnPublic, ctx)
632653
};
633654

634655
let godot_method_name = function.name.clone();

godot-codegen/src/special_cases/codegen_special_cases.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ pub(crate) fn is_native_struct_excluded(_native_struct: &str) -> bool {
4242
#[cfg(not(feature = "codegen-full"))]
4343
fn is_type_excluded(ty: &str, ctx: &mut Context) -> bool {
4444
use crate::conv;
45+
use crate::generator::functions_common::FnParamDecl;
4546
use crate::models::domain::RustTy;
4647

4748
fn is_rust_type_excluded(ty: &RustTy) -> bool {
@@ -62,7 +63,7 @@ fn is_type_excluded(ty: &str, ctx: &mut Context) -> bool {
6263
RustTy::ExtenderReceiver { .. } => false,
6364
}
6465
}
65-
is_rust_type_excluded(&conv::to_rust_type(ty, None, ctx))
66+
is_rust_type_excluded(&conv::to_rust_type(ty, None, FnParamDecl::FnPublic, ctx))
6667
}
6768

6869
#[cfg(feature = "codegen-full")]

0 commit comments

Comments
 (0)