Skip to content

Commit 0f97b60

Browse files
committed
Implement AnyArray, typed-or-untyped covariant array
1 parent 6b3a65a commit 0f97b60

File tree

19 files changed

+1019
-272
lines changed

19 files changed

+1019
-272
lines changed

godot-codegen/src/conv/type_conversions.rs

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@ use quote::{quote, ToTokens};
1414

1515
use crate::context::Context;
1616
use crate::conv;
17-
use crate::models::domain::{ArgPassing, GodotTy, ModName, RustTy, TyName};
17+
use crate::models::domain::{ArgPassing, FlowDirection, GodotTy, ModName, RustTy, TyName};
1818
use crate::special_cases::is_builtin_type_scalar;
1919
use crate::util::ident;
2020

2121
// ----------------------------------------------------------------------------------------------------------------------------------------------
2222
// Godot -> Rust types
2323

2424
/// Returns `(identifier, is_copy)` for a hardcoded Rust type, if it exists.
25-
fn to_hardcoded_rust_ident(full_ty: &GodotTy) -> Option<&str> {
25+
fn to_hardcoded_rust_ident(full_ty: &GodotTy) -> Option<Ident> {
2626
let ty = full_ty.ty.as_str();
2727
let meta = full_ty.meta.as_deref();
2828

@@ -53,7 +53,14 @@ fn to_hardcoded_rust_ident(full_ty: &GodotTy) -> Option<&str> {
5353
// Others
5454
("bool", None) => "bool",
5555
("String", None) => "GString",
56-
("Array", None) => "VariantArray",
56+
57+
// Must remain "Array" because this is checked verbatim for later adjustments (e.g. to AnyArray).
58+
// Keep also in line with default-exprs e.g. `Array::new()`.
59+
("Array", None) => match full_ty.flow {
60+
Some(FlowDirection::RustToGodot) => "AnyArray",
61+
Some(FlowDirection::GodotToRust) => "VariantArray",
62+
None => "_unused__Array_must_not_appear_in_idents",
63+
},
5764

5865
// Types needed for native structures mapping
5966
("uint8_t", None) => "u8",
@@ -76,10 +83,10 @@ fn to_hardcoded_rust_ident(full_ty: &GodotTy) -> Option<&str> {
7683
_ => return None,
7784
};
7885

79-
Some(result)
86+
Some(ident(result))
8087
}
8188

82-
fn to_hardcoded_rust_enum(ty: &str) -> Option<&str> {
89+
fn to_hardcoded_rust_enum(ty: &str) -> Option<Ident> {
8390
// Some types like Vector2[i].Axis may not appear in Godot's current JSON, but they are encountered
8491
// in custom Godot builds, e.g. when extending PhysicsServer2D.
8592
let result = match ty {
@@ -92,7 +99,8 @@ fn to_hardcoded_rust_enum(ty: &str) -> Option<&str> {
9299
"enum::Vector3i.Axis" => "Vector3Axis",
93100
_ => return None,
94101
};
95-
Some(result)
102+
103+
Some(ident(result))
96104
}
97105

98106
/// Maps an input type to a Godot type with the same C representation. This is subtly different from [`to_rust_type`],
@@ -124,7 +132,7 @@ pub(crate) fn to_rust_type_abi(ty: &str, ctx: &mut Context) -> (RustTy, bool) {
124132
ty: ident("f64"),
125133
arg_passing: ArgPassing::ByValue,
126134
},
127-
_ => to_rust_type(ty, None, ctx),
135+
_ => to_rust_temporary_type(ty, ctx),
128136
};
129137

130138
(ty, is_obj)
@@ -134,14 +142,28 @@ pub(crate) fn to_rust_type_abi(ty: &str, ctx: &mut Context) -> (RustTy, bool) {
134142
///
135143
/// Uses an internal cache (via `ctx`), as several types are ubiquitous.
136144
// TODO take TyName as input
137-
pub(crate) fn to_rust_type<'a>(ty: &'a str, meta: Option<&'a String>, ctx: &mut Context) -> RustTy {
145+
pub(crate) fn to_rust_type<'a>(
146+
json_ty: &'a str,
147+
meta: Option<&'a String>,
148+
flow: Option<FlowDirection>,
149+
ctx: &mut Context,
150+
) -> RustTy {
151+
// Flow: don't-care for everything besides GDScript types Array or Array[Array].
152+
// Do not panic if not set (used in to_temporary_rust_type()).
153+
let flow = if json_ty == "Array" || json_ty == "typedarray::Array" {
154+
flow
155+
} else {
156+
None
157+
};
158+
138159
let full_ty = GodotTy {
139-
ty: ty.to_string(),
160+
ty: json_ty.to_string(),
140161
meta: meta.cloned(),
162+
flow,
141163
};
142164

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
165+
// Separate find + insert slightly slower, but much easier with lifetimes.
166+
// The insert path will be hit less often and thus doesn't matter.
145167
if let Some(rust_ty) = ctx.find_rust_type(&full_ty) {
146168
rust_ty.clone()
147169
} else {
@@ -151,6 +173,17 @@ pub(crate) fn to_rust_type<'a>(ty: &'a str, meta: Option<&'a String>, ctx: &mut
151173
}
152174
}
153175

176+
/// Converts a Godot type to a Rust type without caching, suitable for cases where only parts of the returned RustTy are needed.
177+
///
178+
/// This is a lightweight alternative to [`to_rust_type()`] for scenarios where only parts of the returned `RustTy` are needed (e.g.
179+
/// just the identifier name).
180+
///
181+
/// The returned type may have inaccuracies in fields that depend on metad or flow direction, so this should only be used when
182+
/// those fields are not needed. This allows for simpler call sites in code that doesn't require complete type information.
183+
pub(crate) fn to_rust_temporary_type(ty: &str, ctx: &mut Context) -> RustTy {
184+
to_rust_type(ty, None, None, ctx)
185+
}
186+
154187
fn to_rust_type_uncached(full_ty: &GodotTy, ctx: &mut Context) -> RustTy {
155188
let ty = full_ty.ty.as_str();
156189

@@ -188,7 +221,7 @@ fn to_rust_type_uncached(full_ty: &GodotTy, ctx: &mut Context) -> RustTy {
188221

189222
// .trim() is necessary here, as Godot places a space between a type and the stars when representing a double pointer.
190223
// Example: "int*" but "int **".
191-
let inner_type = to_rust_type(ty.trim(), None, ctx);
224+
let inner_type = to_rust_type(ty.trim(), None, None, ctx);
192225
return RustTy::RawPointer {
193226
inner: Box::new(inner_type),
194227
is_const,
@@ -199,15 +232,15 @@ fn to_rust_type_uncached(full_ty: &GodotTy, ctx: &mut Context) -> RustTy {
199232
if !ty.starts_with("typedarray::") {
200233
if let Some(hardcoded) = to_hardcoded_rust_ident(full_ty) {
201234
return RustTy::BuiltinIdent {
202-
ty: ident(hardcoded),
235+
ty: hardcoded,
203236
arg_passing: ctx.get_builtin_arg_passing(full_ty),
204237
};
205238
}
206239
}
207240

208241
if let Some(hardcoded) = to_hardcoded_rust_enum(ty) {
209242
return RustTy::EngineEnum {
210-
tokens: ident(hardcoded).to_token_stream(),
243+
tokens: hardcoded.to_token_stream(),
211244
surrounding_class: None, // would need class passed in
212245
is_bitfield: false,
213246
};
@@ -226,7 +259,7 @@ fn to_rust_type_uncached(full_ty: &GodotTy, ctx: &mut Context) -> RustTy {
226259
};
227260
}
228261
} 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);
262+
let rust_elem_ty = to_rust_type(elem_ty, full_ty.meta.as_ref(), full_ty.flow, ctx);
230263
return if ctx.is_builtin(elem_ty) {
231264
RustTy::BuiltinArray {
232265
elem_type: quote! { Array<#rust_elem_ty> },
@@ -331,6 +364,9 @@ fn to_rust_expr_inner(expr: &str, ty: &RustTy, is_inner: bool) -> TokenStream {
331364
"true" => return quote! { true },
332365
"false" => return quote! { false },
333366
"[]" | "{}" if is_inner => return quote! {},
367+
"[]" if matches!(ty, RustTy::BuiltinIdent { ty, .. } if ty == "AnyArray") => {
368+
return quote! { AnyArray::new_untyped() }
369+
}
334370
"[]" => return quote! { Array::new() }, // VariantArray or Array<T>
335371
"{}" => return quote! { Dictionary::new() },
336372
"null" => {

godot-codegen/src/generator/builtins.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ use crate::generator::functions_common::{FnCode, FnDefinition, FnDefinitions};
1515
use crate::generator::method_tables::MethodTableKey;
1616
use crate::generator::{enums, functions_common};
1717
use crate::models::domain::{
18-
BuiltinClass, BuiltinMethod, ClassLike, ExtensionApi, FnDirection, Function, ModName, RustTy,
19-
TyName,
18+
BuiltinClass, BuiltinMethod, ClassLike, ExtensionApi, FlowDirection, FnDirection, Function,
19+
ModName, RustTy, TyName,
2020
};
2121
use crate::{conv, util, SubmitFn};
2222

@@ -97,9 +97,12 @@ fn make_builtin_class(
9797
) -> GeneratedBuiltin {
9898
let godot_name = &class.name().godot_ty;
9999

100+
// Meta direction irrelevant, since we're just interested in the builtin "outer" name.
101+
// Flow mostly irrelevant too, but we'd like to have VariantArray as the outer class, so choose Godot->Rust.
102+
let flow = FlowDirection::GodotToRust;
100103
let RustTy::BuiltinIdent {
101104
ty: outer_class, ..
102-
} = conv::to_rust_type(godot_name, None, ctx)
105+
} = conv::to_rust_type(godot_name, None, Some(flow), ctx)
103106
else {
104107
panic!("Rust type `{godot_name}` categorized wrong")
105108
};

godot-codegen/src/generator/central_files.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crate::context::Context;
1212
use crate::conv;
1313
use crate::generator::sys_pointer_types::make_godotconvert_for_systypes;
1414
use crate::generator::{enums, gdext_build_struct};
15-
use crate::models::domain::ExtensionApi;
15+
use crate::models::domain::{ExtensionApi, FlowDirection};
1616
use crate::util::ident;
1717

1818
pub fn make_sys_central_code(api: &ExtensionApi) -> TokenStream {
@@ -175,11 +175,14 @@ fn make_variant_enums(api: &ExtensionApi, ctx: &mut Context) -> VariantEnums {
175175
variant_ty_enumerators_rust: Vec::with_capacity(len),
176176
};
177177

178+
// When extracting variant, data moves Godot->Rust. Means `VariantArray` for `Array`.
179+
let flow = FlowDirection::GodotToRust;
180+
178181
// Note: NIL is not part of this iteration, it will be added manually.
179182
for builtin in api.builtins.iter() {
180183
let original_name = builtin.godot_original_name();
181184
let shout_case = builtin.godot_shout_name();
182-
let rust_ty = conv::to_rust_type(original_name, None, ctx);
185+
let rust_ty = conv::to_rust_type(original_name, None, Some(flow), ctx);
183186
let pascal_case = conv::to_pascal_case(original_name);
184187

185188
result

godot-codegen/src/generator/functions_common.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,7 @@ pub(crate) enum FnArgExpr {
389389
StoreInDefaultField,
390390
}
391391

392-
/// How parameters are declared in a function signature.
392+
/// Whether parameters need to be declared in a special way (e.g. with `impl AsArg`).
393393
#[derive(Copy, Clone)]
394394
pub(crate) enum FnParamDecl {
395395
/// Public-facing, i.e. `T`, `&T`, `impl AsArg<T>`.
@@ -494,6 +494,11 @@ pub(crate) fn make_param_or_field_type(
494494
let lft = lifetimes.next();
495495
special_ty = Some(quote! { RefArg<#lft, #ty> });
496496

497+
// Transform VariantArray -> AnyArray for outbound parameters.
498+
// FIXME virtual params
499+
// let ty = if matches!(decl, FnParamDecl::)
500+
// ty.try_to_any_array().unwrap_or_else(|| ty.clone());
501+
497502
match decl {
498503
FnParamDecl::FnPublic => quote! { & #ty },
499504
FnParamDecl::FnPublicLifetime => quote! { &'a #ty },

godot-codegen/src/models/domain.rs

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,15 @@ pub enum FnDirection {
479479
Outbound { hash: i64 },
480480
}
481481

482+
/// Whether return values need to be declared in a special way (e.g. `AnyArray`).
483+
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
484+
pub(crate) enum FlowDirection {
485+
/// Godot -> Rust.
486+
GodotToRust,
487+
488+
/// Rust -> Godot.
489+
RustToGodot,
490+
}
482491
// ----------------------------------------------------------------------------------------------------------------------------------------------
483492

484493
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
@@ -555,39 +564,53 @@ impl FnParamBuilder {
555564
}
556565

557566
/// 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)
567+
pub fn build_single(
568+
self,
569+
method_arg: &JsonMethodArg,
570+
flow: FlowDirection,
571+
ctx: &mut Context,
572+
) -> FnParam {
573+
self.build_single_impl(method_arg, flow, ctx)
560574
}
561575

562576
/// Builds a vector of function parameters from the provided JSON method arguments.
563577
pub fn build_many(
564578
self,
565579
method_args: &Option<Vec<JsonMethodArg>>,
580+
flow: FlowDirection,
566581
ctx: &mut Context,
567582
) -> Vec<FnParam> {
568583
option_as_slice(method_args)
569584
.iter()
570-
.map(|arg| self.build_single_impl(arg, ctx))
585+
.map(|arg| self.build_single_impl(arg, flow, ctx))
571586
.collect()
572587
}
573588

574589
/// 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 {
590+
fn build_single_impl(
591+
&self,
592+
method_arg: &JsonMethodArg,
593+
flow: FlowDirection,
594+
ctx: &mut Context,
595+
) -> FnParam {
576596
let name = safe_ident(&method_arg.name);
577-
let type_ = conv::to_rust_type(&method_arg.type_, method_arg.meta.as_ref(), ctx);
597+
let type_ =
598+
conv::to_rust_type(&method_arg.type_, method_arg.meta.as_ref(), Some(flow), ctx);
578599

579600
// Apply enum replacement if one exists for this parameter
580601
let matching_replacement = self
581602
.replacements
582603
.iter()
583604
.find(|(p, ..)| *p == method_arg.name);
605+
584606
let type_ = if let Some((_, enum_name, is_bitfield)) = matching_replacement {
585607
if !type_.is_integer() {
586608
panic!(
587609
"Parameter `{}` is of type {}, but can only replace int with enum",
588610
method_arg.name, type_
589611
);
590612
}
613+
591614
conv::to_enum_type_uncached(enum_name, *is_bitfield)
592615
} else {
593616
type_
@@ -629,8 +652,12 @@ pub struct FnReturn {
629652
}
630653

631654
impl FnReturn {
632-
pub fn new(return_value: &Option<JsonMethodReturn>, ctx: &mut Context) -> Self {
633-
Self::with_enum_replacements(return_value, &[], ctx)
655+
pub fn new(
656+
return_value: &Option<JsonMethodReturn>,
657+
flow: FlowDirection,
658+
ctx: &mut Context,
659+
) -> Self {
660+
Self::with_enum_replacements(return_value, &[], flow, ctx)
634661
}
635662

636663
pub fn with_generic_builtin(generic_type: RustTy) -> Self {
@@ -643,10 +670,11 @@ impl FnReturn {
643670
pub fn with_enum_replacements(
644671
return_value: &Option<JsonMethodReturn>,
645672
replacements: EnumReplacements,
673+
flow: FlowDirection,
646674
ctx: &mut Context,
647675
) -> Self {
648676
if let Some(ret) = return_value {
649-
let ty = conv::to_rust_type(&ret.type_, ret.meta.as_ref(), ctx);
677+
let ty = conv::to_rust_type(&ret.type_, ret.meta.as_ref(), Some(flow), ctx);
650678

651679
// Apply enum replacement if one exists for return type (indicated by empty string)
652680
let matching_replacement = replacements.iter().find(|(p, ..)| p.is_empty());
@@ -710,6 +738,9 @@ pub type EnumReplacements = &'static [(&'static str, &'static str, bool)];
710738
pub struct GodotTy {
711739
pub ty: String,
712740
pub meta: Option<String>,
741+
742+
// None if flow doesn't matter (most types except "Array").
743+
pub flow: Option<FlowDirection>,
713744
}
714745

715746
// ----------------------------------------------------------------------------------------------------------------------------------------------
@@ -726,7 +757,7 @@ pub enum RustTy {
726757

727758
/// `Array<i32>`
728759
///
729-
/// Note that untyped arrays are mapped as `BuiltinIdent("Array")`.
760+
/// Untyped arrays are either `BuiltinIdent("AnyArray")` for outbound methods, or `BuiltinIdent("Array")` for virtual methods.
730761
BuiltinArray { elem_type: TokenStream },
731762

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

0 commit comments

Comments
 (0)