Change call_fn_raw to call_fn_with_options.

This commit is contained in:
Stephen Chung 2022-11-21 23:42:29 +08:00
parent 3feff3618a
commit d151c87687
18 changed files with 387 additions and 233 deletions

View File

@ -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%.

View File

@ -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}")

View File

@ -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}")

View File

@ -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}")

View File

@ -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<Dynamic>,
/// 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<rhai::EvalAltResult>> {
/// # #[cfg(not(feature = "no_function"))]
/// # {
/// use rhai::{Engine, Scope};
///
/// let engine = Engine::new();
@ -51,30 +109,83 @@ impl Engine {
///
/// let result = engine.call_fn::<i64>(&mut scope, &ast, "bar", () )?;
/// assert_eq!(result, 21);
/// # }
/// # Ok(())
/// # }
/// ```
#[inline]
#[inline(always)]
pub fn call_fn<T: Variant + Clone>(
&self,
scope: &mut Scope,
ast: &AST,
name: impl AsRef<str>,
args: impl FuncArgs,
) -> RhaiResultOf<T> {
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<rhai::EvalAltResult>> {
/// 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::<i64>("hello").unwrap(), 42);
/// # Ok(())
/// # }
/// ```
#[inline(always)]
pub fn call_fn_with_options<T: Variant + Clone>(
&self,
options: CallFnOptions,
scope: &mut Scope,
ast: &AST,
name: impl AsRef<str>,
args: impl FuncArgs,
) -> RhaiResultOf<T> {
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::<T>() == TypeId::of::<Dynamic>() {
return Ok(reify!(result => T));
}
if TypeId::of::<T>() == 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<rhai::EvalAltResult>> {
/// # #[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::<i64>(), 168);
///
/// let result = engine.call_fn_raw(&mut scope, &ast, true, true, "add1", None, [ "abc".into() ])?;
/// assert_eq!(result.cast::<i64>(), 46);
///
/// let result = engine.call_fn_raw(&mut scope, &ast, true, true, "bar", None, [])?;
/// assert_eq!(result.cast::<i64>(), 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::<i64>("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<str>,
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

View File

@ -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<str>,
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

View File

@ -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::<T>() == TypeId::of::<Dynamic>() {
return Ok(reify!(result => T));
}
let typ = self.map_type_name(result.type_name());
result.try_cast::<T>().ok_or_else(|| {

View File

@ -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()))
}
}

View File

@ -66,6 +66,13 @@ impl<T: Variant + Clone> FuncArgs for Vec<T> {
}
}
impl<T: Variant + Clone, const N: usize> FuncArgs for [T; N] {
#[inline]
fn parse<ARGS: Extend<Dynamic>>(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),*) => {

View File

@ -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::<T>() == TypeId::of::<Dynamic>() {
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::<T>() == TypeId::of::<Dynamic>() {
return Ok(reify!(result => T));
}
let typ = self.engine().map_type_name(result.type_name());
result.try_cast().ok_or_else(|| {

View File

@ -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`.

View File

@ -23,7 +23,7 @@ use rust_decimal::Decimal;
#[inline(always)]
fn std_add<T>(x: T, y: T) -> Option<T>
where
T: Debug + Copy + PartialOrd + num_traits::CheckedAdd<Output = T>,
T: num_traits::CheckedAdd<Output = T>,
{
x.checked_add(&y)
}
@ -31,14 +31,14 @@ where
#[allow(dead_code)]
fn regular_add<T>(x: T, y: T) -> Option<T>
where
T: Debug + Copy + PartialOrd + std::ops::Add<Output = T>,
T: std::ops::Add<Output = T>,
{
Some(x + y)
}
// Range iterator with step
#[derive(Clone, Hash, Eq, PartialEq)]
pub struct StepRange<T: Debug + Copy + PartialOrd> {
pub struct StepRange<T> {
pub from: T,
pub to: T,
pub step: T,
@ -46,7 +46,7 @@ pub struct StepRange<T: Debug + Copy + PartialOrd> {
pub dir: i8,
}
impl<T: Debug + Copy + PartialOrd> Debug for StepRange<T> {
impl<T: Debug> Debug for StepRange<T> {
#[cold]
#[inline(never)]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@ -58,7 +58,7 @@ impl<T: Debug + Copy + PartialOrd> Debug for StepRange<T> {
}
}
impl<T: Debug + Copy + PartialOrd> StepRange<T> {
impl<T: Copy + PartialOrd> StepRange<T> {
pub fn new(from: T, to: T, step: T, add: fn(T, T) -> Option<T>) -> RhaiResultOf<Self> {
let mut dir = 0;
@ -95,7 +95,7 @@ impl<T: Debug + Copy + PartialOrd> StepRange<T> {
}
}
impl<T: Debug + Copy + PartialOrd> Iterator for StepRange<T> {
impl<T: Copy + PartialOrd> Iterator for StepRange<T> {
type Item = T;
fn next(&mut self) -> Option<T> {
@ -118,7 +118,7 @@ impl<T: Debug + Copy + PartialOrd> Iterator for StepRange<T> {
}
}
impl<T: Debug + Copy + PartialOrd> FusedIterator for StepRange<T> {}
impl<T: Copy + PartialOrd> FusedIterator for StepRange<T> {}
// Bit-field iterator with step
#[derive(Debug, Clone, Hash, Eq, PartialEq)]

View File

@ -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<AST> {
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<Shared<ScriptFnDef>>)> {
let mut statements = StmtBlockContainer::new_const();
let mut functions = StraightHashMap::default();

View File

@ -1181,42 +1181,105 @@ impl Dynamic {
/// ```
#[inline]
#[must_use]
pub fn try_cast<T: Any>(self) -> Option<T> {
pub fn try_cast<T: Any>(mut self) -> Option<T> {
// 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::<T>();
}
self.flatten_in_place();
reify!(self, |v: T| return Some(v));
if TypeId::of::<T>() == TypeId::of::<Dynamic>() {
return Some(reify!(self => T));
}
if TypeId::of::<T>() == TypeId::of::<()>() {
return match self.0 {
Union::Unit(..) => Some(reify!(() => T)),
_ => None,
};
}
if TypeId::of::<T>() == TypeId::of::<INT>() {
return match self.0 {
Union::Int(n, ..) => Some(reify!(n => T)),
_ => None,
};
}
#[cfg(not(feature = "no_float"))]
if TypeId::of::<T>() == TypeId::of::<crate::FLOAT>() {
return match self.0 {
Union::Float(v, ..) => Some(reify!(*v => T)),
_ => None,
};
}
#[cfg(feature = "decimal")]
if TypeId::of::<T>() == TypeId::of::<rust_decimal::Decimal>() {
return match self.0 {
Union::Decimal(v, ..) => Some(reify!(*v => T)),
_ => None,
};
}
if TypeId::of::<T>() == TypeId::of::<bool>() {
return match self.0 {
Union::Bool(b, ..) => Some(reify!(b => T)),
_ => None,
};
}
if TypeId::of::<T>() == TypeId::of::<ImmutableString>() {
return match self.0 {
Union::Str(s, ..) => Some(reify!(s => T)),
_ => None,
};
}
if TypeId::of::<T>() == TypeId::of::<String>() {
return match self.0 {
Union::Str(s, ..) => Some(reify!(s.to_string() => T)),
_ => None,
};
}
if TypeId::of::<T>() == TypeId::of::<char>() {
return match self.0 {
Union::Char(c, ..) => Some(reify!(c => T)),
_ => None,
};
}
#[cfg(not(feature = "no_index"))]
if TypeId::of::<T>() == TypeId::of::<crate::Array>() {
return match self.0 {
Union::Array(a, ..) => Some(reify!(*a => T)),
_ => None,
};
}
#[cfg(not(feature = "no_index"))]
if TypeId::of::<T>() == TypeId::of::<crate::Blob>() {
return match self.0 {
Union::Blob(b, ..) => Some(reify!(*b => T)),
_ => None,
};
}
#[cfg(not(feature = "no_object"))]
if TypeId::of::<T>() == TypeId::of::<crate::Map>() {
return match self.0 {
Union::Map(m, ..) => Some(reify!(*m => T)),
_ => None,
};
}
if TypeId::of::<T>() == TypeId::of::<FnPtr>() {
return match self.0 {
Union::FnPtr(f, ..) => Some(reify!(*f => T)),
_ => None,
};
}
#[cfg(not(feature = "no_time"))]
if TypeId::of::<T>() == TypeId::of::<Instant>() {
return match self.0 {
Union::TimeStamp(t, ..) => Some(reify!(*t => T)),
_ => None,
};
}
match self.0 {
Union::Null => unreachable!(),
Union::Int(v, ..) => reify!(v => Option<T>),
#[cfg(not(feature = "no_float"))]
Union::Float(v, ..) => reify!(*v => Option<T>),
#[cfg(feature = "decimal")]
Union::Decimal(v, ..) => reify!(*v => Option<T>),
Union::Bool(v, ..) => reify!(v => Option<T>),
Union::Str(v, ..) => {
reify!(v, |v: T| Some(v), || reify!(v.to_string() => Option<T>))
}
Union::Char(v, ..) => reify!(v => Option<T>),
#[cfg(not(feature = "no_index"))]
Union::Array(v, ..) => reify!(*v => Option<T>),
#[cfg(not(feature = "no_index"))]
Union::Blob(v, ..) => reify!(*v => Option<T>),
#[cfg(not(feature = "no_object"))]
Union::Map(v, ..) => reify!(*v => Option<T>),
Union::FnPtr(v, ..) => reify!(*v => Option<T>),
#[cfg(not(feature = "no_time"))]
Union::TimeStamp(v, ..) => reify!(*v => Option<T>),
Union::Unit(v, ..) => reify!(v => Option<T>),
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<T: Any + Clone>(self) -> T {
// Bail out early if the return type needs no cast
if TypeId::of::<T>() == TypeId::of::<Dynamic>() {
return reify!(self => T);
}
#[cfg(not(feature = "no_closure"))]
let self_type_name = if self.is_shared() {
// Avoid panics/deadlocks with shared values

View File

@ -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::<T>() == TypeId::of::<Dynamic>() {
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::<T>() == TypeId::of::<Dynamic>() {
return Ok(reify!(result => T));
}
let typ = context.engine().map_type_name(result.type_name());
result.try_cast().ok_or_else(|| {

View File

@ -80,7 +80,7 @@ impl StringsInterner<'_> {
pub fn get_with_mapper<S: AsRef<str>>(
&mut self,
id: &str,
mapper: impl Fn(S) -> ImmutableString,
mapper: impl FnOnce(S) -> ImmutableString,
text: S,
) -> ImmutableString {
let key = text.as_ref();

View File

@ -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.
///

View File

@ -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<EvalAltResult>> {
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::<INT>(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<EvalAltResult>> {
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::<INT>(
CallFnOptions::new().rewind_scope(false),
&mut scope,
&ast,
"foo",
[Dynamic::THREE],
)?,
168
);
}