diff --git a/CHANGELOG.md b/CHANGELOG.md index a615b018..dc15be12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,17 +4,27 @@ Rhai Release Notes Version 1.12.0 ============== +Net features +------------ + +### `Engine::call_fn_with_options` + +* `Engine::call_fn_raw` is deprecated in favor of `Engine::call_fn_with_options` which allows setting options for the function call. +* The options are for future-proofing the API. +* In this version, it gains the ability to set the value of the _custom state_ (accessible via `NativeCallContext::tag`) for a function evaluation, overriding `Engine::set_default_tag`. + Enhancements ------------ * `CallableFunction` is exported under `internals`. * The `TypeBuilder` type and `CustomType` trait are no longer marked as volatile. +* `FuncArgs` is also implemented for arrays. Version 1.11.0 ============== -Speed Improvements +Speed improvements ------------------ * Due to a code refactor, built-in operators for standard types now run even faster, in certain cases by 20-30%. diff --git a/examples/event_handler_js/main.rs b/examples/event_handler_js/main.rs index 22c128c3..f2aac172 100644 --- a/examples/event_handler_js/main.rs +++ b/examples/event_handler_js/main.rs @@ -8,7 +8,7 @@ pub fn main() { #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_object"))] pub fn main() { - use rhai::{Dynamic, Engine, Map, Scope, AST}; + use rhai::{CallFnOptions, Dynamic, Engine, ImmutableString, Map, Scope, AST}; use std::io::{stdin, stdout, Write}; const SCRIPT_FILE: &str = "event_handler_js/script.rhai"; @@ -98,7 +98,12 @@ pub fn main() { println!(); // Run the 'init' function to initialize the state, retaining variables. - let result = engine.call_fn_raw(&mut scope, &ast, false, true, "init", Some(&mut states), []); + + let options = CallFnOptions::new() + .eval_ast(false) + .bind_this_ptr(&mut states); + + let result = engine.call_fn_with_options::<()>(options, &mut scope, &ast, "init", ()); if let Err(err) = result { eprintln!("! {err}") @@ -124,7 +129,7 @@ pub fn main() { let mut fields = input.trim().splitn(2, ' '); let event = fields.next().expect("event").trim(); - let arg = fields.next().unwrap_or(""); + let arg = fields.next().unwrap_or("").to_string(); // Process event match event { @@ -146,10 +151,11 @@ pub fn main() { let engine = &handler.engine; let scope = &mut handler.scope; let ast = &handler.ast; - let this_ptr = Some(&mut handler.states); + let options = CallFnOptions::new() + .eval_ast(false) + .bind_this_ptr(&mut handler.states); - let result = - engine.call_fn_raw(scope, ast, false, true, event, this_ptr, [arg.into()]); + let result = engine.call_fn_with_options::<()>(options, scope, ast, event, (arg,)); if let Err(err) = result { eprintln!("! {err}") diff --git a/examples/event_handler_main/main.rs b/examples/event_handler_main/main.rs index b5b46577..8fbfdafd 100644 --- a/examples/event_handler_main/main.rs +++ b/examples/event_handler_main/main.rs @@ -7,7 +7,7 @@ pub fn main() { #[cfg(not(feature = "no_function"))] pub fn main() { - use rhai::{Dynamic, Engine, Scope, AST}; + use rhai::{CallFnOptions, Dynamic, Engine, Scope, AST}; use std::io::{stdin, stdout, Write}; const SCRIPT_FILE: &str = "event_handler_main/script.rhai"; @@ -86,7 +86,9 @@ pub fn main() { println!(); // Run the 'init' function to initialize the state, retaining variables. - let result = engine.call_fn_raw(&mut scope, &ast, false, false, "init", None, []); + let options = CallFnOptions::new().eval_ast(false).rewind_scope(false); + + let result = engine.call_fn_with_options::<()>(options, &mut scope, &ast, "init", ()); if let Err(err) = result { eprintln!("! {err}") @@ -107,7 +109,7 @@ pub fn main() { let mut fields = input.trim().splitn(2, ' '); let event = fields.next().expect("event").trim(); - let arg = fields.next().unwrap_or(""); + let arg = fields.next().unwrap_or("").to_string(); // Process event match event { @@ -124,7 +126,7 @@ pub fn main() { let scope = &mut handler.scope; let ast = &handler.ast; - let result = engine.call_fn::<()>(scope, ast, event, (arg.to_string(),)); + let result = engine.call_fn::<()>(scope, ast, event, (arg,)); if let Err(err) = result { eprintln!("! {err}") diff --git a/examples/event_handler_map/main.rs b/examples/event_handler_map/main.rs index b41795c4..b92dba94 100644 --- a/examples/event_handler_map/main.rs +++ b/examples/event_handler_map/main.rs @@ -121,7 +121,7 @@ pub fn main() { let mut fields = input.trim().splitn(2, ' '); let event = fields.next().expect("event").trim(); - let arg = fields.next().unwrap_or(""); + let arg = fields.next().unwrap_or("").to_string(); // Process event match event { @@ -138,7 +138,7 @@ pub fn main() { let scope = &mut handler.scope; let ast = &handler.ast; - let result = engine.call_fn::<()>(scope, ast, event, (arg.to_string(),)); + let result = engine.call_fn::<()>(scope, ast, event, (arg,)); if let Err(err) = result { eprintln!("! {err}") diff --git a/src/api/call_fn.rs b/src/api/call_fn.rs index 335be57d..0d0d5edf 100644 --- a/src/api/call_fn.rs +++ b/src/api/call_fn.rs @@ -8,9 +8,70 @@ use crate::{ reify, Dynamic, Engine, FuncArgs, Position, RhaiResult, RhaiResultOf, Scope, StaticVec, AST, ERR, }; -use std::any::{type_name, TypeId}; #[cfg(feature = "no_std")] use std::prelude::v1::*; +use std::{ + any::{type_name, TypeId}, + mem, +}; + +/// Options for calling a script-defined function via [`Engine::call_fn_with_options`]. +#[derive(Debug, Hash)] +#[non_exhaustive] +pub struct CallFnOptions<'t> { + /// A value for binding to the `this` pointer (if any). + pub this_ptr: Option<&'t mut Dynamic>, + /// The custom state of this evaluation run (if any), overrides [`Engine::default_tag`]. + pub tag: Option, + /// Evaluate the [`AST`] to load necessary modules before calling the function? Default `true`. + pub eval_ast: bool, + /// Rewind the [`Scope`] after the function call? Default `true`. + pub rewind_scope: bool, +} + +impl Default for CallFnOptions<'_> { + #[inline(always)] + fn default() -> Self { + Self::new() + } +} + +impl<'a> CallFnOptions<'a> { + /// Create a default [`CallFnOptions`]. + #[inline(always)] + pub fn new() -> Self { + Self { + this_ptr: None, + tag: None, + eval_ast: true, + rewind_scope: true, + } + } + /// Bind to the `this` pointer. + #[inline(always)] + pub fn bind_this_ptr(mut self, value: &'a mut Dynamic) -> Self { + self.this_ptr = Some(value); + self + } + /// Set the custom state of this evaluation run (if any). + #[inline(always)] + pub fn with_tag(mut self, value: impl Variant + Clone) -> Self { + self.tag = Some(Dynamic::from(value)); + self + } + /// Set whether to evaluate the [`AST`] to load necessary modules before calling the function. + #[inline(always)] + pub const fn eval_ast(mut self, value: bool) -> Self { + self.eval_ast = value; + self + } + /// Set whether to rewind the [`Scope`] after the function call. + #[inline(always)] + pub const fn rewind_scope(mut self, value: bool) -> Self { + self.rewind_scope = value; + self + } +} impl Engine { /// Call a script function defined in an [`AST`] with multiple arguments. @@ -19,15 +80,12 @@ impl Engine { /// /// The [`AST`] is evaluated before calling the function. /// This allows a script to load the necessary modules. - /// This is usually desired. If not, a specialized [`AST`] can be prepared that contains only - /// function definitions without any body script via [`AST::clear_statements`]. + /// This is usually desired. If not, use [`call_fn_with_options`] instead. /// /// # Example /// /// ``` /// # fn main() -> Result<(), Box> { - /// # #[cfg(not(feature = "no_function"))] - /// # { /// use rhai::{Engine, Scope}; /// /// let engine = Engine::new(); @@ -51,30 +109,83 @@ impl Engine { /// /// let result = engine.call_fn::(&mut scope, &ast, "bar", () )?; /// assert_eq!(result, 21); - /// # } /// # Ok(()) /// # } /// ``` - #[inline] + #[inline(always)] pub fn call_fn( &self, scope: &mut Scope, ast: &AST, name: impl AsRef, args: impl FuncArgs, + ) -> RhaiResultOf { + self.call_fn_with_options(Default::default(), scope, ast, name, args) + } + /// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments. + /// + /// Options are provided via the [`CallFnOptions`] type. + /// This is an advanced API. + /// + /// Not available under `no_function`. + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use rhai::{Engine, Scope, Dynamic, CallFnOptions}; + /// + /// let engine = Engine::new(); + /// + /// let ast = engine.compile(" + /// fn action(x) { this += x; } // function using 'this' pointer + /// fn decl(x) { let hello = x; } // declaring variables + /// ")?; + /// + /// let mut scope = Scope::new(); + /// scope.push("foo", 42_i64); + /// + /// // Binding the 'this' pointer + /// let mut value = 1_i64.into(); + /// let options = CallFnOptions::new().bind_this_ptr(&mut value); + /// + /// engine.call_fn_with_options(options, &mut scope, &ast, "action", ( 41_i64, ))?; + /// assert_eq!(value.as_int().unwrap(), 42); + /// + /// // Do not rewind scope + /// let options = CallFnOptions::default().rewind_scope(false); + /// + /// engine.call_fn_with_options(options, &mut scope, &ast, "decl", ( 42_i64, ))?; + /// assert_eq!(scope.get_value::("hello").unwrap(), 42); + /// # Ok(()) + /// # } + /// ``` + #[inline(always)] + pub fn call_fn_with_options( + &self, + options: CallFnOptions, + scope: &mut Scope, + ast: &AST, + name: impl AsRef, + args: impl FuncArgs, ) -> RhaiResultOf { let mut arg_values = StaticVec::new_const(); args.parse(&mut arg_values); - let result = self.call_fn_raw(scope, ast, true, true, name, None, arg_values)?; + let result = self._call_fn( + options, + scope, + &mut GlobalRuntimeState::new(self), + &mut Caches::new(), + ast, + name.as_ref(), + arg_values.as_mut(), + )?; // Bail out early if the return type needs no cast if TypeId::of::() == TypeId::of::() { return Ok(reify!(result => T)); } - if TypeId::of::() == TypeId::of::<()>() { - return Ok(reify!(() => T)); - } // Cast return type let typ = self.map_type_name(result.type_name()); @@ -86,120 +197,6 @@ impl Engine { } /// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments. /// - /// The following options are available: - /// - /// * whether to evaluate the [`AST`] to load necessary modules before calling the function - /// * whether to rewind the [`Scope`] after the function call - /// * a value for binding to the `this` pointer (if any) - /// - /// Not available under `no_function`. - /// - /// # WARNING - Low Level API - /// - /// This function is very low level. - /// - /// # Arguments - /// - /// All the arguments are _consumed_, meaning that they're replaced by `()`. - /// This is to avoid unnecessarily cloning the arguments. - /// - /// Do not use the arguments after this call. If they are needed afterwards, clone them _before_ - /// calling this function. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// # #[cfg(not(feature = "no_function"))] - /// # { - /// use rhai::{Engine, Scope, Dynamic}; - /// - /// let engine = Engine::new(); - /// - /// let ast = engine.compile(" - /// fn add(x, y) { len(x) + y + foo } - /// fn add1(x) { len(x) + 1 + foo } - /// fn bar() { foo/2 } - /// fn action(x) { this += x; } // function using 'this' pointer - /// fn decl(x) { let hello = x; } // declaring variables - /// ")?; - /// - /// let mut scope = Scope::new(); - /// scope.push("foo", 42_i64); - /// - /// // Call the script-defined function - /// let result = engine.call_fn_raw(&mut scope, &ast, true, true, "add", None, [ "abc".into(), 123_i64.into() ])?; - /// // ^^^^ no 'this' pointer - /// assert_eq!(result.cast::(), 168); - /// - /// let result = engine.call_fn_raw(&mut scope, &ast, true, true, "add1", None, [ "abc".into() ])?; - /// assert_eq!(result.cast::(), 46); - /// - /// let result = engine.call_fn_raw(&mut scope, &ast, true, true, "bar", None, [])?; - /// assert_eq!(result.cast::(), 21); - /// - /// let mut value = 1_i64.into(); - /// let result = engine.call_fn_raw(&mut scope, &ast, true, true, "action", Some(&mut value), [ 41_i64.into() ])?; - /// // ^^^^^^^^^^^^^^^^ binding the 'this' pointer - /// assert_eq!(value.as_int().unwrap(), 42); - /// - /// engine.call_fn_raw(&mut scope, &ast, true, false, "decl", None, [ 42_i64.into() ])?; - /// // ^^^^^ do not rewind scope - /// assert_eq!(scope.get_value::("hello").unwrap(), 42); - /// # } - /// # Ok(()) - /// # } - /// ``` - #[inline(always)] - pub fn call_fn_raw( - &self, - scope: &mut Scope, - ast: &AST, - eval_ast: bool, - rewind_scope: bool, - name: impl AsRef, - this_ptr: Option<&mut Dynamic>, - arg_values: impl AsMut<[Dynamic]>, - ) -> RhaiResult { - let mut arg_values = arg_values; - - self._call_fn( - scope, - &mut GlobalRuntimeState::new(self), - &mut Caches::new(), - ast, - eval_ast, - rewind_scope, - name.as_ref(), - this_ptr, - arg_values.as_mut(), - ) - } - /// _(internals)_ Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments. - /// Exported under the `internals` feature only. - /// - /// The following options are available: - /// - /// * whether to evaluate the [`AST`] to load necessary modules before calling the function - /// * whether to rewind the [`Scope`] after the function call - /// * a value for binding to the `this` pointer (if any) - /// - /// Not available under `no_function`. - /// - /// # WARNING - Unstable API - /// - /// This API is volatile and may change in the future. - /// - /// # WARNING - Low Level API - /// - /// This function is _extremely_ low level. - /// - /// A [`GlobalRuntimeState`] and [`Caches`] need to be passed into the function, which can be - /// created via [`GlobalRuntimeState::new`] and [`Caches::new`]. - /// - /// This makes repeatedly calling particular functions more efficient as the functions - /// resolution cache is kept intact. - /// /// # Arguments /// /// All the arguments are _consumed_, meaning that they're replaced by `()`. This is to avoid @@ -207,56 +204,31 @@ impl Engine { /// /// Do not use the arguments after this call. If they are needed afterwards, clone them _before_ /// calling this function. - #[cfg(feature = "internals")] - #[deprecated = "This API is NOT deprecated, but it is considered volatile and may change in the future."] #[inline(always)] - pub fn call_fn_raw_raw( + pub(crate) fn _call_fn( &self, + options: CallFnOptions, scope: &mut Scope, global: &mut GlobalRuntimeState, caches: &mut Caches, ast: &AST, - eval_ast: bool, - rewind_scope: bool, name: &str, - this_ptr: Option<&mut Dynamic>, - arg_values: &mut [Dynamic], - ) -> RhaiResult { - self._call_fn( - scope, - global, - caches, - ast, - eval_ast, - rewind_scope, - name, - this_ptr, - arg_values, - ) - } - - /// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments. - fn _call_fn( - &self, - scope: &mut Scope, - global: &mut GlobalRuntimeState, - caches: &mut Caches, - ast: &AST, - eval_ast: bool, - rewind_scope: bool, - name: &str, - this_ptr: Option<&mut Dynamic>, arg_values: &mut [Dynamic], ) -> RhaiResult { let statements = ast.statements(); let orig_lib_len = global.lib.len(); - #[cfg(not(feature = "no_function"))] + let mut orig_tag = None; + + if let Some(value) = options.tag { + orig_tag = Some(mem::replace(&mut global.tag, value)); + } + global.lib.push(ast.shared_lib().clone()); let mut no_this_ptr = Dynamic::NULL; - let this_ptr = this_ptr.unwrap_or(&mut no_this_ptr); + let this_ptr = options.this_ptr.unwrap_or(&mut no_this_ptr); #[cfg(not(feature = "no_module"))] let orig_embedded_module_resolver = std::mem::replace( @@ -264,7 +236,9 @@ impl Engine { ast.resolver().cloned(), ); - let result = if eval_ast && !statements.is_empty() { + let rewind_scope = options.rewind_scope; + + let result = if options.eval_ast && !statements.is_empty() { let orig_scope_len = scope.len(); let scope = &mut *RestoreOnDrop::lock_if(rewind_scope, scope, move |s| { s.rewind(orig_scope_len); @@ -308,6 +282,11 @@ impl Engine { { global.embedded_module_resolver = orig_embedded_module_resolver; } + + if let Some(value) = orig_tag { + global.tag = value; + } + global.lib.truncate(orig_lib_len); result diff --git a/src/api/deprecated.rs b/src/api/deprecated.rs index cda495db..b1bb63a0 100644 --- a/src/api/deprecated.rs +++ b/src/api/deprecated.rs @@ -117,10 +117,10 @@ impl Engine { /// /// # Deprecated /// - /// This method is deprecated. Use [`run_ast_with_scope`][Engine::run_ast_with_scope] instead. + /// This method is deprecated. Use [`call_fn_with_options`][Engine::call_fn_with_options] instead. /// /// This method will be removed in the next major version. - #[deprecated(since = "1.1.0", note = "use `call_fn_raw` instead")] + #[deprecated(since = "1.1.0", note = "use `call_fn_with_options` instead")] #[cfg(not(feature = "no_function"))] #[inline(always)] pub fn call_fn_dynamic( @@ -132,8 +132,56 @@ impl Engine { this_ptr: Option<&mut Dynamic>, arg_values: impl AsMut<[Dynamic]>, ) -> RhaiResult { + #[allow(deprecated)] self.call_fn_raw(scope, ast, eval_ast, true, name, this_ptr, arg_values) } + /// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments. + /// + /// The following options are available: + /// + /// * whether to evaluate the [`AST`] to load necessary modules before calling the function + /// * whether to rewind the [`Scope`] after the function call + /// * a value for binding to the `this` pointer (if any) + /// + /// Not available under `no_function`. + /// + /// # Deprecated + /// + /// This method is deprecated. Use [`call_fn_with_options`][Engine::call_fn_with_options] instead. + /// + /// This method will be removed in the next major version. + #[deprecated(since = "1.12.0", note = "use `call_fn_with_options` instead")] + #[cfg(not(feature = "no_function"))] + #[inline(always)] + pub fn call_fn_raw( + &self, + scope: &mut Scope, + ast: &AST, + eval_ast: bool, + rewind_scope: bool, + name: impl AsRef, + this_ptr: Option<&mut Dynamic>, + arg_values: impl AsMut<[Dynamic]>, + ) -> RhaiResult { + let mut arg_values = arg_values; + + let options = crate::CallFnOptions { + this_ptr, + eval_ast, + rewind_scope, + ..Default::default() + }; + + self._call_fn( + options, + scope, + &mut crate::eval::GlobalRuntimeState::new(self), + &mut crate::eval::Caches::new(), + ast, + name.as_ref(), + arg_values.as_mut(), + ) + } /// Register a custom fallible function with the [`Engine`]. /// /// # Deprecated diff --git a/src/api/eval.rs b/src/api/eval.rs index 2c5ffb70..a919089d 100644 --- a/src/api/eval.rs +++ b/src/api/eval.rs @@ -4,11 +4,14 @@ use crate::eval::{Caches, GlobalRuntimeState}; use crate::parser::ParseState; use crate::types::dynamic::Variant; use crate::{ - Dynamic, Engine, OptimizationLevel, Position, RhaiResult, RhaiResultOf, Scope, AST, ERR, + reify, Dynamic, Engine, OptimizationLevel, Position, RhaiResult, RhaiResultOf, Scope, AST, ERR, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; -use std::{any::type_name, mem}; +use std::{ + any::{type_name, TypeId}, + mem, +}; impl Engine { /// Evaluate a string as a script, returning the result value or an error. @@ -190,6 +193,11 @@ impl Engine { let result = self.eval_ast_with_scope_raw(global, caches, scope, ast)?; + // Bail out early if the return type needs no cast + if TypeId::of::() == TypeId::of::() { + return Ok(reify!(result => T)); + } + let typ = self.map_type_name(result.type_name()); result.try_cast::().ok_or_else(|| { diff --git a/src/eval/data_check.rs b/src/eval/data_check.rs index fa47b269..c629eba8 100644 --- a/src/eval/data_check.rs +++ b/src/eval/data_check.rs @@ -150,16 +150,15 @@ impl Engine { // Guard against too many operations let max = self.max_operations(); - let num_operations = global.num_operations; - if max > 0 && num_operations > max { + if max > 0 && global.num_operations > max { return Err(ERR::ErrorTooManyOperations(pos).into()); } // Report progress self.progress .as_ref() - .and_then(|p| p(num_operations)) + .and_then(|p| p(global.num_operations)) .map_or(Ok(()), |token| Err(ERR::ErrorTerminated(token, pos).into())) } } diff --git a/src/func/args.rs b/src/func/args.rs index c80dfbbb..2413c0cf 100644 --- a/src/func/args.rs +++ b/src/func/args.rs @@ -66,6 +66,13 @@ impl FuncArgs for Vec { } } +impl FuncArgs for [T; N] { + #[inline] + fn parse>(self, args: &mut ARGS) { + args.extend(IntoIterator::into_iter(self).map(Dynamic::from)); + } +} + /// Macro to implement [`FuncArgs`] for tuples of standard types (each can be converted into a [`Dynamic`]). macro_rules! impl_args { ($($p:ident),*) => { diff --git a/src/func/native.rs b/src/func/native.rs index b22c39ad..7363606a 100644 --- a/src/func/native.rs +++ b/src/func/native.rs @@ -7,10 +7,10 @@ use crate::plugin::PluginFunction; use crate::tokenizer::{is_valid_function_name, Token, TokenizeState}; use crate::types::dynamic::Variant; use crate::{ - calc_fn_hash, Dynamic, Engine, EvalContext, FuncArgs, Position, RhaiResult, RhaiResultOf, - StaticVec, VarDefInfo, ERR, + calc_fn_hash, reify, Dynamic, Engine, EvalContext, FuncArgs, Position, RhaiResult, + RhaiResultOf, StaticVec, VarDefInfo, ERR, }; -use std::any::type_name; +use std::any::{type_name, TypeId}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -305,6 +305,11 @@ impl<'a> NativeCallContext<'a> { let result = self._call_fn_raw(fn_name, &mut args, false, false, false)?; + // Bail out early if the return type needs no cast + if TypeId::of::() == TypeId::of::() { + return Ok(reify!(result => T)); + } + let typ = self.engine().map_type_name(result.type_name()); result.try_cast().ok_or_else(|| { @@ -330,6 +335,11 @@ impl<'a> NativeCallContext<'a> { let result = self._call_fn_raw(fn_name, &mut args, true, false, false)?; + // Bail out early if the return type needs no cast + if TypeId::of::() == TypeId::of::() { + return Ok(reify!(result => T)); + } + let typ = self.engine().map_type_name(result.type_name()); result.try_cast().ok_or_else(|| { diff --git a/src/lib.rs b/src/lib.rs index 7d5d753a..96bee4eb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -253,6 +253,9 @@ pub use func::Func; #[cfg(not(feature = "no_function"))] pub use ast::ScriptFnMetadata; +#[cfg(not(feature = "no_function"))] +pub use api::call_fn::CallFnOptions; + /// Variable-sized array of [`Dynamic`] values. /// /// Not available under `no_index`. diff --git a/src/packages/iter_basic.rs b/src/packages/iter_basic.rs index 163f93a4..f7985004 100644 --- a/src/packages/iter_basic.rs +++ b/src/packages/iter_basic.rs @@ -23,7 +23,7 @@ use rust_decimal::Decimal; #[inline(always)] fn std_add(x: T, y: T) -> Option where - T: Debug + Copy + PartialOrd + num_traits::CheckedAdd, + T: num_traits::CheckedAdd, { x.checked_add(&y) } @@ -31,14 +31,14 @@ where #[allow(dead_code)] fn regular_add(x: T, y: T) -> Option where - T: Debug + Copy + PartialOrd + std::ops::Add, + T: std::ops::Add, { Some(x + y) } // Range iterator with step #[derive(Clone, Hash, Eq, PartialEq)] -pub struct StepRange { +pub struct StepRange { pub from: T, pub to: T, pub step: T, @@ -46,7 +46,7 @@ pub struct StepRange { pub dir: i8, } -impl Debug for StepRange { +impl Debug for StepRange { #[cold] #[inline(never)] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -58,7 +58,7 @@ impl Debug for StepRange { } } -impl StepRange { +impl StepRange { pub fn new(from: T, to: T, step: T, add: fn(T, T) -> Option) -> RhaiResultOf { let mut dir = 0; @@ -95,7 +95,7 @@ impl StepRange { } } -impl Iterator for StepRange { +impl Iterator for StepRange { type Item = T; fn next(&mut self) -> Option { @@ -118,7 +118,7 @@ impl Iterator for StepRange { } } -impl FusedIterator for StepRange {} +impl FusedIterator for StepRange {} // Bit-field iterator with step #[derive(Debug, Clone, Hash, Eq, PartialEq)] diff --git a/src/parser.rs b/src/parser.rs index 38f357bf..0ee1d9f3 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -3818,7 +3818,7 @@ impl Engine { &self, input: &mut TokenStream, state: &mut ParseState, - process_settings: impl Fn(&mut ParseSettings), + process_settings: impl FnOnce(&mut ParseSettings), _optimization_level: OptimizationLevel, ) -> ParseResult { let mut functions = StraightHashMap::default(); @@ -3882,7 +3882,7 @@ impl Engine { &self, input: &mut TokenStream, state: &mut ParseState, - process_settings: impl Fn(&mut ParseSettings), + process_settings: impl FnOnce(&mut ParseSettings), ) -> ParseResult<(StmtBlockContainer, StaticVec>)> { let mut statements = StmtBlockContainer::new_const(); let mut functions = StraightHashMap::default(); diff --git a/src/types/dynamic.rs b/src/types/dynamic.rs index 3b1a6cee..175b293f 100644 --- a/src/types/dynamic.rs +++ b/src/types/dynamic.rs @@ -1181,42 +1181,105 @@ impl Dynamic { /// ``` #[inline] #[must_use] - pub fn try_cast(self) -> Option { + pub fn try_cast(mut self) -> Option { // Coded this way in order to maximally leverage potentials for dead-code removal. #[cfg(not(feature = "no_closure"))] - if let Union::Shared(..) = self.0 { - return self.flatten().try_cast::(); + self.flatten_in_place(); + + if TypeId::of::() == TypeId::of::() { + return Some(reify!(self => T)); + } + if TypeId::of::() == TypeId::of::<()>() { + return match self.0 { + Union::Unit(..) => Some(reify!(() => T)), + _ => None, + }; + } + if TypeId::of::() == TypeId::of::() { + return match self.0 { + Union::Int(n, ..) => Some(reify!(n => T)), + _ => None, + }; + } + #[cfg(not(feature = "no_float"))] + if TypeId::of::() == TypeId::of::() { + return match self.0 { + Union::Float(v, ..) => Some(reify!(*v => T)), + _ => None, + }; + } + #[cfg(feature = "decimal")] + if TypeId::of::() == TypeId::of::() { + return match self.0 { + Union::Decimal(v, ..) => Some(reify!(*v => T)), + _ => None, + }; + } + if TypeId::of::() == TypeId::of::() { + return match self.0 { + Union::Bool(b, ..) => Some(reify!(b => T)), + _ => None, + }; + } + if TypeId::of::() == TypeId::of::() { + return match self.0 { + Union::Str(s, ..) => Some(reify!(s => T)), + _ => None, + }; + } + if TypeId::of::() == TypeId::of::() { + return match self.0 { + Union::Str(s, ..) => Some(reify!(s.to_string() => T)), + _ => None, + }; + } + if TypeId::of::() == TypeId::of::() { + return match self.0 { + Union::Char(c, ..) => Some(reify!(c => T)), + _ => None, + }; + } + #[cfg(not(feature = "no_index"))] + if TypeId::of::() == TypeId::of::() { + return match self.0 { + Union::Array(a, ..) => Some(reify!(*a => T)), + _ => None, + }; + } + #[cfg(not(feature = "no_index"))] + if TypeId::of::() == TypeId::of::() { + return match self.0 { + Union::Blob(b, ..) => Some(reify!(*b => T)), + _ => None, + }; + } + #[cfg(not(feature = "no_object"))] + if TypeId::of::() == TypeId::of::() { + return match self.0 { + Union::Map(m, ..) => Some(reify!(*m => T)), + _ => None, + }; + } + if TypeId::of::() == TypeId::of::() { + return match self.0 { + Union::FnPtr(f, ..) => Some(reify!(*f => T)), + _ => None, + }; + } + #[cfg(not(feature = "no_time"))] + if TypeId::of::() == TypeId::of::() { + return match self.0 { + Union::TimeStamp(t, ..) => Some(reify!(*t => T)), + _ => None, + }; } - reify!(self, |v: T| return Some(v)); - match self.0 { - Union::Null => unreachable!(), - - Union::Int(v, ..) => reify!(v => Option), - #[cfg(not(feature = "no_float"))] - Union::Float(v, ..) => reify!(*v => Option), - #[cfg(feature = "decimal")] - Union::Decimal(v, ..) => reify!(*v => Option), - Union::Bool(v, ..) => reify!(v => Option), - Union::Str(v, ..) => { - reify!(v, |v: T| Some(v), || reify!(v.to_string() => Option)) - } - Union::Char(v, ..) => reify!(v => Option), - #[cfg(not(feature = "no_index"))] - Union::Array(v, ..) => reify!(*v => Option), - #[cfg(not(feature = "no_index"))] - Union::Blob(v, ..) => reify!(*v => Option), - #[cfg(not(feature = "no_object"))] - Union::Map(v, ..) => reify!(*v => Option), - Union::FnPtr(v, ..) => reify!(*v => Option), - #[cfg(not(feature = "no_time"))] - Union::TimeStamp(v, ..) => reify!(*v => Option), - Union::Unit(v, ..) => reify!(v => Option), Union::Variant(v, ..) => (*v).as_boxed_any().downcast().ok().map(|x| *x), #[cfg(not(feature = "no_closure"))] Union::Shared(..) => unreachable!("Union::Shared case should be already handled"), + _ => None, } } /// Convert the [`Dynamic`] value into a specific type. @@ -1245,6 +1308,11 @@ impl Dynamic { #[inline] #[must_use] pub fn cast(self) -> T { + // Bail out early if the return type needs no cast + if TypeId::of::() == TypeId::of::() { + return reify!(self => T); + } + #[cfg(not(feature = "no_closure"))] let self_type_name = if self.is_shared() { // Avoid panics/deadlocks with shared values diff --git a/src/types/fn_ptr.rs b/src/types/fn_ptr.rs index 900b738b..30454502 100644 --- a/src/types/fn_ptr.rs +++ b/src/types/fn_ptr.rs @@ -4,13 +4,13 @@ use crate::eval::GlobalRuntimeState; use crate::tokenizer::is_valid_function_name; use crate::types::dynamic::Variant; use crate::{ - Dynamic, Engine, FuncArgs, ImmutableString, NativeCallContext, Position, RhaiError, RhaiResult, - RhaiResultOf, StaticVec, AST, ERR, + reify, Dynamic, Engine, FuncArgs, ImmutableString, NativeCallContext, Position, RhaiError, + RhaiResult, RhaiResultOf, StaticVec, AST, ERR, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ - any::type_name, + any::{type_name, TypeId}, convert::{TryFrom, TryInto}, fmt, mem, }; @@ -160,6 +160,11 @@ impl FnPtr { let result = self.call_raw(&ctx, None, arg_values)?; + // Bail out early if the return type needs no cast + if TypeId::of::() == TypeId::of::() { + return Ok(reify!(result => T)); + } + let typ = engine.map_type_name(result.type_name()); result.try_cast().ok_or_else(|| { @@ -184,6 +189,11 @@ impl FnPtr { let result = self.call_raw(context, None, arg_values)?; + // Bail out early if the return type needs no cast + if TypeId::of::() == TypeId::of::() { + return Ok(reify!(result => T)); + } + let typ = context.engine().map_type_name(result.type_name()); result.try_cast().ok_or_else(|| { diff --git a/src/types/interner.rs b/src/types/interner.rs index 43a2d395..cd090fe9 100644 --- a/src/types/interner.rs +++ b/src/types/interner.rs @@ -80,7 +80,7 @@ impl StringsInterner<'_> { pub fn get_with_mapper>( &mut self, id: &str, - mapper: impl Fn(S) -> ImmutableString, + mapper: impl FnOnce(S) -> ImmutableString, text: S, ) -> ImmutableString { let key = text.as_ref(); diff --git a/src/types/scope.rs b/src/types/scope.rs index ba699c24..e217a698 100644 --- a/src/types/scope.rs +++ b/src/types/scope.rs @@ -445,7 +445,8 @@ impl Scope<'_> { .rev() .enumerate() .find(|(.., key)| &name == key) - .and_then(|(index, ..)| self.values[len - 1 - index].flatten_clone().try_cast()) + .map(|(index, ..)| self.values[len - 1 - index].flatten_clone()) + .and_then(Dynamic::try_cast) } /// Check if the named entry in the [`Scope`] is constant. /// diff --git a/tests/call_fn.rs b/tests/call_fn.rs index 0ac876a6..a6efae01 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -1,5 +1,5 @@ #![cfg(not(feature = "no_function"))] -use rhai::{Dynamic, Engine, EvalAltResult, FnPtr, Func, FuncArgs, Scope, AST, INT}; +use rhai::{CallFnOptions, Dynamic, Engine, EvalAltResult, FnPtr, Func, FuncArgs, Scope, AST, INT}; use std::any::TypeId; #[test] @@ -50,11 +50,10 @@ fn test_call_fn() -> Result<(), Box> { assert!(!scope.contains("bar")); - let args = [(2 as INT).into()]; - let r = engine - .call_fn_raw(&mut scope, &ast, false, false, "define_var", None, args)? - .as_int() - .unwrap(); + let options = CallFnOptions::new().eval_ast(false).rewind_scope(false); + + let r = + engine.call_fn_with_options::(options, &mut scope, &ast, "define_var", (2 as INT,))?; assert_eq!(r, 42); assert_eq!( @@ -87,9 +86,13 @@ fn test_call_fn_scope() -> Result<(), Box> { for _ in 0..50 { assert_eq!( - engine - .call_fn_raw(&mut scope, &ast, true, false, "foo", None, [Dynamic::THREE])? - .as_int()?, + engine.call_fn_with_options::( + CallFnOptions::new().rewind_scope(false), + &mut scope, + &ast, + "foo", + [Dynamic::THREE], + )?, 168 ); }