Skip to content

Commit e217531

Browse files
committed
Special cases for nullability of virtual function parameters
1 parent 3b36394 commit e217531

File tree

3 files changed

+86
-13
lines changed

3 files changed

+86
-13
lines changed

godot-codegen/src/generator/default_parameters.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,10 @@ pub fn make_function_definition_with_defaults(
5050
let receiver_self = &code.receiver.self_prefix;
5151

5252
let [required_params_impl_asarg, _, _] =
53-
functions_common::make_params_exprs(required_fn_params.iter().cloned(), false, true, true);
53+
functions_common::make_params_exprs(required_fn_params.iter().cloned(), true, true);
5454

5555
let [_, _, required_args_internal] =
56-
functions_common::make_params_exprs(required_fn_params.into_iter(), false, false, false);
56+
functions_common::make_params_exprs(required_fn_params.into_iter(), false, false);
5757

5858
let return_decl = &sig.return_value().decl;
5959

godot-codegen/src/generator/functions_common.rs

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
use crate::generator::default_parameters;
99
use crate::models::domain::{FnParam, FnQualifier, Function, RustTy};
10+
use crate::special_cases;
1011
use crate::util::safe_ident;
1112
use proc_macro2::TokenStream;
1213
use quote::{format_ident, quote};
@@ -119,12 +120,15 @@ pub fn make_function_definition(
119120
(TokenStream::new(), TokenStream::new())
120121
};
121122

122-
let [params, param_types, arg_names] = make_params_exprs(
123-
sig.params().iter(),
124-
sig.is_virtual(),
125-
!has_default_params, // For *_full function, we don't need impl AsObjectArg<T> parameters
126-
!has_default_params, // or arg.as_object_arg() calls.
127-
);
123+
let [params, param_types, arg_names] = if sig.is_virtual() {
124+
make_params_exprs_virtual(sig.params().iter(), sig)
125+
} else {
126+
make_params_exprs(
127+
sig.params().iter(),
128+
!has_default_params, // For *_full function, we don't need impl AsObjectArg<T> parameters
129+
!has_default_params, // or arg.as_object_arg() calls.
130+
)
131+
};
128132

129133
let rust_function_name_str = sig.name();
130134

@@ -202,7 +206,6 @@ pub fn make_function_definition(
202206
// A function() may call try_function(), its arguments should not have .as_object_arg().
203207
let [_, _, arg_names_without_asarg] = make_params_exprs(
204208
sig.params().iter(),
205-
false,
206209
!has_default_params, // For *_full function, we don't need impl AsObjectArg<T> parameters
207210
false, // or arg.as_object_arg() calls.
208211
);
@@ -307,10 +310,9 @@ pub fn make_vis(is_private: bool) -> TokenStream {
307310
// ----------------------------------------------------------------------------------------------------------------------------------------------
308311
// Implementation
309312

310-
// Method could possibly be split -- only one invocation uses all 3 return values, the rest uses only index [0] or [2].
313+
/// For non-virtual functions, returns the parameter declarations, type tokens, and names.
311314
pub(crate) fn make_params_exprs<'a>(
312315
method_args: impl Iterator<Item = &'a FnParam>,
313-
is_virtual: bool,
314316
param_is_impl_asarg: bool,
315317
arg_is_asarg: bool,
316318
) -> [Vec<TokenStream>; 3] {
@@ -328,7 +330,7 @@ pub(crate) fn make_params_exprs<'a>(
328330
object_arg,
329331
impl_as_object_arg,
330332
..
331-
} if !is_virtual => {
333+
} => {
332334
// Parameter declarations in signature: impl AsObjectArg<T>
333335
if param_is_impl_asarg {
334336
params.push(quote! { #param_name: #impl_as_object_arg });
@@ -346,8 +348,40 @@ pub(crate) fn make_params_exprs<'a>(
346348
param_types.push(quote! { #object_arg });
347349
}
348350

351+
// All other methods and parameter types: standard handling.
352+
_ => {
353+
params.push(quote! { #param_name: #param_ty });
354+
arg_names.push(quote! { #param_name });
355+
param_types.push(quote! { #param_ty });
356+
}
357+
}
358+
}
359+
360+
[params, param_types, arg_names]
361+
}
362+
363+
/// For virtual functions, returns the parameter declarations, type tokens, and names.
364+
pub(crate) fn make_params_exprs_virtual<'a>(
365+
method_args: impl Iterator<Item = &'a FnParam>,
366+
function_sig: &dyn Function,
367+
) -> [Vec<TokenStream>; 3] {
368+
let mut params = vec![];
369+
let mut param_types = vec![]; // or non-generic params
370+
let mut arg_names = vec![];
371+
372+
for param in method_args {
373+
let param_name = &param.name;
374+
let param_ty = &param.type_;
375+
376+
match &param.type_ {
349377
// Virtual methods accept Option<Gd<T>>, since we don't know whether objects are nullable or required.
350-
RustTy::EngineClass { .. } if is_virtual => {
378+
RustTy::EngineClass { .. }
379+
if !special_cases::is_class_method_param_required(
380+
function_sig.surrounding_class().unwrap(),
381+
function_sig.name(),
382+
param_name,
383+
) =>
384+
{
351385
params.push(quote! { #param_name: Option<#param_ty> });
352386
arg_names.push(quote! { #param_name });
353387
param_types.push(quote! { #param_ty });

godot-codegen/src/special_cases/special_cases.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ use crate::models::domain::{Enum, RustTy, TyName};
2828
use crate::models::json::{JsonBuiltinMethod, JsonClassMethod, JsonUtilityFunction};
2929
use crate::special_cases::codegen_special_cases;
3030
use crate::Context;
31+
use proc_macro2::Ident;
3132
// Deliberately private -- all checks must go through `special_cases`.
3233

3334
#[rustfmt::skip]
@@ -269,6 +270,44 @@ pub fn is_class_method_const(class_name: &TyName, godot_method: &JsonClassMethod
269270
}
270271
}
271272

273+
/// Currently only for virtual methods; checks if the specified parameter is required (non-null) and can be declared as `Gd<T>`
274+
/// instead of `Option<Gd<T>>`.
275+
pub fn is_class_method_param_required(
276+
class_name: &TyName,
277+
method_name: &str,
278+
param: &Ident, // Don't use `&str` to avoid to_string() allocations for each check on call-site.
279+
) -> bool {
280+
// Note: magically, it's enough if a base class method is declared here; it will be picked up by derived classes.
281+
282+
match (class_name.godot_ty.as_str(), method_name) {
283+
// Nodes.
284+
("Node", "input") => true,
285+
("Node", "shortcut_input") => true,
286+
("Node", "unhandled_input") => true,
287+
("Node", "unhandled_key_input") => true,
288+
289+
// https://docs.godotengine.org/en/stable/classes/class_collisionobject2d.html#class-collisionobject2d-private-method-input-event
290+
("CollisionObject2D", "input_event") => true, // both parameters.
291+
292+
// UI.
293+
("Control", "gui_input") => true,
294+
295+
// Script instances.
296+
("ScriptExtension", "instance_create") => param == "for_object",
297+
("ScriptExtension", "placeholder_instance_create") => param == "for_object",
298+
("ScriptExtension", "inherits_script") => param == "script",
299+
("ScriptExtension", "instance_has") => param == "object",
300+
301+
// Editor.
302+
("EditorExportPlugin", "customize_resource") => param == "resource",
303+
("EditorExportPlugin", "customize_scene") => param == "scene",
304+
305+
("EditorPlugin", "handles") => param == "object",
306+
307+
_ => false,
308+
}
309+
}
310+
272311
/// True if builtin method is excluded. Does NOT check for type exclusion; use [`is_builtin_type_deleted`] for that.
273312
pub fn is_builtin_method_deleted(_class_name: &TyName, method: &JsonBuiltinMethod) -> bool {
274313
// Currently only deleted if codegen.

0 commit comments

Comments
 (0)