diff --git a/CHANGELOG.md b/CHANGELOG.md index d95bc3d0..9340630c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ Rhai Release Notes Version 1.11.0 ============== +New features +------------ + +* A new feature flag, `stable_hash`, is added that forces hashing to be consistent using a fixed seed. + Enhancements ------------ diff --git a/Cargo.toml b/Cargo.toml index 1034f923..6451af51 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,6 +62,7 @@ metadata = ["serde", "serde_json", "rhai_codegen/metadata", "smartstring/serde"] internals = [] # expose internal data structures debugging = ["internals"] # enable debugging serde = ["dep:serde", "smartstring/serde", "smallvec/serde"] # implement serde for rhai types +stable_hash = [] # perform all hashing with fixed seed value # compiling for no-std no_std = ["no-std-compat", "num-traits/libm", "core-error", "libm", "ahash/compile-time-rng", "hashbrown/ahash-compile-time-rng"] diff --git a/src/func/builtin.rs b/src/func/builtin.rs index 9052a5fe..075706fe 100644 --- a/src/func/builtin.rs +++ b/src/func/builtin.rs @@ -280,7 +280,7 @@ pub fn get_builtin_binary_op_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Option< Token::GreaterThanEqualsTo => Some(impl_op!(FLOAT => $xx >= $yy)), Token::LessThan => Some(impl_op!(FLOAT => $xx < $yy)), Token::LessThanEqualsTo => Some(impl_op!(FLOAT => $xx <= $yy)), - _ => None, + _ => None, }; } }; @@ -308,7 +308,7 @@ pub fn get_builtin_binary_op_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Option< Token::Divide => return Some(impl_op!(from Decimal => divide($xx, $yy))), Token::Modulo => return Some(impl_op!(from Decimal => modulo($xx, $yy))), Token::PowerOf => return Some(impl_op!(from Decimal => power($xx, $yy))), - _ => () + _ => () } #[cfg(feature = "unchecked")] @@ -322,17 +322,17 @@ pub fn get_builtin_binary_op_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Option< Token::Divide => return Some(impl_op!(from Decimal => $xx / $yy)), Token::Modulo => return Some(impl_op!(from Decimal => $xx % $yy)), Token::PowerOf => return Some(impl_op!(from Decimal => $xx.powd($yy))), - _ => () + _ => () } return match op { - Token::EqualsTo => Some(impl_op!(from Decimal => $xx == $yy)), - Token::NotEqualsTo => Some(impl_op!(from Decimal => $xx != $yy)), - Token::GreaterThan => Some(impl_op!(from Decimal => $xx > $yy)), - Token::GreaterThanEqualsTo => Some(impl_op!(from Decimal => $xx >= $yy)), - Token::LessThan => Some(impl_op!(from Decimal => $xx < $yy)), - Token::LessThanEqualsTo => Some(impl_op!(from Decimal => $xx <= $yy)), - _ => None + Token::EqualsTo => Some(impl_op!(from Decimal => $xx == $yy)), + Token::NotEqualsTo => Some(impl_op!(from Decimal => $xx != $yy)), + Token::GreaterThan => Some(impl_op!(from Decimal => $xx > $yy)), + Token::GreaterThanEqualsTo => Some(impl_op!(from Decimal => $xx >= $yy)), + Token::LessThan => Some(impl_op!(from Decimal => $xx < $yy)), + Token::LessThanEqualsTo => Some(impl_op!(from Decimal => $xx <= $yy)), + _ => None }; } }; @@ -680,7 +680,7 @@ pub fn get_builtin_op_assignment_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Opt Token::DivideAssign => Some(impl_op!($x /= $yy)), Token::ModuloAssign => Some(impl_op!($x %= $yy)), Token::PowerOfAssign => Some(impl_op!($x => $xx.powf($yy as $x))), - _ => None, + _ => None, }; } } @@ -707,7 +707,7 @@ pub fn get_builtin_op_assignment_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Opt Token::DivideAssign => Some(impl_op!(from $x => divide($xx, $yy))), Token::ModuloAssign => Some(impl_op!(from $x => modulo($xx, $yy))), Token::PowerOfAssign => Some(impl_op!(from $x => power($xx, $yy))), - _ => None, + _ => None, }; #[cfg(feature = "unchecked")] @@ -715,13 +715,13 @@ pub fn get_builtin_op_assignment_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Opt #[cfg(feature = "unchecked")] return match op { - Token::PlusAssign => Some(impl_op!(from $x += $yy)), - Token::MinusAssign => Some(impl_op!(from $x -= $yy)), - Token::MultiplyAssign => Some(impl_op!(from $x *= $yy)), - Token::DivideAssign => Some(impl_op!(from $x /= $yy)), - Token::ModuloAssign => Some(impl_op!(from $x %= $yy)), - Token::PowerOfAssign => Some(impl_op!(from $x => $xx.powd($yy))), - _ => None, + Token::PlusAssign => Some(impl_op!(from $x += $yy)), + Token::MinusAssign => Some(impl_op!(from $x -= $yy)), + Token::MultiplyAssign => Some(impl_op!(from $x *= $yy)), + Token::DivideAssign => Some(impl_op!(from $x /= $yy)), + Token::ModuloAssign => Some(impl_op!(from $x %= $yy)), + Token::PowerOfAssign => Some(impl_op!(from $x => $xx.powd($yy))), + _ => None, }; } }; diff --git a/src/func/hashing.rs b/src/func/hashing.rs index bc29f67b..ae0a57bc 100644 --- a/src/func/hashing.rs +++ b/src/func/hashing.rs @@ -74,7 +74,11 @@ impl BuildHasher for StraightHasherBuilder { #[inline(always)] #[must_use] pub fn get_hasher() -> ahash::AHasher { - ahash::AHasher::default() + if cfg!(feature = "stable_hash") { + ahash::RandomState::with_seeds(42, 999, 123, 0).build_hasher() + } else { + ahash::AHasher::default() + } } /// Calculate a non-zero [`u64`] hash key from a namespace-qualified variable name. diff --git a/src/optimizer.rs b/src/optimizer.rs index 6ab19fcb..c46cf43f 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -254,7 +254,7 @@ fn optimize_stmt_block( }); // Optimize each statement in the block - for stmt in statements.iter_mut() { + for stmt in &mut statements { match stmt { Stmt::Var(x, options, ..) => { if options.contains(ASTFlags::CONSTANT) { @@ -688,7 +688,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b optimize_expr(match_expr, state, false); // Optimize blocks - for b in expressions.iter_mut() { + for b in expressions.as_mut() { optimize_expr(&mut b.condition, state, false); optimize_expr(&mut b.expr, state, false); @@ -1204,7 +1204,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { x.args.iter_mut().for_each(|a| optimize_expr(a, state, false)); // Move constant arguments - for arg in x.args.iter_mut() { + for arg in &mut x.args { match arg { Expr::DynamicConstant(..) | Expr::Unit(..) | Expr::StringConstant(..) | Expr::CharConstant(..) @@ -1254,7 +1254,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { } // id(args ..) or xxx.id(args ..) -> optimize function call arguments - Expr::FnCall(x, ..) | Expr::MethodCall(x, ..) => for arg in x.args.iter_mut() { + Expr::FnCall(x, ..) | Expr::MethodCall(x, ..) => for arg in &mut x.args { optimize_expr(arg, state, false); // Move constant arguments diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 21abbde9..72e2347e 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -834,7 +834,7 @@ pub mod array_functions { return Ok(false); } - for item in array.iter_mut() { + for item in array { if ctx .call_fn_raw(OP_EQUALS, true, false, &mut [item, &mut value.clone()]) .or_else(|err| match *err { diff --git a/src/types/dynamic.rs b/src/types/dynamic.rs index c1fe1dcf..db88b9fa 100644 --- a/src/types/dynamic.rs +++ b/src/types/dynamic.rs @@ -928,7 +928,7 @@ impl Dynamic { #[cfg(not(feature = "no_index"))] Union::Array(ref mut a, _, ref mut access) => { *access = typ; - for v in a.iter_mut() { + for v in a.as_mut() { v.set_access_mode(typ); } } diff --git a/src/types/error.rs b/src/types/error.rs index 822b1a39..ba9b7351 100644 --- a/src/types/error.rs +++ b/src/types/error.rs @@ -126,47 +126,45 @@ impl Error for EvalAltResult {} impl fmt::Display for EvalAltResult { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::ErrorSystem(s, err) => match s.as_str() { - "" => write!(f, "{}", err), - s => write!(f, "{}: {}", s, err), - }?, + Self::ErrorSystem(s, err) if s.is_empty() => write!(f, "{err}")?, + Self::ErrorSystem(s, err) => write!(f, "{s}: {err}")?, - Self::ErrorParsing(p, ..) => write!(f, "Syntax error: {}", p)?, + Self::ErrorParsing(p, ..) => write!(f, "Syntax error: {p}")?, #[cfg(not(feature = "no_function"))] Self::ErrorInFunctionCall(s, src, err, ..) if crate::parser::is_anonymous_fn(s) => { - write!(f, "{} in call to closure", err)?; + write!(f, "{err} in call to closure")?; if !src.is_empty() { - write!(f, " @ '{}'", src)?; + write!(f, " @ '{src}'")?; } } Self::ErrorInFunctionCall(s, src, err, ..) => { - write!(f, "{} in call to function {}", err, s)?; + write!(f, "{err} in call to function {s}")?; if !src.is_empty() { - write!(f, " @ '{}'", src)?; + write!(f, " @ '{src}'")?; } } Self::ErrorInModule(s, err, ..) if s.is_empty() => { - write!(f, "Error in module > {}", err)? + write!(f, "Error in module > {err}")? } - Self::ErrorInModule(s, err, ..) => write!(f, "Error in module '{}' > {}", s, err)?, + Self::ErrorInModule(s, err, ..) => write!(f, "Error in module '{s}' > {err}")?, - Self::ErrorVariableExists(s, ..) => write!(f, "Variable already defined: {}", s)?, - Self::ErrorForbiddenVariable(s, ..) => write!(f, "Forbidden variable name: {}", s)?, - Self::ErrorVariableNotFound(s, ..) => write!(f, "Variable not found: {}", s)?, - Self::ErrorPropertyNotFound(s, ..) => write!(f, "Property not found: {}", s)?, - 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::ErrorVariableExists(s, ..) => write!(f, "Variable already defined: {s}")?, + Self::ErrorForbiddenVariable(s, ..) => write!(f, "Forbidden variable name: {s}")?, + Self::ErrorVariableNotFound(s, ..) => write!(f, "Variable not found: {s}")?, + Self::ErrorPropertyNotFound(s, ..) => write!(f, "Property not found: {s}")?, + 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, ..) => { - write!(f, "Data race detected when accessing variable: {}", s)? + write!(f, "Data race detected when accessing variable: {s}")? } - Self::ErrorDotExpr(s, ..) => match s.as_str() { - "" => f.write_str("Malformed dot expression"), - s => f.write_str(s), - }?, - Self::ErrorIndexingType(s, ..) => write!(f, "Indexer unavailable: {}", s)?, + + Self::ErrorDotExpr(s, ..) if s.is_empty() => f.write_str("Malformed dot expression")?, + Self::ErrorDotExpr(s, ..) => f.write_str(s)?, + + Self::ErrorIndexingType(s, ..) => write!(f, "Indexer unavailable: {s}")?, Self::ErrorUnboundThis(..) => f.write_str("'this' not bound")?, Self::ErrorFor(..) => f.write_str("For loop expects an iterable type")?, Self::ErrorTooManyOperations(..) => f.write_str("Too many operations")?, @@ -181,63 +179,57 @@ impl fmt::Display for EvalAltResult { { write!(f, "Runtime error")? } - Self::ErrorRuntime(d, ..) => write!(f, "Runtime error: {}", d)?, + Self::ErrorRuntime(d, ..) => write!(f, "Runtime error: {d}")?, - Self::ErrorAssignmentToConstant(s, ..) => write!(f, "Cannot modify constant: {}", s)?, + Self::ErrorAssignmentToConstant(s, ..) => write!(f, "Cannot modify constant: {s}")?, Self::ErrorMismatchOutputType(e, a, ..) => match (a.as_str(), e.as_str()) { - ("", e) => write!(f, "Output type incorrect, expecting {}", e), - (a, "") => write!(f, "Output type incorrect: {}", a), - (a, e) => write!(f, "Output type incorrect: {} (expecting {})", a, e), + ("", e) => write!(f, "Output type incorrect, expecting {e}"), + (a, "") => write!(f, "Output type incorrect: {a}"), + (a, e) => write!(f, "Output type incorrect: {a} (expecting {e})"), }?, Self::ErrorMismatchDataType(e, a, ..) => match (a.as_str(), e.as_str()) { - ("", e) => write!(f, "Data type incorrect, expecting {}", e), - (a, "") => write!(f, "Data type incorrect: {}", a), - (a, e) => write!(f, "Data type incorrect: {} (expecting {})", a, e), - }?, - Self::ErrorArithmetic(s, ..) => match s.as_str() { - "" => f.write_str("Arithmetic error"), - s => f.write_str(s), + ("", e) => write!(f, "Data type incorrect, expecting {e}"), + (a, "") => write!(f, "Data type incorrect: {a}"), + (a, e) => write!(f, "Data type incorrect: {a} (expecting {e})"), }?, + Self::ErrorArithmetic(s, ..) if s.is_empty() => f.write_str("Arithmetic error")?, + Self::ErrorArithmetic(s, ..) => f.write_str(s)?, + Self::LoopBreak(true, ..) => f.write_str("'break' not inside a loop")?, Self::LoopBreak(false, ..) => f.write_str("'continue' not inside a loop")?, Self::Return(..) => f.write_str("NOT AN ERROR - function returns value")?, Self::ErrorArrayBounds(max, index, ..) => match max { - 0 => write!(f, "Array index {} out of bounds: array is empty", index), + 0 => write!(f, "Array index {index} out of bounds: array is empty"), 1 => write!( f, - "Array index {} out of bounds: only 1 element in array", - index + "Array index {index} out of bounds: only 1 element in array", ), _ => write!( f, - "Array index {} out of bounds: only {} elements in array", - index, max + "Array index {index} out of bounds: only {max} elements in array", ), }?, Self::ErrorStringBounds(max, index, ..) => match max { - 0 => write!(f, "String index {} out of bounds: string is empty", index), + 0 => write!(f, "String index {index} out of bounds: string is empty"), 1 => write!( f, - "String index {} out of bounds: only 1 character in string", - index + "String index {index} out of bounds: only 1 character in string", ), _ => write!( f, - "String index {} out of bounds: only {} characters in string", - index, max + "String index {index} out of bounds: only {max} characters in string", ), }?, Self::ErrorBitFieldBounds(max, index, ..) => write!( f, - "Bit-field index {} out of bounds: only {} bits in bit-field", - index, max + "Bit-field index {index} out of bounds: only {max} bits in bit-field", )?, - Self::ErrorDataTooLarge(typ, ..) => write!(f, "{} exceeds maximum limit", typ)?, + Self::ErrorDataTooLarge(typ, ..) => write!(f, "{typ} exceeds maximum limit")?, - Self::ErrorCustomSyntax(s, tokens, ..) => write!(f, "{}: {}", s, tokens.join(" "))?, + Self::ErrorCustomSyntax(s, tokens, ..) => write!(f, "{s}: {}", tokens.join(" "))?, } // Do not write any position if None diff --git a/src/types/parse_error.rs b/src/types/parse_error.rs index e9b55ce3..7ac362af 100644 --- a/src/types/parse_error.rs +++ b/src/types/parse_error.rs @@ -184,77 +184,69 @@ impl ParseErrorType { impl fmt::Display for ParseErrorType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::BadInput(err) => write!(f, "{}", err), + Self::BadInput(err) => write!(f, "{err}"), - Self::UnknownOperator(s) => write!(f, "Unknown operator: '{}'", s), + Self::UnknownOperator(s) => write!(f, "Unknown operator: '{s}'"), - Self::MalformedCallExpr(s) => match s.as_str() { - "" => f.write_str("Invalid expression in function call arguments"), - s => f.write_str(s) - }, - Self::MalformedIndexExpr(s) => match s.as_str() { - "" => f.write_str("Invalid index in indexing expression"), - s => f.write_str(s) - }, - Self::MalformedInExpr(s) => match s.as_str() { - "" => f.write_str("Invalid 'in' expression"), - s => f.write_str(s) - }, - Self::MalformedCapture(s) => match s.as_str() { - "" => f.write_str("Invalid capturing"), - s => f.write_str(s) - }, + Self::MalformedCallExpr(s) if s.is_empty() => f.write_str(s), + Self::MalformedCallExpr(..) => f.write_str("Invalid expression in function call arguments"), + + Self::MalformedIndexExpr(s) if s.is_empty() => f.write_str("Invalid index in indexing expression"), + Self::MalformedIndexExpr(s) => f.write_str(s), + + Self::MalformedInExpr(s) if s.is_empty() => f.write_str("Invalid 'in' expression"), + Self::MalformedInExpr(s) => f.write_str(s), + + Self::MalformedCapture(s) if s.is_empty() => f.write_str("Invalid capturing"), + Self::MalformedCapture(s) => f.write_str(s), Self::FnDuplicatedDefinition(s, n) => { - write!(f, "Function {} with ", s)?; + write!(f, "Function {s} with ")?; match n { 0 => f.write_str("no parameters already exists"), 1 => f.write_str("1 parameter already exists"), - _ => write!(f, "{} parameters already exists", n), + _ => write!(f, "{n} parameters already exists"), } } - Self::FnMissingBody(s) => match s.as_str() { - "" => f.write_str("Expecting body statement block for anonymous function"), - s => write!(f, "Expecting body statement block for function {}", s) - }, - Self::FnMissingParams(s) => write!(f, "Expecting parameters for function {}", s), - Self::FnDuplicatedParam(s, arg) => write!(f, "Duplicated parameter {} for function {}", arg, s), - Self::DuplicatedProperty(s) => write!(f, "Duplicated property for object map literal: {}", s), + Self::FnMissingBody(s) if s.is_empty() => f.write_str("Expecting body statement block for anonymous function"), + Self::FnMissingBody(s) => write!(f, "Expecting body statement block for function {s}"), + + Self::FnMissingParams(s) => write!(f, "Expecting parameters for function {s}"), + Self::FnDuplicatedParam(s, arg) => write!(f, "Duplicated parameter {arg} for function {s}"), + + Self::DuplicatedProperty(s) => write!(f, "Duplicated property for object map literal: {s}"), #[allow(deprecated)] Self::DuplicatedSwitchCase => f.write_str("Duplicated switch case"), - Self::DuplicatedVariable(s) => write!(f, "Duplicated variable name: {}", s), + Self::DuplicatedVariable(s) => write!(f, "Duplicated variable name: {s}"), - Self::VariableExists(s) => write!(f, "Variable already defined: {}", s), - Self::VariableUndefined(s) => write!(f, "Undefined variable: {}", s), - Self::ModuleUndefined(s) => write!(f, "Undefined module: {}", s), + Self::VariableExists(s) => write!(f, "Variable already defined: {s}"), + Self::VariableUndefined(s) => write!(f, "Undefined variable: {s}"), + Self::ModuleUndefined(s) => write!(f, "Undefined module: {s}"), - Self::MismatchedType(r, a) => write!(f, "Expecting {}, not {}", r, a), + Self::MismatchedType(r, a) => write!(f, "Expecting {r}, not {a}"), Self::ExprExpected(s) => write!(f, "Expecting {} expression", s), - Self::MissingToken(token, s) => write!(f, "Expecting '{}' {}", token, s), + Self::MissingToken(token, s) => write!(f, "Expecting '{token}' {s}"), Self::MissingSymbol(s) if s.is_empty() => f.write_str("Expecting a symbol"), Self::MissingSymbol(s) => f.write_str(s), - Self::AssignmentToConstant(s) => match s.as_str() { - "" => f.write_str("Cannot assign to a constant value"), - s => write!(f, "Cannot assign to constant {}", s) - }, - Self::AssignmentToInvalidLHS(s) => match s.as_str() { - "" => f.write_str("Expression cannot be assigned to"), - s => f.write_str(s) - }, + Self::AssignmentToConstant(s) if s.is_empty() => f.write_str("Cannot assign to a constant value"), + Self::AssignmentToConstant(s) => write!(f, "Cannot assign to constant {s}"), - Self::LiteralTooLarge(typ, max) => write!(f, "{} exceeds the maximum limit ({})", typ, max), - Self::Reserved(s) if is_valid_identifier(s.chars()) => write!(f, "'{}' is a reserved keyword", s), - Self::Reserved(s) => write!(f, "'{}' is a reserved symbol", s), + Self::AssignmentToInvalidLHS(s) if s.is_empty() => f.write_str("Expression cannot be assigned to"), + Self::AssignmentToInvalidLHS(s) => f.write_str(s), + + Self::LiteralTooLarge(typ, max) => write!(f, "{typ} exceeds the maximum limit ({max})"), + Self::Reserved(s) if is_valid_identifier(s.chars()) => write!(f, "'{s}' is a reserved keyword"), + Self::Reserved(s) => write!(f, "'{s}' is a reserved symbol"), Self::UnexpectedEOF => f.write_str("Script is incomplete"), Self::WrongSwitchIntegerCase => f.write_str("Integer switch case cannot follow a range case"), Self::WrongSwitchDefaultCase => f.write_str("Default switch case must be the last"), Self::WrongSwitchCaseCondition => f.write_str("This switch case cannot have a condition"), Self::PropertyExpected => f.write_str("Expecting name of a property"), Self::VariableExpected => f.write_str("Expecting name of a variable"), - Self::ForbiddenVariable(s) => write!(f, "Forbidden variable name: {}", s), + Self::ForbiddenVariable(s) => write!(f, "Forbidden variable name: {s}"), Self::WrongFnDefinition => f.write_str("Function definitions must be at global level and cannot be inside a block or another function"), Self::FnMissingName => f.write_str("Expecting function name in function declaration"), Self::WrongDocComment => f.write_str("Doc-comment must be followed immediately by a function definition"),