From f5865cbeb5e9e846cf271ccecd20c63ff519b79a Mon Sep 17 00:00:00 2001 From: Jan Haller Date: Sun, 1 Sep 2024 21:46:48 +0200 Subject: [PATCH] Special cases for nullability of virtual function parameters --- .../src/generator/default_parameters.rs | 4 +- .../src/generator/functions_common.rs | 56 +++++++++++++++---- .../src/special_cases/special_cases.rs | 37 ++++++++++++ 3 files changed, 84 insertions(+), 13 deletions(-) diff --git a/godot-codegen/src/generator/default_parameters.rs b/godot-codegen/src/generator/default_parameters.rs index f29b54960..c478874ab 100644 --- a/godot-codegen/src/generator/default_parameters.rs +++ b/godot-codegen/src/generator/default_parameters.rs @@ -50,10 +50,10 @@ pub fn make_function_definition_with_defaults( let receiver_self = &code.receiver.self_prefix; let [required_params_impl_asarg, _, _] = - functions_common::make_params_exprs(required_fn_params.iter().cloned(), false, true, true); + functions_common::make_params_exprs(required_fn_params.iter().cloned(), true, true); let [_, _, required_args_internal] = - functions_common::make_params_exprs(required_fn_params.into_iter(), false, false, false); + functions_common::make_params_exprs(required_fn_params.into_iter(), false, false); let return_decl = &sig.return_value().decl; diff --git a/godot-codegen/src/generator/functions_common.rs b/godot-codegen/src/generator/functions_common.rs index 0d6a2f11c..d44bca6c9 100644 --- a/godot-codegen/src/generator/functions_common.rs +++ b/godot-codegen/src/generator/functions_common.rs @@ -7,6 +7,7 @@ use crate::generator::default_parameters; use crate::models::domain::{FnParam, FnQualifier, Function, RustTy}; +use crate::special_cases; use crate::util::safe_ident; use proc_macro2::TokenStream; use quote::{format_ident, quote}; @@ -119,12 +120,15 @@ pub fn make_function_definition( (TokenStream::new(), TokenStream::new()) }; - let [params, param_types, arg_names] = make_params_exprs( - sig.params().iter(), - sig.is_virtual(), - !has_default_params, // For *_full function, we don't need impl AsObjectArg parameters - !has_default_params, // or arg.as_object_arg() calls. - ); + let [params, param_types, arg_names] = if sig.is_virtual() { + make_params_exprs_virtual(sig.params().iter(), sig) + } else { + make_params_exprs( + sig.params().iter(), + !has_default_params, // For *_full function, we don't need impl AsObjectArg parameters + !has_default_params, // or arg.as_object_arg() calls. + ) + }; let rust_function_name_str = sig.name(); @@ -202,7 +206,6 @@ pub fn make_function_definition( // A function() may call try_function(), its arguments should not have .as_object_arg(). let [_, _, arg_names_without_asarg] = make_params_exprs( sig.params().iter(), - false, !has_default_params, // For *_full function, we don't need impl AsObjectArg parameters false, // or arg.as_object_arg() calls. ); @@ -307,10 +310,9 @@ pub fn make_vis(is_private: bool) -> TokenStream { // ---------------------------------------------------------------------------------------------------------------------------------------------- // Implementation -// Method could possibly be split -- only one invocation uses all 3 return values, the rest uses only index [0] or [2]. +/// For non-virtual functions, returns the parameter declarations, type tokens, and names. pub(crate) fn make_params_exprs<'a>( method_args: impl Iterator, - is_virtual: bool, param_is_impl_asarg: bool, arg_is_asarg: bool, ) -> [Vec; 3] { @@ -328,7 +330,7 @@ pub(crate) fn make_params_exprs<'a>( object_arg, impl_as_object_arg, .. - } if !is_virtual => { + } => { // Parameter declarations in signature: impl AsObjectArg if param_is_impl_asarg { params.push(quote! { #param_name: #impl_as_object_arg }); @@ -346,8 +348,40 @@ pub(crate) fn make_params_exprs<'a>( param_types.push(quote! { #object_arg }); } + // All other methods and parameter types: standard handling. + _ => { + params.push(quote! { #param_name: #param_ty }); + arg_names.push(quote! { #param_name }); + param_types.push(quote! { #param_ty }); + } + } + } + + [params, param_types, arg_names] +} + +/// For virtual functions, returns the parameter declarations, type tokens, and names. +pub(crate) fn make_params_exprs_virtual<'a>( + method_args: impl Iterator, + function_sig: &dyn Function, +) -> [Vec; 3] { + let mut params = vec![]; + let mut param_types = vec![]; // or non-generic params + let mut arg_names = vec![]; + + for param in method_args { + let param_name = ¶m.name; + let param_ty = ¶m.type_; + + match ¶m.type_ { // Virtual methods accept Option>, since we don't know whether objects are nullable or required. - RustTy::EngineClass { .. } if is_virtual => { + RustTy::EngineClass { .. } + if !special_cases::is_class_method_param_required( + function_sig.surrounding_class().unwrap(), + function_sig.name(), + param_name, + ) => + { params.push(quote! { #param_name: Option<#param_ty> }); arg_names.push(quote! { #param_name }); param_types.push(quote! { #param_ty }); diff --git a/godot-codegen/src/special_cases/special_cases.rs b/godot-codegen/src/special_cases/special_cases.rs index 420f0d9fe..e0e13361e 100644 --- a/godot-codegen/src/special_cases/special_cases.rs +++ b/godot-codegen/src/special_cases/special_cases.rs @@ -28,6 +28,7 @@ use crate::models::domain::{Enum, RustTy, TyName}; use crate::models::json::{JsonBuiltinMethod, JsonClassMethod, JsonUtilityFunction}; use crate::special_cases::codegen_special_cases; use crate::Context; +use proc_macro2::Ident; // Deliberately private -- all checks must go through `special_cases`. #[rustfmt::skip] @@ -269,6 +270,42 @@ pub fn is_class_method_const(class_name: &TyName, godot_method: &JsonClassMethod } } +/// Currently only for virtual methods; checks if the specified parameter is required (non-null) and can be declared as `Gd` +/// instead of `Option>`. +pub fn is_class_method_param_required( + class_name: &TyName, + method_name: &str, + param: &Ident, // Don't use `&str` to avoid to_string() allocations for each check on call-site. +) -> bool { + // Note: magically, it's enough if a base class method is declared here; it will be picked up by derived classes. + + match (class_name.godot_ty.as_str(), method_name) { + // Nodes. + ("Node", "input") => true, + ("Node", "shortcut_input") => true, + ("Node", "unhandled_input") => true, + ("Node", "unhandled_key_input") => true, + + // https://docs.godotengine.org/en/stable/classes/class_collisionobject2d.html#class-collisionobject2d-private-method-input-event + ("CollisionObject2D", "input_event") => true, // both parameters. + + // UI. + ("Control", "gui_input") => true, + + // Script instances. + ("ScriptExtension", "instance_create") => param == "for_object", + ("ScriptExtension", "placeholder_instance_create") => param == "for_object", + ("ScriptExtension", "inherits_script") => param == "script", + ("ScriptExtension", "instance_has") => param == "object", + + // Editor. + ("EditorExportPlugin", "customize_resource") => param == "resource", + ("EditorExportPlugin", "customize_scene") => param == "scene", + + _ => false, + } +} + /// True if builtin method is excluded. Does NOT check for type exclusion; use [`is_builtin_type_deleted`] for that. pub fn is_builtin_method_deleted(_class_name: &TyName, method: &JsonBuiltinMethod) -> bool { // Currently only deleted if codegen.