diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f328f411..ccff89562 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,10 +11,13 @@ Bug fixes * `NativeCallContext<'_>` (with a lifetime parameter) now parses correctly in the `#[export_module]` macro. This is to allow for `rust_2018_idioms` lints (thanks [`@ltabis`](https://github.com/ltabis) [864](https://github.com/rhaiscript/rhai/issues/864)). * The `sync` feature now works properly in `no-std` builds (thanks [`@misssonder`](https://github.com/misssonder) [874](https://github.com/rhaiscript/rhai/pull/874)). +* More data-race conditions are caught and returned as errors instead of panicking. +* Missing `min` and `max` functions where both operands are floats or `Decimal` are added. New features ------------ +* The `break`, `continue`, `return` and `throw` statements can now follow the `??` operator to short-circuit operations where the value is `()`. * A new symbol, `$func$`, is added to custom syntax to allow parsing of anonymous functions. diff --git a/benches/eval_array.rs b/benches/eval_array.rs index 397defc04..7287f2224 100644 --- a/benches/eval_array.rs +++ b/benches/eval_array.rs @@ -67,7 +67,7 @@ fn bench_eval_array_loop(bench: &mut Bencher) { let script = " let list = []; - for i in 0..1_888 { + for i in 0..10_000 { list.push(i); } diff --git a/benches/eval_expression.rs b/benches/eval_expression.rs index 35114effc..dfcdbb06e 100644 --- a/benches/eval_expression.rs +++ b/benches/eval_expression.rs @@ -125,7 +125,7 @@ fn bench_eval_deeply_nested(bench: &mut Bencher) { fn bench_eval_loop_number(bench: &mut Bencher) { let script = " let s = 0; - for x in 0..1_888 { + for x in 0..10000 { s += 1; } "; @@ -142,7 +142,7 @@ fn bench_eval_loop_number(bench: &mut Bencher) { fn bench_eval_loop_strings_build(bench: &mut Bencher) { let script = r#" let s; - for x in 0..1_888 { + for x in 0..10000 { s = "hello, world!" + "hello, world!"; } "#; @@ -159,7 +159,7 @@ fn bench_eval_loop_strings_build(bench: &mut Bencher) { fn bench_eval_loop_strings_no_build(bench: &mut Bencher) { let script = r#" let s; - for x in 0..1_888 { + for x in 0..10000 { s = "hello" + ""; } "#; diff --git a/benches/iterations.rs b/benches/iterations.rs index 33ac414f4..197ca4767 100644 --- a/benches/iterations.rs +++ b/benches/iterations.rs @@ -10,7 +10,7 @@ use test::Bencher; fn bench_iterations_1000(bench: &mut Bencher) { let script = " let x = 1_000; - + while x > 0 { x -= 1; } diff --git a/src/api/events.rs b/src/api/events.rs index 3f426ffe0..b47eae074 100644 --- a/src/api/events.rs +++ b/src/api/events.rs @@ -356,6 +356,7 @@ impl Engine { /// ``` /// # fn main() -> Result<(), Box> { /// # use rhai::{Engine, Dynamic, EvalAltResult, Position}; + /// # use std::convert::TryInto; /// let mut engine = Engine::new(); /// /// engine.on_invalid_array_index(|arr, index, _| match index @@ -365,7 +366,7 @@ impl Engine { /// arr.push((42_i64).into()); /// // Return a mutable reference to an element /// let value_ref = arr.last_mut().unwrap(); - /// Ok(value_ref.into()) + /// value_ref.try_into() /// } /// 100 => { /// let value = Dynamic::from(100_i64); @@ -433,6 +434,7 @@ impl Engine { /// ``` /// # fn main() -> Result<(), Box> { /// # use rhai::{Engine, Dynamic, EvalAltResult, Position}; + /// # use std::convert::TryInto; /// let mut engine = Engine::new(); /// /// engine.on_map_missing_property(|map, prop, _| match prop @@ -442,7 +444,7 @@ impl Engine { /// map.insert("y".into(), (42_i64).into()); /// // Return a mutable reference to an element /// let value_ref = map.get_mut("y").unwrap(); - /// Ok(value_ref.into()) + /// value_ref.try_into() /// } /// "z" => { /// // Return a temporary value (not a reference) diff --git a/src/eval/chaining.rs b/src/eval/chaining.rs index 0053d07fa..d58dded33 100644 --- a/src/eval/chaining.rs +++ b/src/eval/chaining.rs @@ -9,9 +9,9 @@ use crate::{ calc_fn_hash, Dynamic, Engine, ExclusiveRange, FnArgsVec, InclusiveRange, OnceCell, Position, RhaiResult, RhaiResultOf, Scope, ERR, }; -use std::hash::Hash; #[cfg(feature = "no_std")] use std::prelude::v1::*; +use std::{convert::TryInto, hash::Hash}; /// Function call hashes to index getters and setters. static INDEXER_HASHES: OnceCell<(u64, u64)> = OnceCell::new(); @@ -146,7 +146,7 @@ impl Engine { } }; - Ok(arr.get_mut(arr_idx).map(Target::from).unwrap()) + arr.get_mut(arr_idx).unwrap().try_into() } #[cfg(not(feature = "no_index"))] @@ -192,7 +192,7 @@ impl Engine { } if let Some(value) = map.get_mut(index.as_str()) { - Ok(Target::from(value)) + value.try_into() } else if self.fail_on_invalid_map_property() { Err(ERR::ErrorPropertyNotFound(index.to_string(), idx_pos).into()) } else { @@ -508,7 +508,7 @@ impl Engine { this_ptr.map_or_else( || Err(ERR::ErrorUnboundThis(*var_pos).into()), |this_ptr| { - let target = &mut this_ptr.into(); + let target = &mut this_ptr.try_into()?; let scope = Some(scope); self.eval_dot_index_chain_raw( global, caches, scope, None, lhs, expr, target, rhs, idx_values, @@ -969,7 +969,7 @@ impl Engine { })?; { - let orig_val = &mut (&mut orig_val).into(); + let orig_val = &mut (&mut orig_val).try_into()?; self.eval_op_assignment( global, caches, op_info, root, orig_val, new_val, @@ -1139,7 +1139,7 @@ impl Engine { _ => Err(err), })?; - let val = &mut (&mut val).into(); + let val = &mut (&mut val).try_into()?; let (result, may_be_changed) = self.eval_dot_index_chain_raw( global, caches, s, _this_ptr, root, rhs, val, &x.rhs, diff --git a/src/eval/expr.rs b/src/eval/expr.rs index d6291b39f..bd904f823 100644 --- a/src/eval/expr.rs +++ b/src/eval/expr.rs @@ -7,7 +7,7 @@ use crate::types::dynamic::AccessMode; use crate::{Dynamic, Engine, RhaiResult, RhaiResultOf, Scope, SmartString, ERR}; #[cfg(feature = "no_std")] use std::prelude::v1::*; -use std::{fmt::Write, num::NonZeroUsize}; +use std::{convert::TryInto, fmt::Write, num::NonZeroUsize}; impl Engine { /// Search for a module within an imports stack. @@ -142,7 +142,7 @@ impl Engine { let val = scope.get_mut_by_index(index); - Ok(val.into()) + val.try_into() } /// Search for a variable within the scope or within imports, /// depending on whether the variable name is namespace-qualified. diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index c7886cc00..247c66f75 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -8,9 +8,12 @@ use crate::func::{get_builtin_op_assignment_fn, get_hasher}; use crate::tokenizer::Token; use crate::types::dynamic::{AccessMode, Union}; use crate::{Dynamic, Engine, RhaiResult, RhaiResultOf, Scope, VarDefInfo, ERR, INT}; -use std::hash::{Hash, Hasher}; #[cfg(feature = "no_std")] use std::prelude::v1::*; +use std::{ + convert::TryInto, + hash::{Hash, Hasher}, +}; impl Engine { /// If the value is a string, intern it. @@ -310,7 +313,7 @@ impl Engine { self.track_operation(global, lhs.position())?; - let target = &mut this_ptr.unwrap().into(); + let target = &mut this_ptr.unwrap().try_into()?; self.eval_op_assignment(global, caches, op_info, lhs, target, rhs_val)?; } diff --git a/src/eval/target.rs b/src/eval/target.rs index 298bc733e..ac3e49bf1 100644 --- a/src/eval/target.rs +++ b/src/eval/target.rs @@ -1,9 +1,12 @@ //! Type to hold a mutable reference to the target of an evaluation. -use crate::{Dynamic, Position, RhaiResultOf}; -use std::borrow::{Borrow, BorrowMut}; +use crate::{Dynamic, EvalAltResult, Position, RhaiError, RhaiResultOf}; #[cfg(feature = "no_std")] use std::prelude::v1::*; +use std::{ + borrow::{Borrow, BorrowMut}, + convert::TryFrom, +}; /// Calculate an offset+len pair given an actual length of the underlying array. /// @@ -413,21 +416,25 @@ impl<'a> Target<'a> { } } -impl<'a> From<&'a mut Dynamic> for Target<'a> { +impl<'a> TryFrom<&'a mut Dynamic> for Target<'a> { + type Error = RhaiError; + #[inline] - fn from(value: &'a mut Dynamic) -> Self { + fn try_from(value: &'a mut Dynamic) -> Result { #[cfg(not(feature = "no_closure"))] if value.is_shared() { // Cloning is cheap for a shared value let shared_value = value.clone(); - let guard = value.write_lock::().unwrap(); - return Self::SharedValue { + let Some(guard) = value.write_lock::() else { + return Err(EvalAltResult::ErrorDataRace(String::new(), Position::NONE).into()); + }; + return Ok(Self::SharedValue { guard, shared_value, - }; + }); } - Self::RefMut(value) + Ok(Self::RefMut(value)) } } diff --git a/src/packages/logic.rs b/src/packages/logic.rs index 946e4e1aa..1fa6a911e 100644 --- a/src/packages/logic.rs +++ b/src/packages/logic.rs @@ -115,6 +115,14 @@ mod min_max_functions { mod float_functions { use crate::INT; + #[rhai_fn(name = "max")] + pub fn max_ff_32(x: f32, y: f32) -> f32 { + if x >= y { + x + } else { + y + } + } #[rhai_fn(name = "max")] pub fn max_if_32(x: INT, y: f32) -> f32 { let (x, y) = (x as f32, y); @@ -134,6 +142,14 @@ mod float_functions { } } #[rhai_fn(name = "min")] + pub fn min_ff_32(x: f32, y: f32) -> f32 { + if x <= y { + x + } else { + y + } + } + #[rhai_fn(name = "min")] pub fn min_if_32(x: INT, y: f32) -> f32 { let (x, y) = (x as f32, y); if x <= y { @@ -152,6 +168,14 @@ mod float_functions { } } #[rhai_fn(name = "max")] + pub fn max_ff_64(x: f64, y: f64) -> f64 { + if x >= y { + x + } else { + y + } + } + #[rhai_fn(name = "max")] pub fn max_if_64(x: INT, y: f64) -> f64 { let (x, y) = (x as f64, y); if x >= y { @@ -170,6 +194,14 @@ mod float_functions { } } #[rhai_fn(name = "min")] + pub fn min_ff_64(x: f64, y: f64) -> f64 { + if x <= y { + x + } else { + y + } + } + #[rhai_fn(name = "min")] pub fn min_if_64(x: INT, y: f64) -> f64 { let (x, y) = (x as f64, y); if x <= y { @@ -419,6 +451,14 @@ mod decimal_functions { use crate::INT; use rust_decimal::Decimal; + #[rhai_fn(name = "max")] + pub fn max_dd(x: Decimal, y: Decimal) -> Decimal { + if x >= y { + x + } else { + y + } + } #[rhai_fn(name = "max")] pub fn max_id(x: INT, y: Decimal) -> Decimal { let x = x.into(); @@ -438,6 +478,14 @@ mod decimal_functions { } } #[rhai_fn(name = "min")] + pub fn min_dd(x: Decimal, y: Decimal) -> Decimal { + if x <= y { + x + } else { + y + } + } + #[rhai_fn(name = "min")] pub fn min_id(x: INT, y: Decimal) -> Decimal { let x = x.into(); if x <= y { diff --git a/src/parser.rs b/src/parser.rs index 048d44aa6..462b90057 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2265,20 +2265,31 @@ impl Engine { // Parse the RHS let rhs = match op_token { + Token::DoubleQuestion + if matches!( + state.input.peek().unwrap().0, + Token::Break | Token::Continue | Token::Return | Token::Throw + ) => + { + let stmt = self.parse_stmt(state, settings)?; + let block: StmtBlock = stmt.into(); + Expr::Stmt(block.into()) + } // [xxx..] | (xxx..) | {xxx..} | xxx.., | xxx..; | xxx.. => // [xxx..=] | (xxx..=) | {xxx..=} | xxx..=, | xxx..=; | xxx..= => - Token::ExclusiveRange | Token::InclusiveRange => { - let (next_op, next_pos) = state.input.peek().unwrap(); - - match next_op { + Token::ExclusiveRange | Token::InclusiveRange + if matches!( + state.input.peek().unwrap().0, Token::RightBracket - | Token::RightParen - | Token::RightBrace - | Token::Comma - | Token::SemiColon - | Token::DoubleArrow => Expr::Unit(*next_pos), - _ => self.parse_unary(state, settings)?, - } + | Token::RightParen + | Token::RightBrace + | Token::Comma + | Token::SemiColon + | Token::DoubleArrow + ) => + { + let (_, next_pos) = state.input.peek().unwrap(); + Expr::Unit(*next_pos) } _ => self.parse_unary(state, settings)?, }; diff --git a/src/types/error.rs b/src/types/error.rs index 99ae18721..9ae277185 100644 --- a/src/types/error.rs +++ b/src/types/error.rs @@ -162,6 +162,7 @@ impl fmt::Display for EvalAltResult { Self::ErrorIndexNotFound(s, ..) => write!(f, "Invalid index: {s}")?, Self::ErrorFunctionNotFound(s, ..) => write!(f, "Function not found: {s}")?, Self::ErrorModuleNotFound(s, ..) => write!(f, "Module not found: {s}")?, + Self::ErrorDataRace(s, ..) if s.is_empty() => write!(f, "Data race detected")?, Self::ErrorDataRace(s, ..) => write!(f, "Data race detected on variable '{s}'")?, Self::ErrorDotExpr(s, ..) if s.is_empty() => f.write_str("Malformed dot expression")?, diff --git a/tests/arrays.rs b/tests/arrays.rs index 7b58baab6..35e15d250 100644 --- a/tests/arrays.rs +++ b/tests/arrays.rs @@ -1,6 +1,6 @@ #![cfg(not(feature = "no_index"))] use rhai::{Array, Dynamic, Engine, EvalAltResult, ParseErrorType, Position, INT}; -use std::iter::FromIterator; +use std::{convert::TryInto, iter::FromIterator}; #[test] fn test_arrays() { @@ -509,7 +509,7 @@ fn test_array_invalid_index_callback() { engine.on_invalid_array_index(|arr, index, _| match index { -100 => { arr.push((42 as INT).into()); - Ok(arr.last_mut().unwrap().into()) + arr.last_mut().unwrap().try_into() } 100 => Ok(Dynamic::from(100 as INT).into()), _ => Err(EvalAltResult::ErrorArrayBounds(arr.len(), index, Position::NONE).into()), diff --git a/tests/looping.rs b/tests/looping.rs index dc142ee8d..b891d67ba 100644 --- a/tests/looping.rs +++ b/tests/looping.rs @@ -28,6 +28,20 @@ fn test_loop() { 21 ); + assert_eq!( + engine + .eval::( + " + for n in 0..10 { + let x = if n <= 5 { n }; + x ?? break 42; + } + " + ) + .unwrap(), + 42 + ); + assert_eq!(*engine.compile("let x = 0; break;").unwrap_err().err_type(), ParseErrorType::LoopBreak); #[cfg(not(feature = "no_function"))] diff --git a/tests/maps.rs b/tests/maps.rs index e2391841b..fed8b844c 100644 --- a/tests/maps.rs +++ b/tests/maps.rs @@ -1,5 +1,6 @@ #![cfg(not(feature = "no_object"))] use rhai::{Dynamic, Engine, EvalAltResult, Map, ParseErrorType, Position, Scope, INT}; +use std::convert::TryInto; #[test] fn test_map_indexing() { @@ -264,7 +265,7 @@ fn test_map_missing_property_callback() { engine.on_map_missing_property(|map, prop, _| match prop { "x" => { map.insert("y".into(), (42 as INT).into()); - Ok(map.get_mut("y").unwrap().into()) + map.get_mut("y").unwrap().try_into() } "z" => Ok(Dynamic::from(100 as INT).into()), _ => Err(EvalAltResult::ErrorPropertyNotFound(prop.to_string(), Position::NONE).into()),