diff --git a/CHANGELOG.md b/CHANGELOG.md index 26ccf297..97aea371 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ Bug fixes * Errors in native Rust functions now contain the correct function call positions. * Fixed error types in `EvalAltResult::ErrorMismatchDataType` which were swapped. * Some expressions involving shared variables now work properly, for example `x in shared_value`, `return shared_value`, `obj.field = shared_value` etc. Previously, the resultant value is still shared which is counter-intuitive. +* Potential overflow panics in `range(from, to, step)` is fixed. +* `switch` statements no longer panic on custom types. Breaking changes ---------------- @@ -23,6 +25,7 @@ Breaking changes * Function keywords (e.g. `type_of`, `eval`, `Fn`) can no longer be overloaded. It is more trouble than worth. To disable these keywords, use `Engine::disable_symbol`. * `is_def_var` and `is_def_fn` are now reserved keywords. * `Engine::id` field is removed. +* `num-traits` is now a required dependency. Enhancements ------------ diff --git a/Cargo.toml b/Cargo.toml index a334d795..9ce632c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ categories = [ "no-std", "embedded", "wasm", "parser-implementations" ] [dependencies] smallvec = { version = "1.6", default-features = false, features = ["union"] } ahash = { version = "0.6", default-features = false } +num-traits = { version = "0.2", default_features = false } rhai_codegen = { version = "0.3.3", path = "codegen" } [features] @@ -65,11 +66,6 @@ version = "0.2" default_features = false optional = true -[dependencies.num-traits] -version = "0.2" -default-features = false -optional = true - [dependencies.core-error] version = "0.0" default_features = false diff --git a/README.md b/README.md index 1bfd1806..8d2b90e6 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Standard features * Built-in support for most common [data types](https://rhai.rs/book/language/values-and-types.html) including booleans, integers, floating-point numbers (including [`Decimal`](https://crates.io/crates/rust_decimal)), strings, Unicode characters, arrays and maps. * Easily [call a script-defined function](https://rhai.rs/book/engine/call-fn.html) from Rust. * Relatively little `unsafe` code (yes there are some for performance reasons). -* Few dependencies (currently only [`smallvec`](https://crates.io/crates/smallvec) and [`ahash`](https://crates.io/crates/ahash)). +* Few dependencies (currently only [`smallvec`](https://crates.io/crates/smallvec), [`num-traits`](https://crates.io/crates/num-traits) and [`ahash`](https://crates.io/crates/ahash)). * Re-entrant scripting engine can be made `Send + Sync` (via the `sync` feature). * Compile once to [AST](https://rhai.rs/book/engine/compile.html) form for repeated evaluations. * Scripts are [optimized](https://rhai.rs/book/engine/optimize.html) (useful for template-based machine-generated scripts). diff --git a/src/ast.rs b/src/ast.rs index 050ea197..13127fe4 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1109,7 +1109,7 @@ pub struct FloatWrapper(FLOAT); #[cfg(not(feature = "no_float"))] impl Hash for FloatWrapper { fn hash(&self, state: &mut H) { - self.0.to_le_bytes().hash(state); + self.0.to_ne_bytes().hash(state); } } diff --git a/src/dynamic.rs b/src/dynamic.rs index 193280a7..b0168ccb 100644 --- a/src/dynamic.rs +++ b/src/dynamic.rs @@ -414,13 +414,13 @@ impl Hash for Dynamic { #[cfg(feature = "sync")] Union::Shared(cell, _) => (*cell.read().unwrap()).hash(state), - _ => unimplemented!(), + _ => unimplemented!("{} cannot be hashed", self.type_name()), } } } /// Map the name of a standard type into a friendly form. -#[inline] +#[inline(always)] pub(crate) fn map_std_type_name(name: &str) -> &str { if name == type_name::() { "string" @@ -761,6 +761,32 @@ impl Dynamic { _ => self.access_mode().is_read_only(), } } + /// Can this [`Dynamic`] be hashed? + pub(crate) fn is_hashable(&self) -> bool { + match &self.0 { + Union::Unit(_, _) + | Union::Bool(_, _) + | Union::Str(_, _) + | Union::Char(_, _) + | Union::Int(_, _) => true, + + #[cfg(not(feature = "no_float"))] + Union::Float(_, _) => true, + #[cfg(not(feature = "no_index"))] + Union::Array(_, _) => true, + #[cfg(not(feature = "no_object"))] + Union::Map(_, _) => true, + + #[cfg(not(feature = "no_closure"))] + #[cfg(not(feature = "sync"))] + Union::Shared(cell, _) => cell.borrow().is_hashable(), + #[cfg(not(feature = "no_closure"))] + #[cfg(feature = "sync")] + Union::Shared(cell, _) => cell.read().unwrap().is_hashable(), + + _ => false, + } + } /// Create a [`Dynamic`] from any type. A [`Dynamic`] value is simply returned as is. /// /// # Safety @@ -1496,7 +1522,7 @@ impl Dynamic { } /// Convert the [`Dynamic`] into an [`ImmutableString`] and return it. /// Returns the name of the actual type if the cast fails. - #[inline] + #[inline(always)] pub fn take_immutable_string(self) -> Result { match self.0 { Union::Str(s, _) => Ok(s), diff --git a/src/engine.rs b/src/engine.rs index 7f562742..7a57d972 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -862,7 +862,7 @@ impl Engine { /// Create a new [`Engine`] with minimal built-in functions. /// /// Use [`register_global_module`][Engine::register_global_module] to add packages of functions. - #[inline] + #[inline(always)] pub fn new_raw() -> Self { Self { global_namespace: Default::default(), @@ -2111,18 +2111,29 @@ impl Engine { Stmt::Switch(match_expr, x, _) => { let (table, def_stmt) = x.as_ref(); - let hasher = &mut get_hasher(); - self.eval_expr(scope, mods, state, lib, this_ptr, match_expr, level)? - .hash(hasher); - let hash = hasher.finish(); + let value = self.eval_expr(scope, mods, state, lib, this_ptr, match_expr, level)?; - if let Some(stmt) = table.get(&hash) { - self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level) - } else if let Some(def_stmt) = def_stmt { - self.eval_stmt(scope, mods, state, lib, this_ptr, def_stmt, level) + if value.is_hashable() { + let hasher = &mut get_hasher(); + value.hash(hasher); + let hash = hasher.finish(); + + table + .get(&hash) + .map(|stmt| self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level)) } else { - Ok(Dynamic::UNIT) + // Non-hashable values never match any specific clause + None } + .unwrap_or_else(|| { + // Default match clause + def_stmt.as_ref().map_or_else( + || Ok(Dynamic::UNIT), + |def_stmt| { + self.eval_stmt(scope, mods, state, lib, this_ptr, def_stmt, level) + }, + ) + }) } // While loop diff --git a/src/engine_api.rs b/src/engine_api.rs index ee804636..9ac38a97 100644 --- a/src/engine_api.rs +++ b/src/engine_api.rs @@ -1034,7 +1034,6 @@ impl Engine { /// Read the contents of a file into a string. #[cfg(not(feature = "no_std"))] #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] - #[inline] fn read_file(path: crate::stdlib::path::PathBuf) -> Result> { use crate::stdlib::io::Read; @@ -1276,7 +1275,7 @@ impl Engine { /// # Ok(()) /// # } /// ``` - #[inline] + #[inline(always)] pub fn compile_expression_with_scope( &self, scope: &Scope, @@ -1385,7 +1384,7 @@ impl Engine { /// # Ok(()) /// # } /// ``` - #[inline] + #[inline(always)] pub fn eval_with_scope( &self, scope: &mut Scope, @@ -1437,7 +1436,7 @@ impl Engine { /// # Ok(()) /// # } /// ``` - #[inline] + #[inline(always)] pub fn eval_expression_with_scope( &self, scope: &mut Scope, @@ -1502,7 +1501,7 @@ impl Engine { /// # Ok(()) /// # } /// ``` - #[inline] + #[inline(always)] pub fn eval_ast_with_scope( &self, scope: &mut Scope, @@ -1578,7 +1577,7 @@ impl Engine { } /// Evaluate a string with own scope, but throw away the result and only return error (if any). /// Useful for when you don't need the result, but still need to keep track of possible errors. - #[inline] + #[inline(always)] pub fn consume_with_scope( &self, scope: &mut Scope, @@ -1659,7 +1658,7 @@ impl Engine { /// # } /// ``` #[cfg(not(feature = "no_function"))] - #[inline] + #[inline(always)] pub fn call_fn( &self, scope: &mut Scope, @@ -1760,7 +1759,7 @@ impl Engine { /// Do not use the arguments after this call. If they are needed afterwards, /// clone them _before_ calling this function. #[cfg(not(feature = "no_function"))] - #[inline] + #[inline(always)] pub(crate) fn call_fn_dynamic_raw( &self, scope: &mut Scope, @@ -1814,7 +1813,7 @@ impl Engine { /// (i.e. with [`Scope::push_constant`]). /// Then, the [`AST`] is cloned and the copy re-optimized before running. #[cfg(not(feature = "no_optimize"))] - #[inline] + #[inline(always)] pub fn optimize_ast( &self, scope: &Scope, diff --git a/src/fn_builtin.rs b/src/fn_builtin.rs index 4a9561de..7d4f9c04 100644 --- a/src/fn_builtin.rs +++ b/src/fn_builtin.rs @@ -15,6 +15,7 @@ use rust_decimal::Decimal; use num_traits::float::Float; /// Is the type a numeric type? +#[inline(always)] fn is_numeric(type_id: TypeId) -> bool { let result = type_id == TypeId::of::() || type_id == TypeId::of::() @@ -75,55 +76,48 @@ pub fn get_builtin_binary_op_fn( Ok((x $op y).into()) }) }; - ($xx:ident $op:tt $yy:ident -> $base:ty) => { + ($base:ty => $xx:ident $op:tt $yy:ident) => { return Some(|_, args| { let x = args[0].$xx().unwrap() as $base; let y = args[1].$yy().unwrap() as $base; Ok((x $op y).into()) }) }; - ($xx:ident . $func:ident ( $yy:ident as $yyy:ty) -> $base:ty) => { + ($base:ty => $xx:ident . $func:ident ( $yy:ident as $yyy:ty)) => { return Some(|_, args| { let x = args[0].$xx().unwrap() as $base; let y = args[1].$yy().unwrap() as $base; Ok(x.$func(y as $yyy).into()) }) }; - ($func:ident ( $xx:ident, $yy:ident ) -> $base:ty) => { + ($base:ty => $func:ident ( $xx:ident, $yy:ident )) => { return Some(|_, args| { let x = args[0].$xx().unwrap() as $base; let y = args[1].$yy().unwrap() as $base; $func(x, y) }) }; - ($xx:ident $op:tt $yy:ident -> from $base:ty) => { + (from $base:ty => $xx:ident $op:tt $yy:ident) => { return Some(|_, args| { let x = <$base>::from(args[0].$xx().unwrap()); let y = <$base>::from(args[1].$yy().unwrap()); Ok((x $op y).into()) }) }; - ($xx:ident . $func:ident ( $yy:ident ) -> from $base:ty) => { + (from $base:ty => $xx:ident . $func:ident ( $yy:ident )) => { return Some(|_, args| { let x = <$base>::from(args[0].$xx().unwrap()); let y = <$base>::from(args[1].$yy().unwrap()); Ok(x.$func(y).into()) }) }; - ($func:ident ( $xx:ident, $yy:ident ) -> from $base:ty) => { + (from $base:ty => $func:ident ( $xx:ident, $yy:ident )) => { return Some(|_, args| { let x = <$base>::from(args[0].$xx().unwrap()); let y = <$base>::from(args[1].$yy().unwrap()); $func(x, y) }) }; - (& $x:ident $op:tt & $y:ident) => { - return Some(|_, args| { - let x = &*args[0].read_lock::<$x>().unwrap(); - let y = &*args[1].read_lock::<$y>().unwrap(); - Ok((x $op y).into()) - }) - } } macro_rules! impl_float { @@ -131,18 +125,18 @@ pub fn get_builtin_binary_op_fn( #[cfg(not(feature = "no_float"))] if types_pair == (TypeId::of::<$x>(), TypeId::of::<$y>()) { match op { - "+" => impl_op!($xx + $yy -> FLOAT), - "-" => impl_op!($xx - $yy -> FLOAT), - "*" => impl_op!($xx * $yy -> FLOAT), - "/" => impl_op!($xx / $yy -> FLOAT), - "%" => impl_op!($xx % $yy -> FLOAT), - "**" => impl_op!($xx.powf($yy as FLOAT) -> FLOAT), - "==" => impl_op!($xx == $yy -> FLOAT), - "!=" => impl_op!($xx != $yy -> FLOAT), - ">" => impl_op!($xx > $yy -> FLOAT), - ">=" => impl_op!($xx >= $yy -> FLOAT), - "<" => impl_op!($xx < $yy -> FLOAT), - "<=" => impl_op!($xx <= $yy -> FLOAT), + "+" => impl_op!(FLOAT => $xx + $yy), + "-" => impl_op!(FLOAT => $xx - $yy), + "*" => impl_op!(FLOAT => $xx * $yy), + "/" => impl_op!(FLOAT => $xx / $yy), + "%" => impl_op!(FLOAT => $xx % $yy), + "**" => impl_op!(FLOAT => $xx.powf($yy as FLOAT)), + "==" => impl_op!(FLOAT => $xx == $yy), + "!=" => impl_op!(FLOAT => $xx != $yy), + ">" => impl_op!(FLOAT => $xx > $yy), + ">=" => impl_op!(FLOAT => $xx >= $yy), + "<" => impl_op!(FLOAT => $xx < $yy), + "<=" => impl_op!(FLOAT => $xx <= $yy), _ => return None, } } @@ -161,31 +155,31 @@ pub fn get_builtin_binary_op_fn( use crate::packages::arithmetic::decimal_functions::*; match op { - "+" => impl_op!(add($xx, $yy) -> from Decimal), - "-" => impl_op!(subtract($xx, $yy) -> from Decimal), - "*" => impl_op!(multiply($xx, $yy) -> from Decimal), - "/" => impl_op!(divide($xx, $yy) -> from Decimal), - "%" => impl_op!(modulo($xx, $yy) -> from Decimal), + "+" => impl_op!(from Decimal => add($xx, $yy)), + "-" => impl_op!(from Decimal => subtract($xx, $yy)), + "*" => impl_op!(from Decimal => multiply($xx, $yy)), + "/" => impl_op!(from Decimal => divide($xx, $yy)), + "%" => impl_op!(from Decimal => modulo($xx, $yy)), _ => () } } else { match op { - "+" => impl_op!($xx + $yy -> from Decimal), - "-" => impl_op!($xx - $yy -> from Decimal), - "*" => impl_op!($xx * $yy -> from Decimal), - "/" => impl_op!($xx / $yy -> from Decimal), - "%" => impl_op!($xx % $yy -> from Decimal), + "+" => impl_op!(from Decimal => $xx + $yy), + "-" => impl_op!(from Decimal => $xx - $yy), + "*" => impl_op!(from Decimal => $xx * $yy), + "/" => impl_op!(from Decimal => $xx / $yy), + "%" => impl_op!(from Decimal => $xx % $yy), _ => () } } match op { - "==" => impl_op!($xx == $yy -> from Decimal), - "!=" => impl_op!($xx != $yy -> from Decimal), - ">" => impl_op!($xx > $yy -> from Decimal), - ">=" => impl_op!($xx >= $yy -> from Decimal), - "<" => impl_op!($xx < $yy -> from Decimal), - "<=" => impl_op!($xx <= $yy -> from Decimal), + "==" => impl_op!(from Decimal => $xx == $yy), + "!=" => impl_op!(from Decimal => $xx != $yy), + ">" => impl_op!(from Decimal => $xx > $yy), + ">=" => impl_op!(from Decimal => $xx >= $yy), + "<" => impl_op!(from Decimal => $xx < $yy), + "<=" => impl_op!(from Decimal => $xx <= $yy), _ => return None } } @@ -278,81 +272,100 @@ pub fn get_builtin_binary_op_fn( use crate::packages::arithmetic::arith_basic::INT::functions::*; match op { - "+" => impl_op!(add(as_int, as_int) -> INT), - "-" => impl_op!(subtract(as_int, as_int) -> INT), - "*" => impl_op!(multiply(as_int, as_int) -> INT), - "/" => impl_op!(divide(as_int, as_int) -> INT), - "%" => impl_op!(modulo(as_int, as_int) -> INT), - "**" => impl_op!(power(as_int, as_int) -> INT), - ">>" => impl_op!(shift_right(as_int, as_int) -> INT), - "<<" => impl_op!(shift_left(as_int, as_int) -> INT), + "+" => impl_op!(INT => add(as_int, as_int)), + "-" => impl_op!(INT => subtract(as_int, as_int)), + "*" => impl_op!(INT => multiply(as_int, as_int)), + "/" => impl_op!(INT => divide(as_int, as_int)), + "%" => impl_op!(INT => modulo(as_int, as_int)), + "**" => impl_op!(INT => power(as_int, as_int)), + ">>" => impl_op!(INT => shift_right(as_int, as_int)), + "<<" => impl_op!(INT => shift_left(as_int, as_int)), _ => (), } } else { match op { - "+" => impl_op!(as_int + as_int -> INT), - "-" => impl_op!(as_int - as_int -> INT), - "*" => impl_op!(as_int * as_int -> INT), - "/" => impl_op!(as_int / as_int -> INT), - "%" => impl_op!(as_int % as_int -> INT), - "**" => impl_op!(as_int.pow(as_int as u32) -> INT), - ">>" => impl_op!(as_int >> as_int -> INT), - "<<" => impl_op!(as_int << as_int -> INT), + "+" => impl_op!(INT => as_int + as_int), + "-" => impl_op!(INT => as_int - as_int), + "*" => impl_op!(INT => as_int * as_int), + "/" => impl_op!(INT => as_int / as_int), + "%" => impl_op!(INT => as_int % as_int), + "**" => impl_op!(INT => as_int.pow(as_int as u32)), + ">>" => impl_op!(INT => as_int >> as_int), + "<<" => impl_op!(INT => as_int << as_int), _ => (), } } match op { - "==" => impl_op!(as_int == as_int -> INT), - "!=" => impl_op!(as_int != as_int -> INT), - ">" => impl_op!(as_int > as_int -> INT), - ">=" => impl_op!(as_int >= as_int -> INT), - "<" => impl_op!(as_int < as_int -> INT), - "<=" => impl_op!(as_int <= as_int -> INT), - "&" => impl_op!(as_int & as_int -> INT), - "|" => impl_op!(as_int | as_int -> INT), - "^" => impl_op!(as_int ^ as_int -> INT), + "==" => impl_op!(INT => as_int == as_int), + "!=" => impl_op!(INT => as_int != as_int), + ">" => impl_op!(INT => as_int > as_int), + ">=" => impl_op!(INT => as_int >= as_int), + "<" => impl_op!(INT => as_int < as_int), + "<=" => impl_op!(INT => as_int <= as_int), + "&" => impl_op!(INT => as_int & as_int), + "|" => impl_op!(INT => as_int | as_int), + "^" => impl_op!(INT => as_int ^ as_int), _ => return None, } } if type1 == TypeId::of::() { match op { - "==" => impl_op!(as_bool == as_bool -> bool), - "!=" => impl_op!(as_bool != as_bool -> bool), - ">" => impl_op!(as_bool > as_bool -> bool), - ">=" => impl_op!(as_bool >= as_bool -> bool), - "<" => impl_op!(as_bool < as_bool -> bool), - "<=" => impl_op!(as_bool <= as_bool -> bool), - "&" => impl_op!(as_bool & as_bool -> bool), - "|" => impl_op!(as_bool | as_bool -> bool), - "^" => impl_op!(as_bool ^ as_bool -> bool), + "==" => impl_op!(bool => as_bool == as_bool), + "!=" => impl_op!(bool => as_bool != as_bool), + ">" => impl_op!(bool => as_bool > as_bool), + ">=" => impl_op!(bool => as_bool >= as_bool), + "<" => impl_op!(bool => as_bool < as_bool), + "<=" => impl_op!(bool => as_bool <= as_bool), + "&" => impl_op!(bool => as_bool & as_bool), + "|" => impl_op!(bool => as_bool | as_bool), + "^" => impl_op!(bool => as_bool ^ as_bool), _ => return None, } } if type1 == TypeId::of::() { match op { - "+" => impl_op!(&ImmutableString + &ImmutableString), - "-" => impl_op!(&ImmutableString - &ImmutableString), - "==" => impl_op!(&ImmutableString == &ImmutableString), - "!=" => impl_op!(&ImmutableString != &ImmutableString), - ">" => impl_op!(&ImmutableString > &ImmutableString), - ">=" => impl_op!(&ImmutableString >= &ImmutableString), - "<" => impl_op!(&ImmutableString < &ImmutableString), - "<=" => impl_op!(&ImmutableString <= &ImmutableString), + "+" => { + return Some(|_, args| { + let x = &*args[0].read_lock::().unwrap(); + let y = &*args[1].read_lock::().unwrap(); + Ok((x + y).into()) + }) + } + "-" => { + return Some(|_, args| { + let x = &*args[0].read_lock::().unwrap(); + let y = &*args[1].read_lock::().unwrap(); + Ok((x - y).into()) + }) + } + "==" => impl_op!(&str => as_str == as_str), + "!=" => impl_op!(&str => as_str != as_str), + ">" => impl_op!(&str => as_str > as_str), + ">=" => impl_op!(&str => as_str >= as_str), + "<" => impl_op!(&str => as_str < as_str), + "<=" => impl_op!(&str => as_str <= as_str), _ => return None, } } if type1 == TypeId::of::() { match op { - "==" => impl_op!(as_char == as_char -> char), - "!=" => impl_op!(as_char != as_char -> char), - ">" => impl_op!(as_char > as_char -> char), - ">=" => impl_op!(as_char >= as_char -> char), - "<" => impl_op!(as_char < as_char -> char), - "<=" => impl_op!(as_char <= as_char -> char), + "+" => { + return Some(|_, args| { + let x = args[0].as_char().unwrap(); + let y = args[1].as_char().unwrap(); + Ok(format!("{}{}", x, y).into()) + }) + } + "==" => impl_op!(char => as_char == as_char), + "!=" => impl_op!(char => as_char != as_char), + ">" => impl_op!(char => as_char > as_char), + ">=" => impl_op!(char => as_char >= as_char), + "<" => impl_op!(char => as_char < as_char), + "<=" => impl_op!(char => as_char <= as_char), _ => return None, } } @@ -380,7 +393,7 @@ pub fn get_builtin_op_assignment_fn( let types_pair = (type1, type2); macro_rules! impl_op { - ($x:ident = x $op:tt $yy:ident) => { + ($x:ty = x $op:tt $yy:ident) => { return Some(|_, args| { let x = args[0].$yy().unwrap(); let y = args[1].$yy().unwrap() as $x; @@ -399,14 +412,14 @@ pub fn get_builtin_op_assignment_fn( Ok((*args[0].write_lock::<$x>().unwrap() $op y).into()) }) }; - ($xx:ident . $func:ident ( $yy:ident as $yyy:ty ) -> $x:ty) => { + ($x:ty => $xx:ident . $func:ident ( $yy:ident as $yyy:ty )) => { return Some(|_, args| { let x = args[0].$xx().unwrap(); let y = args[1].$yy().unwrap() as $x; Ok((*args[0].write_lock::<$x>().unwrap() = x.$func(y as $yyy)).into()) }) }; - ($func:ident ( $xx:ident, $yy:ident ) -> $x:ty) => { + ($x:ty => $func:ident ( $xx:ident, $yy:ident )) => { return Some(|_, args| { let x = args[0].$xx().unwrap(); let y = args[1].$yy().unwrap() as $x; @@ -419,14 +432,14 @@ pub fn get_builtin_op_assignment_fn( Ok((*args[0].write_lock::<$x>().unwrap() $op y).into()) }) }; - ($xx:ident . $func:ident ( $yy:ident ) -> from $x:ty) => { + (from $x:ty => $xx:ident . $func:ident ( $yy:ident )) => { return Some(|_, args| { let x = args[0].$xx().unwrap(); let y = <$x>::from(args[1].$yy().unwrap()); Ok((*args[0].write_lock::<$x>().unwrap() = x.$func(y)).into()) }) }; - ($func:ident ( $xx:ident, $yy:ident ) -> from $x:ty) => { + (from $x:ty => $func:ident ( $xx:ident, $yy:ident )) => { return Some(|_, args| { let x = args[0].$xx().unwrap(); let y = <$x>::from(args[1].$yy().unwrap()); @@ -445,7 +458,7 @@ pub fn get_builtin_op_assignment_fn( "*=" => impl_op!($x *= $yy), "/=" => impl_op!($x /= $yy), "%=" => impl_op!($x %= $yy), - "**=" => impl_op!($xx.powf($yy as $x) -> $x), + "**=" => impl_op!($x => $xx.powf($yy as $x)), _ => return None, } } @@ -463,11 +476,11 @@ pub fn get_builtin_op_assignment_fn( use crate::packages::arithmetic::decimal_functions::*; match op { - "+=" => impl_op!(add($xx, $yy) -> from $x), - "-=" => impl_op!(subtract($xx, $yy) -> from $x), - "*=" => impl_op!(multiply($xx, $yy) -> from $x), - "/=" => impl_op!(divide($xx, $yy) -> from $x), - "%=" => impl_op!(modulo($xx, $yy) -> from $x), + "+=" => impl_op!(from $x => add($xx, $yy)), + "-=" => impl_op!(from $x => subtract($xx, $yy)), + "*=" => impl_op!(from $x => multiply($xx, $yy)), + "/=" => impl_op!(from $x => divide($xx, $yy)), + "%=" => impl_op!(from $x => modulo($xx, $yy)), _ => return None, } } else { @@ -522,14 +535,14 @@ pub fn get_builtin_op_assignment_fn( use crate::packages::arithmetic::arith_basic::INT::functions::*; match op { - "+=" => impl_op!(add(as_int, as_int) -> INT), - "-=" => impl_op!(subtract(as_int, as_int) -> INT), - "*=" => impl_op!(multiply(as_int, as_int) -> INT), - "/=" => impl_op!(divide(as_int, as_int) -> INT), - "%=" => impl_op!(modulo(as_int, as_int) -> INT), - "**=" => impl_op!(power(as_int, as_int) -> INT), - ">>=" => impl_op!(shift_right(as_int, as_int) -> INT), - "<<=" => impl_op!(shift_left(as_int, as_int) -> INT), + "+=" => impl_op!(INT => add(as_int, as_int)), + "-=" => impl_op!(INT => subtract(as_int, as_int)), + "*=" => impl_op!(INT => multiply(as_int, as_int)), + "/=" => impl_op!(INT => divide(as_int, as_int)), + "%=" => impl_op!(INT => modulo(as_int, as_int)), + "**=" => impl_op!(INT => power(as_int, as_int)), + ">>=" => impl_op!(INT => shift_right(as_int, as_int)), + "<<=" => impl_op!(INT => shift_left(as_int, as_int)), _ => (), } } else { @@ -539,7 +552,7 @@ pub fn get_builtin_op_assignment_fn( "*=" => impl_op!(INT *= as_int), "/=" => impl_op!(INT /= as_int), "%=" => impl_op!(INT %= as_int), - "**=" => impl_op!(as_int.pow(as_int as u32) -> INT), + "**=" => impl_op!(INT => as_int.pow(as_int as u32)), ">>=" => impl_op!(INT >>= as_int), "<<=" => impl_op!(INT <<= as_int), _ => (), diff --git a/src/fn_call.rs b/src/fn_call.rs index a9e4c009..18d11447 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -176,7 +176,7 @@ impl Engine { /// 3) Global modules - packages /// 4) Imported modules - functions marked with global namespace /// 5) Global sub-modules - functions marked with global namespace - #[inline] + #[inline(always)] fn resolve_function<'s>( &self, mods: &Imports, @@ -831,7 +831,7 @@ impl Engine { /// Evaluate a list of statements with no `this` pointer. /// This is commonly used to evaluate a list of statements in an [`AST`] or a script function body. - #[inline] + #[inline(always)] pub(crate) fn eval_global_statements( &self, scope: &mut Scope, diff --git a/src/fn_native.rs b/src/fn_native.rs index 11801050..9296da62 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -18,11 +18,6 @@ use crate::{ Position, RhaiResult, }; -#[cfg(not(feature = "sync"))] -use crate::stdlib::rc::Rc; -#[cfg(feature = "sync")] -use crate::stdlib::sync::Arc; - /// Trait that maps to `Send + Sync` only under the `sync` feature. #[cfg(feature = "sync")] pub trait SendSync: Send + Sync {} @@ -39,19 +34,19 @@ impl SendSync for T {} /// Immutable reference-counted container. #[cfg(not(feature = "sync"))] -pub type Shared = Rc; +pub use crate::stdlib::rc::Rc as Shared; /// Immutable reference-counted container. #[cfg(feature = "sync")] -pub type Shared = Arc; +pub use crate::stdlib::sync::Arc as Shared; /// Synchronized shared object. #[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "sync"))] -pub type Locked = crate::stdlib::cell::RefCell; +pub use crate::stdlib::cell::RefCell as Locked; /// Synchronized shared object. #[cfg(not(feature = "no_closure"))] #[cfg(feature = "sync")] -pub type Locked = crate::stdlib::sync::RwLock; +pub use crate::stdlib::sync::RwLock as Locked; /// Context of a native Rust function call. #[derive(Debug, Copy, Clone)] @@ -215,10 +210,7 @@ impl<'a> NativeCallContext<'a> { /// If the resource is shared (i.e. has other outstanding references), a cloned copy is used. #[inline(always)] pub fn shared_make_mut(value: &mut Shared) -> &mut T { - #[cfg(not(feature = "sync"))] - return Rc::make_mut(value); - #[cfg(feature = "sync")] - return Arc::make_mut(value); + Shared::make_mut(value) } /// Consume a [`Shared`] resource if is unique (i.e. not shared), or clone it otherwise. @@ -230,10 +222,7 @@ pub fn shared_take_or_clone(value: Shared) -> T { /// Consume a [`Shared`] resource if is unique (i.e. not shared). #[inline(always)] pub fn shared_try_take(value: Shared) -> Result> { - #[cfg(not(feature = "sync"))] - return Rc::try_unwrap(value); - #[cfg(feature = "sync")] - return Arc::try_unwrap(value); + Shared::try_unwrap(value) } /// Consume a [`Shared`] resource, assuming that it is unique (i.e. not shared). diff --git a/src/fn_register.rs b/src/fn_register.rs index bba1e46d..48ed4bab 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -183,7 +183,7 @@ macro_rules! def_register { RET: Variant + Clone > RegisterFn for Engine { - #[inline] + #[inline(always)] fn register_fn(&mut self, name: &str, f: FN) -> &mut Self { self.global_namespace.set_fn(name, FnNamespace::Global, FnAccess::Public, None, &[$(map_type_id::<$par>()),*], @@ -198,7 +198,7 @@ macro_rules! def_register { FN: Fn($($param),*) -> RhaiResult + SendSync + 'static, > RegisterResultFn for Engine { - #[inline] + #[inline(always)] fn register_result_fn(&mut self, name: &str, f: FN) -> &mut Self { self.global_namespace.set_fn(name, FnNamespace::Global, FnAccess::Public, None, &[$(map_type_id::<$par>()),*], diff --git a/src/lib.rs b/src/lib.rs index 72a8c881..94652bed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -125,7 +125,7 @@ pub type FLOAT = f32; pub use ast::{FnAccess, ScriptFnMetadata, AST}; pub use dynamic::Dynamic; pub use engine::{Engine, EvalContext}; -pub use fn_native::{FnPtr, NativeCallContext, Shared}; +pub use fn_native::{FnPtr, NativeCallContext}; pub use fn_register::{RegisterFn, RegisterResultFn}; pub use module::{FnNamespace, Module}; pub use parse_error::{LexError, ParseError, ParseErrorType}; @@ -135,6 +135,9 @@ pub use syntax::Expression; pub use token::Position; pub use utils::ImmutableString; +/// Alias to [`Rc`][std::rc::Rc] or [`Arc`][std::sync::Arc] depending on the `sync` feature flag. +pub use fn_native::Shared; + #[cfg(not(feature = "no_closure"))] use fn_native::Locked; diff --git a/src/module/mod.rs b/src/module/mod.rs index 695f8565..0c3a0041 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -598,7 +598,7 @@ impl Module { /// let hash = module.set_fn_0("calc", || Ok(42_i64)); /// assert!(module.contains_fn(hash, true)); /// ``` - #[inline] + #[inline(always)] pub fn contains_fn(&self, hash_fn: NonZeroU64, public_only: bool) -> bool { if public_only { self.functions diff --git a/src/packages/iter_basic.rs b/src/packages/iter_basic.rs index 3d927e9f..2246a1d7 100644 --- a/src/packages/iter_basic.rs +++ b/src/packages/iter_basic.rs @@ -1,10 +1,15 @@ use crate::dynamic::Variant; -use crate::stdlib::{ - boxed::Box, - ops::{Add, Range}, - string::ToString, -}; -use crate::{def_package, EvalAltResult, Position, INT}; +use crate::stdlib::{boxed::Box, ops::Range}; +use crate::{def_package, EvalAltResult, INT}; + +#[cfg(not(feature = "unchecked"))] +use crate::stdlib::string::ToString; + +#[cfg(not(feature = "unchecked"))] +use num_traits::{CheckedAdd as Add, CheckedSub as Sub}; + +#[cfg(feature = "unchecked")] +use crate::stdlib::ops::{Add, Sub}; fn get_range(from: T, to: T) -> Result, Box> { Ok(from..to) @@ -14,30 +19,35 @@ fn get_range(from: T, to: T) -> Result, Box(T, T, T) where - for<'a> &'a T: Add<&'a T, Output = T>, - T: Variant + Clone + PartialOrd; + T: Variant + Copy + PartialOrd + Add + Sub; impl StepRange where - for<'a> &'a T: Add<&'a T, Output = T>, - T: Variant + Clone + PartialOrd, + T: Variant + Copy + PartialOrd + Add + Sub, { pub fn new(from: T, to: T, step: T) -> Result> { - if &from + &step == from { - Err(Box::new(EvalAltResult::ErrorArithmetic( - "invalid step value".to_string(), - Position::NONE, - ))) - } else { - Ok(Self(from, to, step)) + #[cfg(not(feature = "unchecked"))] + if let Some(r) = from.checked_add(&step) { + if r == from { + return Err(Box::new(EvalAltResult::ErrorInFunctionCall( + "range".to_string(), + "".to_string(), + Box::new(EvalAltResult::ErrorArithmetic( + "step value cannot be zero".to_string(), + crate::Position::NONE, + )), + crate::Position::NONE, + ))); + } } + + Ok(Self(from, to, step)) } } impl Iterator for StepRange where - for<'a> &'a T: Add<&'a T, Output = T>, - T: Variant + Clone + PartialOrd, + T: Variant + Copy + PartialOrd + Add + Sub, { type Item = T; @@ -45,29 +55,90 @@ where if self.0 == self.1 { None } else if self.0 < self.1 { - let v = self.0.clone(); - let n = self.0.add(&self.2); - self.0 = if n >= self.1 { self.1.clone() } else { n }; - Some(v) + #[cfg(not(feature = "unchecked"))] + let diff1 = if let Some(diff) = self.1.checked_sub(&self.0) { + diff + } else { + return None; + }; + #[cfg(feature = "unchecked")] + let diff1 = self.1 - self.0; + + let v = self.0; + + #[cfg(not(feature = "unchecked"))] + let n = if let Some(num) = self.0.checked_add(&self.2) { + num + } else { + return None; + }; + #[cfg(feature = "unchecked")] + let n = self.0 + self.2; + + #[cfg(not(feature = "unchecked"))] + let diff2 = if let Some(diff) = self.1.checked_sub(&n) { + diff + } else { + return None; + }; + #[cfg(feature = "unchecked")] + let diff2 = self.1 - n; + + if diff2 >= diff1 { + None + } else { + self.0 = if n >= self.1 { self.1 } else { n }; + Some(v) + } } else { - let v = self.0.clone(); - let n = self.0.add(&self.2); - self.0 = if n <= self.1 { self.1.clone() } else { n }; - Some(v) + #[cfg(not(feature = "unchecked"))] + let diff1 = if let Some(diff) = self.0.checked_sub(&self.1) { + diff + } else { + return None; + }; + #[cfg(feature = "unchecked")] + let diff1 = self.0 - self.1; + + let v = self.0; + + #[cfg(not(feature = "unchecked"))] + let n = if let Some(num) = self.0.checked_add(&self.2) { + num + } else { + return None; + }; + #[cfg(feature = "unchecked")] + let n = self.0 + self.2; + + #[cfg(not(feature = "unchecked"))] + let diff2 = if let Some(diff) = n.checked_sub(&self.1) { + diff + } else { + return None; + }; + #[cfg(feature = "unchecked")] + let diff2 = n - self.1; + + if diff2 >= diff1 { + None + } else { + self.0 = if n <= self.1 { self.1 } else { n }; + Some(v) + } } } } fn get_step_range(from: T, to: T, step: T) -> Result, Box> where - for<'a> &'a T: Add<&'a T, Output = T>, - T: Variant + Clone + PartialOrd, + T: Variant + Copy + PartialOrd + Add + Sub, { StepRange::::new(from, to, step) } macro_rules! reg_range { - ($lib:expr, $x:expr, $( $y:ty ),*) => ( + ($lib:ident | $x:expr => $( $y:ty ),*) => { $( $lib.set_iterator::>(); let hash = $lib.set_fn_2($x, get_range::<$y>); @@ -77,11 +148,8 @@ macro_rules! reg_range { concat!("Iterator") ]); )* - ) -} - -macro_rules! reg_stepped_range { - ($lib:expr, $x:expr, $( $y:ty ),*) => ( + }; + ($lib:ident | step $x:expr => $( $y:ty ),*) => { $( $lib.set_iterator::>(); let hash = $lib.set_fn_3($x, get_step_range::<$y>); @@ -92,31 +160,98 @@ macro_rules! reg_stepped_range { concat!("Iterator") ]); )* - ) + }; } def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, { - reg_range!(lib, "range", INT); + reg_range!(lib | "range" => INT); #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] { - reg_range!(lib, "range", i8, u8, i16, u16, i32, u32, i64, u64); + reg_range!(lib | "range" => i8, u8, i16, u16, i32, u32, i64, u64); if cfg!(not(target_arch = "wasm32")) { - reg_range!(lib, "range", i128, u128); + reg_range!(lib | "range" => i128, u128); } } - reg_stepped_range!(lib, "range", INT); + reg_range!(lib | step "range" => INT); #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] { - reg_stepped_range!(lib, "range", i8, u8, i16, u16, i32, u32, i64, u64); + reg_range!(lib | step "range" => i8, u8, i16, u16, i32, u32, i64, u64); if cfg!(not(target_arch = "wasm32")) { - reg_stepped_range!(lib, "range", i128, u128); + reg_range!(lib | step "range" => i128, u128); } } + + #[cfg(feature = "decimal")] + { + use rust_decimal::{ + prelude::{One, Zero}, + Decimal, + }; + + #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] + struct StepDecimalRange(Decimal, Decimal, Decimal); + + impl StepDecimalRange { + pub fn new(from: Decimal, to: Decimal, step: Decimal) -> Result> { + #[cfg(not(feature = "unchecked"))] + if step.is_zero() { + use crate::stdlib::string::ToString; + + return Err(Box::new(EvalAltResult::ErrorInFunctionCall("range".to_string(), "".to_string(), + Box::new(EvalAltResult::ErrorArithmetic("step value cannot be zero".to_string(), crate::Position::NONE)), + crate::Position::NONE, + ))); + } + + Ok(Self(from, to, step)) + } + } + + impl Iterator for StepDecimalRange { + type Item = Decimal; + + fn next(&mut self) -> Option { + if self.0 == self.1 { + None + } else if self.0 < self.1 { + #[cfg(not(feature = "unchecked"))] + if self.2.is_sign_negative() { + return None; + } + + let v = self.0; + let n = self.0 + self.2; + + self.0 = if n >= self.1 { self.1 } else { n }; + Some(v) + } else { + #[cfg(not(feature = "unchecked"))] + if self.2.is_sign_positive() { + return None; + } + + let v = self.0; + let n = self.0 + self.2; + + self.0 = if n <= self.1 { self.1 } else { n }; + Some(v) + } + } + } + + lib.set_iterator::(); + + let hash = lib.set_fn_2("range", |from, to| StepDecimalRange::new(from, to, Decimal::one())); + lib.update_fn_metadata(hash, &["from: Decimal", "to: Decimal", "Iterator"]); + + let hash = lib.set_fn_3("range", |from, to, step| StepDecimalRange::new(from, to, step)); + lib.update_fn_metadata(hash, &["from: Decimal", "to: Decimal", "step: Decimal", "Iterator"]); + } }); diff --git a/src/packages/logic.rs b/src/packages/logic.rs index e6b6af54..6a2cbe68 100644 --- a/src/packages/logic.rs +++ b/src/packages/logic.rs @@ -3,9 +3,6 @@ use crate::def_package; use crate::plugin::*; -#[cfg(feature = "decimal")] -use rust_decimal::Decimal; - #[cfg(any( not(feature = "no_float"), all(not(feature = "only_i32"), not(feature = "only_i64")) @@ -59,12 +56,6 @@ def_package!(crate:LogicPackage:"Logical operators.", lib, { combine_with_exported_module!(lib, "f64", f64_functions); } - #[cfg(feature = "decimal")] - { - reg_functions!(lib += decimal; Decimal); - combine_with_exported_module!(lib, "decimal", decimal_functions); - } - set_exported_fn!(lib, "!", not); }); @@ -91,9 +82,6 @@ gen_cmp_functions!(float => f32); #[cfg(feature = "f32_float")] gen_cmp_functions!(float => f64); -#[cfg(feature = "decimal")] -gen_cmp_functions!(decimal => Decimal); - #[cfg(not(feature = "no_float"))] #[export_module] mod f32_functions { @@ -203,59 +191,3 @@ mod f64_functions { (x as f64) <= (y as f64) } } - -#[cfg(feature = "decimal")] -#[export_module] -mod decimal_functions { - use crate::INT; - use rust_decimal::Decimal; - - #[rhai_fn(name = "==")] - pub fn eq_if(x: INT, y: Decimal) -> bool { - Decimal::from(x) == y - } - #[rhai_fn(name = "==")] - pub fn eq_fi(x: Decimal, y: INT) -> bool { - x == Decimal::from(y) - } - #[rhai_fn(name = "!=")] - pub fn neq_if(x: INT, y: Decimal) -> bool { - Decimal::from(x) != y - } - #[rhai_fn(name = "!=")] - pub fn neq_fi(x: Decimal, y: INT) -> bool { - x != Decimal::from(y) - } - #[rhai_fn(name = ">")] - pub fn gt_if(x: INT, y: Decimal) -> bool { - Decimal::from(x) > y - } - #[rhai_fn(name = ">")] - pub fn gt_fi(x: Decimal, y: INT) -> bool { - x > Decimal::from(y) - } - #[rhai_fn(name = ">=")] - pub fn gte_if(x: INT, y: Decimal) -> bool { - Decimal::from(x) >= y - } - #[rhai_fn(name = ">=")] - pub fn gte_fi(x: Decimal, y: INT) -> bool { - x >= Decimal::from(y) - } - #[rhai_fn(name = "<")] - pub fn lt_if(x: INT, y: Decimal) -> bool { - Decimal::from(x) < y - } - #[rhai_fn(name = "<")] - pub fn lt_fi(x: Decimal, y: INT) -> bool { - x < Decimal::from(y) - } - #[rhai_fn(name = "<=")] - pub fn lte_if(x: INT, y: Decimal) -> bool { - Decimal::from(x) <= y - } - #[rhai_fn(name = "<=")] - pub fn lte_fi(x: Decimal, y: INT) -> bool { - x <= Decimal::from(y) - } -} diff --git a/src/parser.rs b/src/parser.rs index bfef0a54..96e1da4e 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -105,7 +105,7 @@ impl<'e> ParseState<'e> { /// i.e. the top element of the [`ParseState`] is offset 1. /// /// Return `None` when the variable name is not found in the `stack`. - #[inline] + #[inline(always)] fn access_var(&mut self, name: &str, _pos: Position) -> Option { let mut barrier = false; @@ -230,7 +230,7 @@ impl Expr { /// Convert a [`Variable`][Expr::Variable] into a [`Property`][Expr::Property]. /// All other variants are untouched. #[cfg(not(feature = "no_object"))] - #[inline] + #[inline(always)] fn into_property(self, state: &mut ParseState) -> Self { match self { Self::Variable(x) if x.1.is_none() => { diff --git a/src/result.rs b/src/result.rs index 1e6ec07b..1376dbcb 100644 --- a/src/result.rs +++ b/src/result.rs @@ -153,13 +153,13 @@ impl fmt::Display for EvalAltResult { #[cfg(not(feature = "no_function"))] Self::ErrorInFunctionCall(s, src, err, _) if crate::engine::is_anonymous_fn(s) => { - write!(f, "{}, in call to closure", err)?; + write!(f, "{} in call to closure", err)?; if !src.is_empty() { write!(f, " @ '{}'", src)?; } } Self::ErrorInFunctionCall(s, src, err, _) => { - write!(f, "{}, in call to function {}", err, s)?; + write!(f, "{} in call to function {}", err, s)?; if !src.is_empty() { write!(f, " @ '{}'", src)?; } diff --git a/src/scope.rs b/src/scope.rs index 4adda3e1..2071a75d 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -237,7 +237,7 @@ impl<'a> Scope<'a> { self.push_dynamic_value(name, AccessMode::ReadOnly, value) } /// Add (push) a new entry with a [`Dynamic`] value to the [`Scope`]. - #[inline] + #[inline(always)] pub(crate) fn push_dynamic_value( &mut self, name: impl Into>, @@ -420,7 +420,7 @@ impl<'a> Scope<'a> { } /// Clone the [`Scope`], keeping only the last instances of each variable name. /// Shadowed variables are omitted in the copy. - #[inline] + #[inline(always)] pub(crate) fn clone_visible(&self) -> Self { let mut entries: Self = Default::default(); diff --git a/src/token.rs b/src/token.rs index 31c87f30..5cd1cc5c 100644 --- a/src/token.rs +++ b/src/token.rs @@ -1029,7 +1029,7 @@ fn scan_block_comment( /// # Volatile API /// /// This function is volatile and may change. -#[inline] +#[inline(always)] pub fn get_next_token( stream: &mut impl InputStream, state: &mut TokenizeState, @@ -1856,7 +1856,7 @@ impl Engine { self.lex_raw(input, Some(map)) } /// Tokenize an input text stream with an optional mapping function. - #[inline] + #[inline(always)] pub(crate) fn lex_raw<'a>( &'a self, input: impl IntoIterator, diff --git a/src/unsafe.rs b/src/unsafe.rs index 2cef9997..772eb35a 100644 --- a/src/unsafe.rs +++ b/src/unsafe.rs @@ -49,7 +49,7 @@ pub fn unsafe_cast_box(item: Box) -> Result, B /// /// Force-casting a local variable's lifetime to the current [`Scope`][crate::Scope]'s larger lifetime saves /// on allocations and string cloning, thus avoids us having to maintain a chain of [`Scope`][crate::Scope]'s. -#[inline] +#[inline(always)] pub fn unsafe_cast_var_name_to_lifetime<'s>(name: &str) -> &'s str { // WARNING - force-cast the variable name into the scope's lifetime to avoid cloning it // this is safe because all local variables are cleared at the end of the block diff --git a/src/utils.rs b/src/utils.rs index a6a2a0eb..b84ea2e3 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -34,11 +34,13 @@ impl Hasher for StraightHasher { } #[inline(always)] fn write(&mut self, bytes: &[u8]) { + assert_eq!(bytes.len(), 8, "StraightHasher can only hash u64 values"); + let mut key = [0_u8; 8]; - key.copy_from_slice(&bytes[..8]); // Panics if fewer than 8 bytes + key.copy_from_slice(bytes); // HACK - If it so happens to hash directly to zero (OMG!) then change it to 42... - self.0 = NonZeroU64::new(u64::from_le_bytes(key)) + self.0 = NonZeroU64::new(u64::from_ne_bytes(key)) .unwrap_or_else(|| NonZeroU64::new(42).unwrap()); } } @@ -58,9 +60,8 @@ impl BuildHasher for StraightHasherBuilder { /// Create an instance of the default hasher. #[inline(always)] -pub fn get_hasher() -> impl Hasher { - let s: ahash::AHasher = Default::default(); - s +pub fn get_hasher() -> ahash::AHasher { + Default::default() } /// _(INTERNALS)_ Calculate a [`NonZeroU64`] hash key from a namespace-qualified function name and diff --git a/tests/for.rs b/tests/for.rs index 03a40a89..8827c123 100644 --- a/tests/for.rs +++ b/tests/for.rs @@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, Module, INT}; #[cfg(not(feature = "no_index"))] #[test] -fn test_for_array() -> Result<(), Box> { +fn test_for() -> Result<(), Box> { let engine = Engine::new(); let script = r" @@ -27,6 +27,81 @@ fn test_for_array() -> Result<(), Box> { assert_eq!(engine.eval::(script)?, 35); + assert_eq!( + engine.eval::( + r" + let sum = 0; + for x in range(1, 10, 2) { sum += x; } + sum + " + )?, + 25 + ); + + assert_eq!( + engine.eval::( + r" + let sum = 0; + for x in range(10, 1, 2) { sum += x; } + sum + " + )?, + 0 + ); + + assert_eq!( + engine.eval::( + r" + let sum = 0; + for x in range(1, 10, -2) { sum += x; } + sum + " + )?, + 0 + ); + + assert_eq!( + engine.eval::( + r" + let sum = 0; + for x in range(10, 1, -2) { sum += x; } + sum + " + )?, + 30 + ); + + Ok(()) +} + +#[cfg(not(feature = "unchecked"))] +#[test] +fn test_for_overflow() -> Result<(), Box> { + let engine = Engine::new(); + + #[cfg(not(feature = "only_i32"))] + let script = r" + let sum = 0; + + for x in range(9223372036854775807, 0, 9223372036854775807) { + sum += 1; + } + + sum + "; + #[cfg(feature = "only_i32")] + let script = r" + let sum = 0; + + for x in range(2147483647 , 0, 2147483647 ) { + sum += 1; + } + + sum + "; + + assert_eq!(engine.eval::(script)?, 0); + Ok(()) } diff --git a/tests/switch.rs b/tests/switch.rs index 9f2cca35..537dfd04 100644 --- a/tests/switch.rs +++ b/tests/switch.rs @@ -18,6 +18,10 @@ fn test_switch() -> Result<(), Box> { engine.eval_with_scope::<()>(&mut scope, "switch x { 1 => 123, 2 => 'a' }")?, () ); + assert_eq!( + engine.eval::("let x = timestamp(); switch x { 1 => 123, _ => 42 }")?, + 42 + ); assert_eq!( engine.eval_with_scope::( &mut scope,