Skip to content

Commit dc82c84

Browse files
authored
Merge pull request #800 from godot-rust/feature/param-conversions
`AsObjectArg` trait enabling implicit conversions for object parameters
2 parents 6d34dad + f1d2817 commit dc82c84

File tree

19 files changed

+603
-133
lines changed

19 files changed

+603
-133
lines changed

examples/dodge-the-creeps/rust/src/main_scene.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ impl Main {
9090

9191
mob_scene.set_rotation(direction);
9292

93-
self.base_mut().add_child(mob_scene.clone().upcast());
93+
self.base_mut().add_child(&mob_scene);
9494

9595
let mut mob = mob_scene.cast::<mob::Mob>();
9696
let range = {

godot-codegen/src/conv/type_conversions.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -232,8 +232,12 @@ fn to_rust_type_uncached(full_ty: &GodotTy, ctx: &mut Context) -> RustTy {
232232
RustTy::BuiltinIdent(rustify_ty(ty))
233233
} else {
234234
let ty = rustify_ty(ty);
235+
let qualified_class = quote! { crate::classes::#ty };
236+
235237
RustTy::EngineClass {
236-
tokens: quote! { Gd<crate::classes::#ty> },
238+
tokens: quote! { Gd<#qualified_class> },
239+
arg_view: quote! { ObjectArg<#qualified_class> },
240+
impl_as_arg: quote! { impl AsObjectArg<#qualified_class> },
237241
inner_class: ty,
238242
}
239243
}

godot-codegen/src/generator/default_parameters.rs

+15-7
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,13 @@ pub fn make_function_definition_with_defaults(
4848

4949
let receiver_param = &code.receiver.param;
5050
let receiver_self = &code.receiver.self_prefix;
51-
let (required_params, required_args) =
52-
functions_common::make_params_and_args(&required_fn_params);
51+
52+
let [required_params_impl_asarg, _, required_args_asarg] =
53+
functions_common::make_params_exprs(required_fn_params.iter().cloned(), false, true, true);
54+
55+
let [required_params_plain, _, required_args_internal] =
56+
functions_common::make_params_exprs(required_fn_params.into_iter(), false, false, false);
57+
5358
let return_decl = &sig.return_value().decl;
5459

5560
// Technically, the builder would not need a lifetime -- it could just maintain an `object_ptr` copy.
@@ -73,7 +78,7 @@ pub fn make_function_definition_with_defaults(
7378
impl #builder_lifetime #builder_ty #builder_lifetime {
7479
fn new(
7580
#object_param
76-
#( #required_params, )*
81+
#( #required_params_plain, )*
7782
) -> Self {
7883
Self {
7984
#( #builder_inits, )*
@@ -95,21 +100,21 @@ pub fn make_function_definition_with_defaults(
95100
#[inline]
96101
#vis fn #simple_fn_name(
97102
#receiver_param
98-
#( #required_params, )*
103+
#( #required_params_impl_asarg, )*
99104
) #return_decl {
100105
#receiver_self #extended_fn_name(
101-
#( #required_args, )*
106+
#( #required_args_internal, )*
102107
).done()
103108
}
104109

105110
#[inline]
106111
#vis fn #extended_fn_name(
107112
#receiver_param
108-
#( #required_params, )*
113+
#( #required_params_impl_asarg, )*
109114
) -> #builder_ty #builder_anon_lifetime {
110115
#builder_ty::new(
111116
#object_arg
112-
#( #required_args, )*
117+
#( #required_args_asarg, )*
113118
)
114119
}
115120
};
@@ -242,6 +247,8 @@ fn make_extender(
242247
default_value,
243248
} = param;
244249

250+
let type_ = type_.param_decl();
251+
245252
// Initialize with default parameters where available, forward constructor args otherwise
246253
let init = if let Some(value) = default_value {
247254
quote! { #name: #value }
@@ -256,6 +263,7 @@ fn make_extender(
256263

257264
for param in default_fn_params {
258265
let FnParam { name, type_, .. } = param;
266+
let type_ = type_.param_decl();
259267

260268
let method = quote! {
261269
#[inline]

godot-codegen/src/generator/functions_common.rs

+67-34
Original file line numberDiff line numberDiff line change
@@ -118,24 +118,30 @@ pub fn make_function_definition(
118118
(TokenStream::new(), TokenStream::new())
119119
};
120120

121-
let [params, param_types, arg_names] = make_params_exprs(sig.params());
121+
let [params, param_types, arg_names] = make_params_exprs(
122+
sig.params().iter(),
123+
sig.is_virtual(),
124+
!has_default_params, // For *_full function, we don't need impl AsObjectArg<T> parameters
125+
!has_default_params, // or arg.as_object_arg() calls.
126+
);
122127

123128
let rust_function_name_str = sig.name();
124-
let primary_fn_name = if has_default_params {
125-
format_ident!("{}_full", safe_ident(rust_function_name_str))
126-
} else {
127-
safe_ident(rust_function_name_str)
128-
};
129129

130-
let (default_fn_code, default_structs_code) = if has_default_params {
131-
default_parameters::make_function_definition_with_defaults(
132-
sig,
133-
code,
134-
&primary_fn_name,
135-
cfg_attributes,
136-
)
130+
let (primary_fn_name, default_fn_code, default_structs_code);
131+
if has_default_params {
132+
primary_fn_name = format_ident!("{}_full", safe_ident(rust_function_name_str));
133+
134+
(default_fn_code, default_structs_code) =
135+
default_parameters::make_function_definition_with_defaults(
136+
sig,
137+
code,
138+
&primary_fn_name,
139+
cfg_attributes,
140+
);
137141
} else {
138-
(TokenStream::new(), TokenStream::new())
142+
primary_fn_name = safe_ident(rust_function_name_str);
143+
default_fn_code = TokenStream::new();
144+
default_structs_code = TokenStream::new();
139145
};
140146

141147
let return_ty = &sig.return_value().type_tokens();
@@ -189,6 +195,14 @@ pub fn make_function_definition(
189195
// Note: all varargs functions are non-static, which is why there are some shortcuts in try_*() argument forwarding.
190196
// This can be made more complex if ever necessary.
191197

198+
// A function() may call try_function(), its arguments should not have .as_object_arg().
199+
let [_, _, arg_names_without_asarg] = make_params_exprs(
200+
sig.params().iter(),
201+
false,
202+
!has_default_params, // For *_full function, we don't need impl AsObjectArg<T> parameters
203+
false, // or arg.as_object_arg() calls.
204+
);
205+
192206
quote! {
193207
/// # Panics
194208
/// This is a _varcall_ method, meaning parameters and return values are passed as `Variant`.
@@ -199,7 +213,7 @@ pub fn make_function_definition(
199213
#( #params, )*
200214
varargs: &[Variant]
201215
) #return_decl {
202-
Self::#try_fn_name(self, #( #arg_names, )* varargs)
216+
Self::#try_fn_name(self, #( #arg_names_without_asarg, )* varargs)
203217
.unwrap_or_else(|e| panic!("{e}"))
204218
}
205219

@@ -278,19 +292,6 @@ pub fn make_receiver(qualifier: FnQualifier, ffi_arg_in: TokenStream) -> FnRecei
278292
self_prefix,
279293
}
280294
}
281-
282-
pub fn make_params_and_args(method_args: &[&FnParam]) -> (Vec<TokenStream>, Vec<TokenStream>) {
283-
method_args
284-
.iter()
285-
.map(|param| {
286-
let param_name = &param.name;
287-
let param_ty = &param.type_;
288-
289-
(quote! { #param_name: #param_ty }, quote! { #param_name })
290-
})
291-
.unzip()
292-
}
293-
294295
pub fn make_vis(is_private: bool) -> TokenStream {
295296
if is_private {
296297
quote! { pub(crate) }
@@ -302,18 +303,50 @@ pub fn make_vis(is_private: bool) -> TokenStream {
302303
// ----------------------------------------------------------------------------------------------------------------------------------------------
303304
// Implementation
304305

305-
fn make_params_exprs(method_args: &[FnParam]) -> [Vec<TokenStream>; 3] {
306+
pub(crate) fn make_params_exprs<'a>(
307+
method_args: impl Iterator<Item = &'a FnParam>,
308+
is_virtual: bool,
309+
param_is_impl_asarg: bool,
310+
arg_is_asarg: bool,
311+
) -> [Vec<TokenStream>; 3] {
306312
let mut params = vec![];
307-
let mut param_types = vec![];
313+
let mut param_types = vec![]; // or non-generic params
308314
let mut arg_names = vec![];
309315

310-
for param in method_args.iter() {
316+
for param in method_args {
311317
let param_name = &param.name;
312318
let param_ty = &param.type_;
313319

314-
params.push(quote! { #param_name: #param_ty });
315-
param_types.push(quote! { #param_ty });
316-
arg_names.push(quote! { #param_name });
320+
// Objects (Gd<T>) use implicit conversions via AsObjectArg. Only use in non-virtual functions.
321+
match &param.type_ {
322+
RustTy::EngineClass {
323+
arg_view,
324+
impl_as_arg,
325+
..
326+
} if !is_virtual => {
327+
// Parameter declarations in signature: impl AsObjectArg<T>
328+
if param_is_impl_asarg {
329+
params.push(quote! { #param_name: #impl_as_arg });
330+
} else {
331+
params.push(quote! { #param_name: #arg_view });
332+
}
333+
334+
// Argument names in function body: arg.as_object_arg() vs. arg
335+
if arg_is_asarg {
336+
arg_names.push(quote! { #param_name.as_object_arg() });
337+
} else {
338+
arg_names.push(quote! { #param_name });
339+
}
340+
341+
param_types.push(quote! { #arg_view });
342+
}
343+
344+
_ => {
345+
params.push(quote! { #param_name: #param_ty });
346+
arg_names.push(quote! { #param_name });
347+
param_types.push(quote! { #param_ty });
348+
}
349+
}
317350
}
318351

319352
[params, param_types, arg_names]

godot-codegen/src/models/domain.rs

+18-1
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,7 @@ impl FnParam {
514514
}
515515
}
516516

517+
/// `impl AsObjectArg<T>` for object parameters. Only set if requested and `T` is an engine class.
517518
pub fn new_no_defaults(method_arg: &JsonMethodArg, ctx: &mut Context) -> FnParam {
518519
FnParam {
519520
name: safe_ident(&method_arg.name),
@@ -616,8 +617,15 @@ pub enum RustTy {
616617

617618
/// `Gd<Node>`
618619
EngineClass {
619-
/// Tokens with full `Gd<T>`
620+
/// Tokens with full `Gd<T>` (e.g. used in return type position).
620621
tokens: TokenStream,
622+
623+
/// Tokens with `ObjectArg<T>` (used in `type CallSig` tuple types).
624+
arg_view: TokenStream,
625+
626+
/// Signature declaration with `impl AsObjectArg<T>`.
627+
impl_as_arg: TokenStream,
628+
621629
/// only inner `T`
622630
#[allow(dead_code)] // only read in minimal config
623631
inner_class: Ident,
@@ -628,6 +636,15 @@ pub enum RustTy {
628636
}
629637

630638
impl RustTy {
639+
pub fn param_decl(&self) -> TokenStream {
640+
match self {
641+
RustTy::EngineClass {
642+
arg_view: raw_gd, ..
643+
} => raw_gd.clone(),
644+
other => other.to_token_stream(),
645+
}
646+
}
647+
631648
pub fn return_decl(&self) -> TokenStream {
632649
match self {
633650
Self::EngineClass { tokens, .. } => quote! { -> Option<#tokens> },

godot-codegen/src/util.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ pub fn make_imports() -> TokenStream {
2626
use crate::meta::{ClassName, PtrcallSignatureTuple, VarcallSignatureTuple};
2727
use crate::classes::native::*;
2828
use crate::classes::Object;
29-
use crate::obj::Gd;
29+
use crate::obj::{Gd, ObjectArg, AsObjectArg};
3030
use crate::sys::GodotFfi as _;
3131
}
3232
}

godot-core/src/builtin/variant/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ pub struct Variant {
2929

3030
impl Variant {
3131
/// Create an empty variant (`null` value in GDScript).
32+
///
33+
/// If a Godot engine API accepts object (not variant) parameters and you'd like to pass `null`, use
34+
/// [`Gd::null_arg()`][crate::obj::Gd::null_arg] instead.
3235
pub fn nil() -> Self {
3336
Self::default()
3437
}

godot-core/src/meta/sealed.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,9 @@ impl Sealed for f32 {}
6161
impl Sealed for () {}
6262
impl Sealed for Variant {}
6363
impl<T: ArrayElement> Sealed for Array<T> {}
64-
impl<T: GodotClass> Sealed for RawGd<T> {}
6564
impl<T: GodotClass> Sealed for Gd<T> {}
65+
impl<T: GodotClass> Sealed for RawGd<T> {}
66+
impl<T: GodotClass> Sealed for ObjectArg<T> {}
6667
impl<T> Sealed for Option<T>
6768
where
6869
T: GodotType,

godot-core/src/meta/traits.rs

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ pub trait GodotFfiVariant: Sized + GodotFfi {
3737
// this type to indicate that they are 32 bits large.
3838
pub trait GodotType:
3939
GodotConvert<Via = Self> + ToGodot + FromGodot + sealed::Sealed + 'static
40+
// 'static is not technically required, but it simplifies a few things (limits e.g. ObjectArg).
4041
{
4142
#[doc(hidden)]
4243
type Ffi: GodotFfiVariant;

0 commit comments

Comments
 (0)