diff --git a/src/engine_api.rs b/src/engine_api.rs index 4f899fd7..b861c06c 100644 --- a/src/engine_api.rs +++ b/src/engine_api.rs @@ -1650,7 +1650,8 @@ impl Engine { name: &str, args: impl crate::fn_args::FuncArgs, ) -> Result> { - let mut arg_values = args.into_vec(); + let mut arg_values: crate::StaticVec<_> = Default::default(); + args.parse(&mut arg_values); let mut args: crate::StaticVec<_> = arg_values.as_mut().iter_mut().collect(); let result = diff --git a/src/fn_args.rs b/src/fn_args.rs index eeb53a1a..5e9761d8 100644 --- a/src/fn_args.rs +++ b/src/fn_args.rs @@ -3,14 +3,60 @@ #![allow(non_snake_case)] use crate::dynamic::Variant; +use crate::stdlib::vec::Vec; use crate::{Dynamic, StaticVec}; -/// Trait that represents arguments to a function call. -/// Any data type that can be converted into a [`Vec`]`<`[`Dynamic`]`>` can be used -/// as arguments to a function call. +/// Trait that parses arguments to a function call. +/// +/// Any data type can implement this trait in order to pass arguments to a function call. pub trait FuncArgs { - /// Convert to a [`StaticVec`]`<`[`Dynamic`]`>` of the function call arguments. - fn into_vec(self) -> StaticVec; + /// Parse function call arguments into a container. + /// + /// # Example + /// + /// ``` + /// use rhai::{Engine, Dynamic, FuncArgs, Scope}; + /// + /// // A struct containing function arguments + /// struct Options { + /// pub foo: bool, + /// pub bar: String, + /// pub baz: i64, + /// } + /// + /// impl FuncArgs for Options { + /// fn parse>(self, container: &mut C) { + /// container.extend(std::iter::once(self.foo.into())); + /// container.extend(std::iter::once(self.bar.into())); + /// container.extend(std::iter::once(self.baz.into())); + /// } + /// } + /// + /// # fn main() -> Result<(), Box> { + /// let options = Options { foo: false, bar: "world".to_string(), baz: 42 }; + /// + /// let engine = Engine::new(); + /// let mut scope = Scope::new(); + /// + /// let ast = engine.compile(r#" + /// fn hello(x, y, z) { + /// if x { "hello " + y } else { y + z } + /// } + /// "#)?; + /// + /// let result: String = engine.call_fn(&mut scope, &ast, "hello", options)?; + /// + /// assert_eq!(result, "world42"); + /// # Ok(()) + /// # } + /// ``` + fn parse>(self, container: &mut T); +} + +impl FuncArgs for Vec { + fn parse>(self, container: &mut C) { + container.extend(self.into_iter().map(Variant::into_dynamic)); + } } /// Macro to implement [`FuncArgs`] for tuples of standard types (each can be @@ -20,13 +66,13 @@ macro_rules! impl_args { impl<$($p: Variant + Clone),*> FuncArgs for ($($p,)*) { #[inline(always)] - fn into_vec(self) -> StaticVec { + fn parse>(self, container: &mut CONTAINER) { let ($($p,)*) = self; let mut _v = StaticVec::new(); $(_v.push($p.into_dynamic());)* - _v + container.extend(_v.into_iter()); } } diff --git a/src/lib.rs b/src/lib.rs index 8cb99223..b73119c2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -122,6 +122,7 @@ pub type FLOAT = f32; pub use ast::{FnAccess, ScriptFnMetadata, AST}; pub use dynamic::Dynamic; pub use engine::{Engine, EvalContext}; +pub use fn_args::FuncArgs; pub use fn_native::{FnPtr, NativeCallContext, Shared}; pub use fn_register::{RegisterFn, RegisterResultFn}; pub use module::{FnNamespace, Module}; diff --git a/tests/call_fn.rs b/tests/call_fn.rs index c95e2853..552ae48f 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -1,22 +1,6 @@ #![cfg(not(feature = "no_function"))] -use rhai::{Engine, EvalAltResult, FnPtr, Func, ParseErrorType, RegisterFn, Scope, INT}; -use std::any::TypeId; - -#[test] -fn test_fn() -> Result<(), Box> { - let engine = Engine::new(); - - // Expect duplicated parameters error - assert_eq!( - *engine - .compile("fn hello(x, x) { x }") - .expect_err("should be error") - .0, - ParseErrorType::FnDuplicatedParam("hello".to_string(), "x".to_string()) - ); - - Ok(()) -} +use rhai::{Dynamic, Engine, EvalAltResult, FnPtr, Func, FuncArgs, RegisterFn, Scope, INT}; +use std::{any::TypeId, iter::once}; #[test] fn test_call_fn() -> Result<(), Box> { @@ -69,6 +53,46 @@ fn test_call_fn() -> Result<(), Box> { Ok(()) } +struct Options { + pub foo: bool, + pub bar: String, + pub baz: INT, +} + +impl FuncArgs for Options { + fn parse>(self, container: &mut C) { + container.extend(once(self.foo.into())); + container.extend(once(self.bar.into())); + container.extend(once(self.baz.into())); + } +} + +#[test] +fn test_call_fn_args() -> Result<(), Box> { + let options = Options { + foo: false, + bar: "world".to_string(), + baz: 42, + }; + + let engine = Engine::new(); + let mut scope = Scope::new(); + + let ast = engine.compile( + r#" + fn hello(x, y, z) { + if x { "hello " + y } else { y + z } + } + "#, + )?; + + let result: String = engine.call_fn(&mut scope, &ast, "hello", options)?; + + assert_eq!(result, "world42"); + + Ok(()) +} + #[test] fn test_call_fn_private() -> Result<(), Box> { let engine = Engine::new(); diff --git a/tests/functions.rs b/tests/functions.rs index 68b8a214..f5624fbf 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -51,6 +51,22 @@ fn test_functions() -> Result<(), Box> { Ok(()) } +#[test] +fn test_functions_params() -> Result<(), Box> { + let engine = Engine::new(); + + // Expect duplicated parameters error + assert_eq!( + *engine + .compile("fn hello(x, x) { x }") + .expect_err("should be error") + .0, + ParseErrorType::FnDuplicatedParam("hello".to_string(), "x".to_string()) + ); + + Ok(()) +} + #[cfg(not(feature = "no_function"))] #[test] fn test_functions_namespaces() -> Result<(), Box> {