@@ -120,12 +120,24 @@ pub fn make_method_registration(
120120 interface_trait,
121121 ) ;
122122
123+ let default_parameters = make_default_argument_vec (
124+ & signature_info. optional_param_default_exprs ,
125+ & signature_info. param_types ,
126+ ) ?;
127+
123128 // String literals
124129 let class_name_str = class_name. to_string ( ) ;
125130 let method_name_str = func_definition. godot_name ( ) ;
126131
127132 let call_ctx = make_call_context ( & class_name_str, & method_name_str) ;
128- let varcall_fn_decl = make_varcall_fn ( & call_ctx, & forwarding_closure) ;
133+
134+ // Both varcall and ptrcall functions are always generated and registered, even when default parameters are present via #[opt].
135+ // Key differences are:
136+ // - varcall: handles default parameters, applying them when caller provides fewer arguments.
137+ // - ptrcall: optimized path without default handling, can be used when caller provides all arguments.
138+ //
139+ // Godot decides at call-time which calling convention to use based on available type information.
140+ let varcall_fn_decl = make_varcall_fn ( & call_ctx, & forwarding_closure, & default_parameters) ;
129141 let ptrcall_fn_decl = make_ptrcall_fn ( & call_ctx, & forwarding_closure) ;
130142
131143 // String literals II
@@ -166,6 +178,7 @@ pub fn make_method_registration(
166178 & [
167179 #( #param_ident_strs ) , *
168180 ] ,
181+ #default_parameters,
169182 )
170183 } ;
171184
@@ -183,6 +196,48 @@ pub fn make_method_registration(
183196 Ok ( registration)
184197}
185198
199+ /// Generates code to create a `Vec<Variant>` containing default argument values for varcall. Allocates on every call.
200+ fn make_default_argument_vec (
201+ optional_param_default_exprs : & [ TokenStream ] ,
202+ all_params : & [ venial:: TypeExpr ] ,
203+ ) -> ParseResult < TokenStream > {
204+ // Optional params appearing at the end has already been validated in validate_default_exprs().
205+
206+ // Early exit: all parameters are required, not optional. This check is not necessary for correctness.
207+ if optional_param_default_exprs. is_empty ( ) {
208+ return Ok ( quote ! { vec![ ] } ) ;
209+ }
210+
211+ let optional_param_types = all_params
212+ . iter ( )
213+ . skip ( all_params. len ( ) - optional_param_default_exprs. len ( ) ) ;
214+
215+ let default_parameters = optional_param_default_exprs
216+ . iter ( )
217+ . zip ( optional_param_types)
218+ . map ( |( value, param_type) | {
219+ quote ! {
220+ :: godot:: builtin:: Variant :: from(
221+ :: godot:: meta:: AsArg :: <#param_type>:: into_arg( #value)
222+ )
223+ }
224+ } ) ;
225+
226+ // Performance: This generates `vec![...]` in the varcall FFI function, which allocates on *every* call when default parameters
227+ // are present. This is a performance cost we accept for now.
228+ //
229+ // If no #[opt] attributes are used, this generates `vec![]` which does *not* allocate, so most #[func] functions are unaffected.
230+ //
231+ // Potential future improvements:
232+ // - Use `Global<Vec<Variant>>` (or LazyLock/thread_local) to allocate once per function instead of per call.
233+ // - Store defaults in MethodInfo during registration and retrieve via method_data pointer.
234+ //
235+ // Note also that there may be a semantic difference on reusing the same object vs. recreating it, see Python's default-param issue.
236+ Ok ( quote ! {
237+ vec![ #( #default_parameters) , * ]
238+ } )
239+ }
240+
186241// ----------------------------------------------------------------------------------------------------------------------------------------------
187242// Implementation
188243
@@ -208,6 +263,9 @@ pub struct SignatureInfo {
208263 ///
209264 /// Index points into original venial tokens (i.e. takes into account potential receiver params).
210265 pub modified_param_types : Vec < ( usize , venial:: TypeExpr ) > ,
266+
267+ /// Default value expressions `EXPR` from `#[opt(default = EXPR)]`, for all optional parameters.
268+ pub optional_param_default_exprs : Vec < TokenStream > ,
211269}
212270
213271impl SignatureInfo {
@@ -220,6 +278,7 @@ impl SignatureInfo {
220278 param_types : vec ! [ ] ,
221279 return_type : quote ! { ( ) } ,
222280 modified_param_types : vec ! [ ] ,
281+ optional_param_default_exprs : vec ! [ ] ,
223282 }
224283 }
225284
@@ -413,7 +472,7 @@ pub(crate) fn into_signature_info(
413472 let params_span = signature. span ( ) ;
414473 let mut param_idents = Vec :: with_capacity ( num_params) ;
415474 let mut param_types = Vec :: with_capacity ( num_params) ;
416- let ret_type = match signature. return_ty {
475+ let return_type = match signature. return_ty {
417476 None => quote ! { ( ) } ,
418477 Some ( ty) => map_self_to_class_name ( ty. tokens , class_name) ,
419478 } ;
@@ -469,8 +528,9 @@ pub(crate) fn into_signature_info(
469528 params_span,
470529 param_idents,
471530 param_types,
472- return_type : ret_type ,
531+ return_type,
473532 modified_param_types,
533+ optional_param_default_exprs : vec ! [ ] , // Assigned outside, if relevant.
474534 }
475535}
476536
@@ -552,8 +612,12 @@ fn make_method_flags(
552612}
553613
554614/// Generate code for a C FFI function that performs a varcall.
555- fn make_varcall_fn ( call_ctx : & TokenStream , wrapped_method : & TokenStream ) -> TokenStream {
556- let invocation = make_varcall_invocation ( wrapped_method) ;
615+ fn make_varcall_fn (
616+ call_ctx : & TokenStream ,
617+ wrapped_method : & TokenStream ,
618+ default_parameters : & TokenStream ,
619+ ) -> TokenStream {
620+ let invocation = make_varcall_invocation ( wrapped_method, default_parameters) ;
557621
558622 // TODO reduce amount of code generated, by delegating work to a library function. Could even be one that produces this function pointer.
559623 quote ! {
@@ -616,17 +680,24 @@ fn make_ptrcall_invocation(wrapped_method: &TokenStream, is_virtual: bool) -> To
616680}
617681
618682/// Generate code for a `varcall()` call expression.
619- fn make_varcall_invocation ( wrapped_method : & TokenStream ) -> TokenStream {
683+ fn make_varcall_invocation (
684+ wrapped_method : & TokenStream ,
685+ default_parameters : & TokenStream ,
686+ ) -> TokenStream {
620687 quote ! {
621- :: godot:: meta:: Signature :: <CallParams , CallRet >:: in_varcall(
622- instance_ptr,
623- & call_ctx,
624- args_ptr,
625- arg_count,
626- ret,
627- err,
628- #wrapped_method,
629- )
688+ {
689+ let defaults = #default_parameters;
690+ :: godot:: meta:: Signature :: <CallParams , CallRet >:: in_varcall(
691+ instance_ptr,
692+ & call_ctx,
693+ args_ptr,
694+ arg_count,
695+ & defaults,
696+ ret,
697+ err,
698+ #wrapped_method,
699+ )
700+ }
630701 }
631702}
632703
0 commit comments