Skip to content

Commit

Permalink
Special cases for nullability of virtual function parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
Bromeon committed Sep 2, 2024
1 parent 68002b6 commit f5865cb
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 13 deletions.
4 changes: 2 additions & 2 deletions godot-codegen/src/generator/default_parameters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
56 changes: 45 additions & 11 deletions godot-codegen/src/generator/functions_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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<T> 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<T> parameters
!has_default_params, // or arg.as_object_arg() calls.
)
};

let rust_function_name_str = sig.name();

Expand Down Expand Up @@ -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<T> parameters
false, // or arg.as_object_arg() calls.
);
Expand Down Expand Up @@ -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<Item = &'a FnParam>,
is_virtual: bool,
param_is_impl_asarg: bool,
arg_is_asarg: bool,
) -> [Vec<TokenStream>; 3] {
Expand All @@ -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<T>
if param_is_impl_asarg {
params.push(quote! { #param_name: #impl_as_object_arg });
Expand All @@ -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<Item = &'a FnParam>,
function_sig: &dyn Function,
) -> [Vec<TokenStream>; 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 = &param.name;
let param_ty = &param.type_;

match &param.type_ {
// Virtual methods accept Option<Gd<T>>, 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 });
Expand Down
37 changes: 37 additions & 0 deletions godot-codegen/src/special_cases/special_cases.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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<T>`
/// instead of `Option<Gd<T>>`.
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.
Expand Down

0 comments on commit f5865cb

Please sign in to comment.