@@ -120,15 +120,24 @@ pub fn make_method_registration(
120120 interface_trait,
121121 ) ;
122122
123- let ( default_parameters, default_parameters_count) =
124- make_default_parameters ( & func_definition, signature_info) ?;
123+ let default_parameters = make_default_argument_vec (
124+ & signature_info. optional_param_default_exprs ,
125+ & signature_info. param_types ,
126+ ) ?;
125127
126128 // String literals
127129 let class_name_str = class_name. to_string ( ) ;
128130 let method_name_str = func_definition. godot_name ( ) ;
129131
130132 let call_ctx = make_call_context ( & class_name_str, & method_name_str) ;
131- let varcall_fn_decl = make_varcall_fn ( & call_ctx, & forwarding_closure, default_parameters_count) ;
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) ;
132141 let ptrcall_fn_decl = make_ptrcall_fn ( & call_ctx, & forwarding_closure) ;
133142
134143 // String literals II
@@ -187,52 +196,46 @@ pub fn make_method_registration(
187196 Ok ( registration)
188197}
189198
190- fn make_default_parameters (
191- func_definition : & FuncDefinition ,
192- signature_info : & SignatureInfo ,
193- ) -> Result < ( TokenStream , usize ) , venial:: Error > {
194- let default_parameters =
195- validate_default_parameters ( & func_definition. signature_info . default_parameters ) ?;
196- let len = default_parameters. len ( ) ;
197- let default_parameters_type = signature_info
198- . param_types
199- . iter ( )
200- . rev ( )
201- . take ( default_parameters. len ( ) )
202- . rev ( ) ;
203- let default_parameters = default_parameters
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
204212 . iter ( )
205- . zip ( default_parameters_type)
206- . map ( |( value, ty) | quote ! ( :: godot:: builtin:: Variant :: from( #value) ) ) ;
207- // .map(|(value, ty)| quote!(::godot::meta::arg_into_ref!(#value: #ty)));
208- let default_parameters = quote ! { vec![ #( #default_parameters) , * ] } ;
209- Ok ( ( default_parameters, len) )
210- }
213+ . skip ( all_params. len ( ) - optional_param_default_exprs. len ( ) ) ;
211214
212- fn validate_default_parameters (
213- default_parameters : & [ Option < TokenStream > ] ,
214- ) -> ParseResult < Vec < TokenStream > > {
215- let mut res = vec ! [ ] ;
216- let mut allowed = true ;
217- for param in default_parameters. iter ( ) . rev ( ) {
218- match ( param, allowed) {
219- ( Some ( tk) , true ) => {
220- res. push ( tk. clone ( ) ) ; // toreview: if we really care about it, we can use &mut sig_info and mem::take() as we don't use this later
221- }
222- ( None , true ) => {
223- allowed = false ;
224- }
225- ( None , false ) => { }
226- ( Some ( tk) , false ) => {
227- return bail ! (
228- tk,
229- "opt arguments are only allowed at the end of the argument list."
230- ) ;
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+ )
231223 }
232- }
233- }
234- res. reverse ( ) ;
235- Ok ( res)
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+ } )
236239}
237240
238241// ----------------------------------------------------------------------------------------------------------------------------------------------
@@ -260,8 +263,9 @@ pub struct SignatureInfo {
260263 ///
261264 /// Index points into original venial tokens (i.e. takes into account potential receiver params).
262265 pub modified_param_types : Vec < ( usize , venial:: TypeExpr ) > ,
263- /// Contains expressions of the default values of parameters.
264- pub default_parameters : Vec < Option < TokenStream > > ,
266+
267+ /// Default value expressions `EXPR` from `#[opt(default = EXPR)]`, for all optional parameters.
268+ pub optional_param_default_exprs : Vec < TokenStream > ,
265269}
266270
267271impl SignatureInfo {
@@ -274,7 +278,7 @@ impl SignatureInfo {
274278 param_types : vec ! [ ] ,
275279 return_type : quote ! { ( ) } ,
276280 modified_param_types : vec ! [ ] ,
277- default_parameters : vec ! [ ] ,
281+ optional_param_default_exprs : vec ! [ ] ,
278282 }
279283 }
280284
@@ -468,7 +472,7 @@ pub(crate) fn into_signature_info(
468472 let params_span = signature. span ( ) ;
469473 let mut param_idents = Vec :: with_capacity ( num_params) ;
470474 let mut param_types = Vec :: with_capacity ( num_params) ;
471- let ret_type = match signature. return_ty {
475+ let return_type = match signature. return_ty {
472476 None => quote ! { ( ) } ,
473477 Some ( ty) => map_self_to_class_name ( ty. tokens , class_name) ,
474478 } ;
@@ -524,9 +528,9 @@ pub(crate) fn into_signature_info(
524528 params_span,
525529 param_idents,
526530 param_types,
527- return_type : ret_type ,
531+ return_type,
528532 modified_param_types,
529- default_parameters : vec ! [ ] ,
533+ optional_param_default_exprs : vec ! [ ] , // Assigned outside, if relevant.
530534 }
531535}
532536
@@ -611,9 +615,9 @@ fn make_method_flags(
611615fn make_varcall_fn (
612616 call_ctx : & TokenStream ,
613617 wrapped_method : & TokenStream ,
614- default_parameters_count : usize ,
618+ default_parameters : & TokenStream ,
615619) -> TokenStream {
616- let invocation = make_varcall_invocation ( wrapped_method, default_parameters_count ) ;
620+ let invocation = make_varcall_invocation ( wrapped_method, default_parameters ) ;
617621
618622 // TODO reduce amount of code generated, by delegating work to a library function. Could even be one that produces this function pointer.
619623 quote ! {
@@ -678,19 +682,22 @@ fn make_ptrcall_invocation(wrapped_method: &TokenStream, is_virtual: bool) -> To
678682/// Generate code for a `varcall()` call expression.
679683fn make_varcall_invocation (
680684 wrapped_method : & TokenStream ,
681- default_parameters_count : usize ,
685+ default_parameters : & TokenStream ,
682686) -> TokenStream {
683687 quote ! {
684- :: godot:: meta:: Signature :: <CallParams , CallRet >:: in_varcall(
685- instance_ptr,
686- & call_ctx,
687- args_ptr,
688- arg_count,
689- #default_parameters_count,
690- ret,
691- err,
692- #wrapped_method,
693- )
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+ }
694701 }
695702}
696703
0 commit comments