Skip to content

Implements Defaut parameters #1213

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion godot-core/src/meta/error/call_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,11 @@ impl CallError {
pub(crate) fn check_arg_count(
call_ctx: &CallContext,
arg_count: usize,
default_args_count: usize,
param_count: usize,
) -> Result<(), Self> {
// This will need to be adjusted once optional parameters are supported in #[func].
if arg_count == param_count {
if arg_count + default_args_count >= param_count {
return Ok(());
}

Expand Down Expand Up @@ -221,6 +222,7 @@ impl CallError {
)
}

// should this be adjusted with default param counts?
fn failed_param_count(
call_ctx: &CallContext,
arg_count: usize,
Expand Down
3 changes: 2 additions & 1 deletion godot-core/src/meta/signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,13 @@ impl<Params: InParamTuple, Ret: ToGodot> Signature<Params, Ret> {
call_ctx: &CallContext,
args_ptr: *const sys::GDExtensionConstVariantPtr,
arg_count: i64,
default_arg_count: usize,
ret: sys::GDExtensionVariantPtr,
err: *mut sys::GDExtensionCallError,
func: unsafe fn(sys::GDExtensionClassInstancePtr, Params) -> Ret,
) -> CallResult<()> {
//$crate::out!("in_varcall: {call_ctx}");
CallError::check_arg_count(call_ctx, arg_count as usize, Params::LEN)?;
CallError::check_arg_count(call_ctx, arg_count as usize, default_arg_count, Params::LEN)?;

#[cfg(feature = "trace")]
trace::push(true, false, call_ctx);
Expand Down
3 changes: 1 addition & 2 deletions godot-core/src/registry/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,11 @@ impl ClassMethodInfo {
ptrcall_func: sys::GDExtensionClassMethodPtrCall,
method_flags: MethodFlags,
param_names: &[&str],
// default_arguments: Vec<Variant>, - not yet implemented
default_arguments: Vec<Variant>,
) -> Self {
let return_value = Ret::Via::return_info();
let arguments = Signature::<Params, Ret>::param_names(param_names);

let default_arguments = vec![]; // not yet implemented.
assert!(
default_arguments.len() <= arguments.len(),
"cannot have more default arguments than arguments"
Expand Down
77 changes: 72 additions & 5 deletions godot-macros/src/class/data_models/func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

use crate::class::RpcAttr;
use crate::util::{bail_fn, ident, safe_ident};
use crate::{util, ParseResult};
use crate::{bail, util, ParseResult};
use proc_macro2::{Group, Ident, TokenStream, TokenTree};
use quote::{format_ident, quote};

Expand Down Expand Up @@ -112,12 +112,15 @@ pub fn make_method_registration(
interface_trait,
);

let (default_parameters, default_parameters_count) =
make_default_parameters(&func_definition, signature_info)?;

// String literals
let class_name_str = class_name.to_string();
let method_name_str = func_definition.godot_name();

let call_ctx = make_call_context(&class_name_str, &method_name_str);
let varcall_fn_decl = make_varcall_fn(&call_ctx, &forwarding_closure);
let varcall_fn_decl = make_varcall_fn(&call_ctx, &forwarding_closure, default_parameters_count);
let ptrcall_fn_decl = make_ptrcall_fn(&call_ctx, &forwarding_closure);

// String literals II
Expand All @@ -126,6 +129,8 @@ pub fn make_method_registration(
.iter()
.map(|ident| ident.to_string());

// #(::godot::builtin::Variant::from(#default_parameters)),*

// Transport #[cfg] attrs to the FFI glue to ensure functions which were conditionally
// removed from compilation don't cause errors.
let cfg_attrs = util::extract_cfg_attrs(&func_definition.external_attributes)
Expand Down Expand Up @@ -158,6 +163,7 @@ pub fn make_method_registration(
&[
#( #param_ident_strs ),*
],
#default_parameters,
)
};

Expand All @@ -175,6 +181,54 @@ pub fn make_method_registration(
Ok(registration)
}

fn make_default_parameters(
func_definition: &FuncDefinition,
signature_info: &SignatureInfo,
) -> Result<(TokenStream, usize), venial::Error> {
let default_parameters =
validate_default_parameters(&func_definition.signature_info.default_parameters)?;
let len = default_parameters.len();
let default_parameters_type = signature_info
.param_types
.iter()
.rev()
.take(default_parameters.len())
.rev();
let default_parameters = default_parameters
.iter()
.zip(default_parameters_type)
.map(|(value, ty)| quote!(::godot::builtin::Variant::from(#value)));
// .map(|(value, ty)| quote!(::godot::meta::arg_into_ref!(#value: #ty)));
let default_parameters = quote! {vec![#(#default_parameters),*]};
Ok((default_parameters, len))
}

fn validate_default_parameters(
default_parameters: &[Option<TokenStream>],
) -> ParseResult<Vec<TokenStream>> {
let mut res = vec![];
let mut allowed = true;
for param in default_parameters.iter().rev() {
match (param, allowed) {
(Some(tk), true) => {
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
}
(None, true) => {
allowed = false;
}
(None, false) => {}
(Some(tk), false) => {
return bail!(
tk,
"opt arguments are only allowed at the end of the argument list."
);
}
}
}
res.reverse();
Ok(res)
}

// ----------------------------------------------------------------------------------------------------------------------------------------------
// Implementation

Expand All @@ -199,6 +253,8 @@ pub struct SignatureInfo {
///
/// Index points into original venial tokens (i.e. takes into account potential receiver params).
pub modified_param_types: Vec<(usize, venial::TypeExpr)>,
/// Contains expressions of the default values of parameters.
pub default_parameters: Vec<Option<TokenStream>>,
}

impl SignatureInfo {
Expand All @@ -210,6 +266,7 @@ impl SignatureInfo {
param_types: vec![],
return_type: quote! { () },
modified_param_types: vec![],
default_parameters: vec![],
}
}

Expand Down Expand Up @@ -412,6 +469,7 @@ pub(crate) fn into_signature_info(
param_types,
return_type: ret_type,
modified_param_types,
default_parameters: vec![],
}
}

Expand Down Expand Up @@ -489,8 +547,12 @@ fn make_method_flags(
}

/// Generate code for a C FFI function that performs a varcall.
fn make_varcall_fn(call_ctx: &TokenStream, wrapped_method: &TokenStream) -> TokenStream {
let invocation = make_varcall_invocation(wrapped_method);
fn make_varcall_fn(
call_ctx: &TokenStream,
wrapped_method: &TokenStream,
default_parameters_count: usize,
) -> TokenStream {
let invocation = make_varcall_invocation(wrapped_method, default_parameters_count);

// TODO reduce amount of code generated, by delegating work to a library function. Could even be one that produces this function pointer.
quote! {
Expand Down Expand Up @@ -557,13 +619,18 @@ fn make_ptrcall_invocation(wrapped_method: &TokenStream, is_virtual: bool) -> To
}

/// Generate code for a `varcall()` call expression.
fn make_varcall_invocation(wrapped_method: &TokenStream) -> TokenStream {
fn make_varcall_invocation(
wrapped_method: &TokenStream,
default_parameters_count: usize,
) -> TokenStream {
// to adjust with use of default parameters
quote! {
::godot::meta::Signature::<CallParams, CallRet>::in_varcall(
instance_ptr,
&call_ctx,
args_ptr,
arg_count,
#default_parameters_count,
ret,
err,
#wrapped_method,
Expand Down
21 changes: 20 additions & 1 deletion godot-macros/src/class/data_models/inherent_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,9 +304,10 @@ fn process_godot_fns(
};

// Clone might not strictly be necessary, but the 2 other callers of into_signature_info() are better off with pass-by-value.
let signature_info =
let mut signature_info =
into_signature_info(signature.clone(), class_name, gd_self_parameter.is_some());

signature_info.default_parameters = parse_default_parameters(&mut function.params)?;
// For virtual methods, rename/mangle existing user method and create a new method with the original name,
// which performs a dynamic dispatch.
let registered_name = if func.is_virtual {
Expand Down Expand Up @@ -688,6 +689,24 @@ fn parse_constant_attr(
Ok(AttrParseResult::Constant(attr.value.clone()))
}

fn parse_default_parameters(
params: &mut venial::Punctuated<venial::FnParam>,
) -> ParseResult<Vec<Option<TokenStream>>> {
let mut res = vec![];
for param in params.iter_mut() {
let typed_param = match &mut param.0 {
venial::FnParam::Receiver(_) => continue,
venial::FnParam::Typed(fn_typed_param) => fn_typed_param,
};
let default = match KvParser::parse_remove(&mut typed_param.attributes, "opt")? {
None => None,
Some(mut parser) => Some(parser.handle_expr_required("default")?),
};
res.push(default);
}
Ok(res)
}

fn bail_attr<R>(attr_name: &Ident, msg: &str, method_name: &Ident) -> ParseResult<R> {
bail!(method_name, "#[{attr_name}]: {msg}")
}
Expand Down
27 changes: 27 additions & 0 deletions itest/rust/src/register_tests/default_parameters_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use crate::framework::itest;
use godot::prelude::*;

#[derive(GodotClass)]
#[class(init)]
struct HasDefaultParameters {}

#[godot_api]
impl HasDefaultParameters {
#[func]
fn function_with_default_params(
required: i32,
#[opt(default = "test")] string: GString,
#[opt(default = 123)] integer: i32,
// #[opt(default=None)] object: Option<Gd<Node>>,
) -> VariantArray {
varray![required, string, integer,]
}
}

#[itest(focus)]
fn tests_default_parameters() {
let mut obj = HasDefaultParameters::new_gd();
let r = obj.call("function_with_default_params", &[0.to_variant()]);
let r = r.to::<VariantArray>();
assert_eq!(r, varray![0, "test", 123]);
}
1 change: 1 addition & 0 deletions itest/rust/src/register_tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

mod constant_test;
mod conversion_test;
mod default_parameters_test;
mod derive_godotconvert_test;
mod func_test;
mod gdscript_ffi_test;
Expand Down
Loading