Skip to content

Commit fa413fa

Browse files
committed
AsArg trait enabling implicit conversions for object parameters (WIP)
1 parent 8d60b03 commit fa413fa

File tree

16 files changed

+303
-196
lines changed

16 files changed

+303
-196
lines changed

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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,8 +231,12 @@ fn to_rust_type_uncached(full_ty: &GodotTy, ctx: &mut Context) -> RustTy {
231231
RustTy::BuiltinIdent(rustify_ty(ty))
232232
} else {
233233
let ty = rustify_ty(ty);
234+
let qualified_class = quote! { crate::classes::#ty };
235+
234236
RustTy::EngineClass {
235-
tokens: quote! { Gd<crate::classes::#ty> },
237+
tokens: quote! { Gd<#qualified_class> },
238+
arg_view: quote! { RawGd<#qualified_class> },
239+
impl_as_arg: quote! { impl AsArg<#qualified_class> },
236240
inner_class: ty,
237241
}
238242
}

godot-codegen/src/generator/default_parameters.rs

Lines changed: 16 additions & 9 deletions
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
};
@@ -192,9 +197,8 @@ fn make_extender_receiver(sig: &dyn Function) -> ExtenderReceiver {
192197
object_fn_param: Some(FnParam {
193198
name: ident("surround_object"),
194199
// Not exactly EngineClass, but close enough
195-
type_: RustTy::EngineClass {
200+
type_: RustTy::ExtenderReceiver {
196201
tokens: quote! { &'a #builder_mut re_export::#class },
197-
inner_class: ident("unknown"),
198202
},
199203
default_value: None,
200204
}),
@@ -244,6 +248,8 @@ fn make_extender(
244248
default_value,
245249
} = param;
246250

251+
let type_ = type_.param_decl();
252+
247253
// Initialize with default parameters where available, forward constructor args otherwise
248254
let init = if let Some(value) = default_value {
249255
quote! { #name: #value }
@@ -258,6 +264,7 @@ fn make_extender(
258264

259265
for param in default_fn_params {
260266
let FnParam { name, type_, .. } = param;
267+
let type_ = type_.param_decl();
261268

262269
let method = quote! {
263270
#[inline]

godot-codegen/src/generator/functions_common.rs

Lines changed: 67 additions & 34 deletions
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().into_iter(),
123+
sig.is_virtual(),
124+
!has_default_params, // For *_full function, we don't need impl AsArg<T> parameters
125+
!has_default_params, // or arg.as_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_arg().
199+
let [_, _, arg_names_without_asarg] = make_params_exprs(
200+
sig.params().into_iter(),
201+
false,
202+
!has_default_params, // For *_full function, we don't need impl AsArg<T> parameters
203+
false, // or arg.as_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 AsArg. 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 AsArg<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_arg() vs. arg
335+
if arg_is_asarg {
336+
arg_names.push(quote! { #param_name.as_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

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,11 @@ impl FnQualifier {
473473

474474
pub struct FnParam {
475475
pub name: Ident,
476+
477+
/// Type, as it appears in `type CallSig` tuple definition.
476478
pub type_: RustTy,
479+
480+
/// Rust expression for default value, if available.
477481
pub default_value: Option<TokenStream>,
478482
}
479483

@@ -510,6 +514,7 @@ impl FnParam {
510514
}
511515
}
512516

517+
/// `impl AsArg<T>` for object parameters. Only set if requested and `T` is an engine class.
513518
pub fn new_no_defaults(method_arg: &JsonMethodArg, ctx: &mut Context) -> FnParam {
514519
FnParam {
515520
name: safe_ident(&method_arg.name),
@@ -518,6 +523,15 @@ impl FnParam {
518523
default_value: None,
519524
}
520525
}
526+
527+
// pub fn impl_param_decl(&self) -> Option<TokenStream> {
528+
// match &self.type_ {
529+
// RustTy::EngineClass { inner_class, .. } => {
530+
// Some(quote! { impl AsArg<crate::classes::#inner_class> })
531+
// }
532+
// _ => None,
533+
// }
534+
// }
521535
}
522536

523537
// ----------------------------------------------------------------------------------------------------------------------------------------------
@@ -573,15 +587,6 @@ pub struct GodotTy {
573587
pub meta: Option<String>,
574588
}
575589

576-
// impl GodotTy {
577-
// fn new<'a>(ty: &'a String, meta: &'a Option<String>) -> Self {
578-
// Self {
579-
// ty: ty.clone(),
580-
// meta: meta.clone(),
581-
// }
582-
// }
583-
// }
584-
585590
// ----------------------------------------------------------------------------------------------------------------------------------------------
586591
// Rust type
587592

@@ -621,15 +626,34 @@ pub enum RustTy {
621626

622627
/// `Gd<Node>`
623628
EngineClass {
624-
/// Tokens with full `Gd<T>`
629+
/// Tokens with full `Gd<T>` (e.g. used in return type position).
625630
tokens: TokenStream,
631+
632+
/// Tokens with `RawGd<T>` (used in `type CallSig` tuple types).
633+
arg_view: TokenStream,
634+
635+
/// Signature declaration with `impl AsArg<T>`.
636+
impl_as_arg: TokenStream,
637+
626638
/// only inner `T`
627639
#[allow(dead_code)] // only read in minimal config
628640
inner_class: Ident,
629641
},
642+
643+
/// Receiver type of default parameters extender constructor.
644+
ExtenderReceiver { tokens: TokenStream },
630645
}
631646

632647
impl RustTy {
648+
pub fn param_decl(&self) -> TokenStream {
649+
match self {
650+
RustTy::EngineClass {
651+
arg_view: raw_gd, ..
652+
} => raw_gd.clone(),
653+
other => other.to_token_stream(),
654+
}
655+
}
656+
633657
pub fn return_decl(&self) -> TokenStream {
634658
match self {
635659
Self::EngineClass { tokens, .. } => quote! { -> Option<#tokens> },
@@ -655,6 +679,7 @@ impl ToTokens for RustTy {
655679
RustTy::EngineEnum { tokens: path, .. } => path.to_tokens(tokens),
656680
RustTy::EngineBitfield { tokens: path, .. } => path.to_tokens(tokens),
657681
RustTy::EngineClass { tokens: path, .. } => path.to_tokens(tokens),
682+
RustTy::ExtenderReceiver { tokens: path } => path.to_tokens(tokens),
658683
}
659684
}
660685
}

godot-codegen/src/special_cases/codegen_special_cases.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ fn is_type_excluded(ty: &str, ctx: &mut Context) -> bool {
5353
Some(class) => is_class_excluded(class.as_str()),
5454
},
5555
RustTy::EngineClass { inner_class, .. } => is_class_excluded(&inner_class.to_string()),
56+
RustTy::ExtenderReceiver { .. } => false,
5657
}
5758
}
5859
is_rust_type_excluded(&conv::to_rust_type(ty, None, ctx))

godot-codegen/src/util.rs

Lines changed: 1 addition & 1 deletion
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, RawGd, AsArg};
3030
use crate::sys::GodotFfi as _;
3131
}
3232
}

godot-core/src/meta/sealed.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ 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> {}
6666
impl<T> Sealed for Option<T>
6767
where
6868
T: GodotType,

godot-core/src/meta/traits.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,7 @@ pub trait GodotFfiVariant: Sized + GodotFfi {
3535
// type. For instance [`i32`] does not implement `GodotFfi` because it cannot represent all values of
3636
// Godot's `int` type, however it does implement `GodotType` because we can set the metadata of values with
3737
// this type to indicate that they are 32 bits large.
38-
pub trait GodotType:
39-
GodotConvert<Via = Self> + ToGodot + FromGodot + sealed::Sealed + 'static
38+
pub trait GodotType: GodotConvert<Via = Self> + ToGodot + FromGodot + sealed::Sealed //+ 'static
4039
{
4140
#[doc(hidden)]
4241
type Ffi: GodotFfiVariant;

0 commit comments

Comments
 (0)