diff --git a/CHANGELOG.md b/CHANGELOG.md index 70fc8550..9c2848dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,13 @@ New features ### Stable hashing -* It is now possible to specify a fixed _seed_ for use with the `ahash` hasher in order to force stable (i.e. deterministic) hashes for function signatures. +* It is now possible to specify a fixed _seed_ for use with the `ahash` hasher, via an environment variable, in order to force stable (i.e. deterministic) hashes for function signatures. This is necessary when using Rhai across shared-library boundaries. +* A build script is now used to extract the environment variable (`RHAI_AHASH_SEED`) and splice it into the source code before compilation. + +Bug fixes +--------- + +* `Engine::parse_json` now returns an error on unquoted keys to be consistent with JSON specifications. Enhancements ------------ @@ -18,6 +24,8 @@ Enhancements * Due to a code refactor, built-in operators for standard types now run even faster, in certain cases by 20-30%. * `Scope` is now serializable and deserializable via `serde`. * `Scope` now contains a const generic parameter that allows specifying how many entries to be kept inline. +* `parse_json` function is added to parse a JSON string into an object map. +* Methods returning maximum limits (e.g. `Engine::max_string_len`) are now available even under `unchecked` in order to avoid unnecessary feature flags in third-party library code. Version 1.10.1 diff --git a/Cargo.toml b/Cargo.toml index 1034f923..5b506221 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ categories = ["no-std", "embedded", "wasm", "parser-implementations"] [dependencies] smallvec = { version = "1.7", default-features = false, features = ["union", "const_new", "const_generics"] } -ahash = { version = "0.8", default-features = false } +ahash = { version = "0.8", default-features = false, features = ["compile-time-rng"] } num-traits = { version = "0.2", default-features = false } bitflags = { version = "1", default-features = false } smartstring = { version = "1", default-features = false } @@ -40,8 +40,8 @@ serde_bytes = "0.11" serde_json = { version = "1.0", default-features = false, features = ["alloc"] } [features] -default = ["std"] -std = ["ahash/std", "ahash/runtime-rng", "num-traits/std", "smartstring/std"] +default = ["std", "ahash/runtime-rng"] # ahash/runtime-rng trumps ahash/compile-time-rng +std = ["ahash/std", "num-traits/std", "smartstring/std"] unchecked = [] # unchecked arithmetic sync = [] # restrict to only types that implement Send + Sync no_position = [] # do not track position in the parser @@ -64,7 +64,7 @@ debugging = ["internals"] # enable debugging serde = ["dep:serde", "smartstring/serde", "smallvec/serde"] # implement serde for rhai types # compiling for no-std -no_std = ["no-std-compat", "num-traits/libm", "core-error", "libm", "ahash/compile-time-rng", "hashbrown/ahash-compile-time-rng"] +no_std = ["no-std-compat", "num-traits/libm", "core-error", "libm", "hashbrown"] # compiling for WASM wasm-bindgen = ["instant/wasm-bindgen"] diff --git a/build.rs b/build.rs index d17bb3da..a8fa7496 100644 --- a/build.rs +++ b/build.rs @@ -10,7 +10,6 @@ fn main() { f.write_fmt(format_args!( "//! Configuration settings for this Rhai build -#![allow(dead_code)] " )) diff --git a/examples/custom_types.rs b/examples/custom_types.rs index 3fa7c7ef..ba57de58 100644 --- a/examples/custom_types.rs +++ b/examples/custom_types.rs @@ -36,6 +36,8 @@ fn main() -> Result<(), Box> { type Item = i64; type IntoIter = std::vec::IntoIter; + #[inline] + #[must_use] fn into_iter(self) -> Self::IntoIter { vec![self.x - 1, self.x, self.x + 1].into_iter() } diff --git a/src/api/compile.rs b/src/api/compile.rs index 5322dbeb..2d187160 100644 --- a/src/api/compile.rs +++ b/src/api/compile.rs @@ -295,6 +295,6 @@ impl Engine { let mut peekable = stream.peekable(); let mut state = ParseState::new(self, scope, Default::default(), tokenizer_control); - self.parse_global_expr(&mut peekable, &mut state, self.optimization_level) + self.parse_global_expr(&mut peekable, &mut state, |_| {}, self.optimization_level) } } diff --git a/src/api/custom_syntax.rs b/src/api/custom_syntax.rs index 080d4477..7ee568c0 100644 --- a/src/api/custom_syntax.rs +++ b/src/api/custom_syntax.rs @@ -157,7 +157,6 @@ impl Deref for Expression<'_> { type Target = Expr; #[inline(always)] - #[must_use] fn deref(&self) -> &Self::Target { self.0 } diff --git a/src/api/definitions/mod.rs b/src/api/definitions/mod.rs index 73908d04..cd0a51c2 100644 --- a/src/api/definitions/mod.rs +++ b/src/api/definitions/mod.rs @@ -77,6 +77,7 @@ pub struct DefinitionsConfig { impl Default for DefinitionsConfig { #[inline(always)] + #[must_use] fn default() -> Self { Self { write_headers: false, diff --git a/src/api/eval.rs b/src/api/eval.rs index 4b22d99c..6808e2e6 100644 --- a/src/api/eval.rs +++ b/src/api/eval.rs @@ -122,6 +122,7 @@ impl Engine { let ast = self.parse_global_expr( &mut stream.peekable(), &mut state, + |_| {}, #[cfg(not(feature = "no_optimize"))] OptimizationLevel::None, #[cfg(feature = "no_optimize")] diff --git a/src/api/json.rs b/src/api/json.rs index 034062bf..24069fdd 100644 --- a/src/api/json.rs +++ b/src/api/json.rs @@ -23,7 +23,7 @@ impl Engine { /// JSON sub-objects are handled transparently. /// /// This function can be used together with [`format_map_as_json`] to work with JSON texts - /// without using the [`serde`](https://crates.io/crates/serde) crate (which is heavy). + /// without using the [`serde_json`](https://crates.io/crates/serde_json) crate (which is heavy). /// /// # Example /// @@ -122,6 +122,7 @@ impl Engine { let ast = self.parse_global_expr( &mut stream.peekable(), &mut state, + |s| s.allow_unquoted_map_properties = false, #[cfg(not(feature = "no_optimize"))] OptimizationLevel::None, #[cfg(feature = "no_optimize")] @@ -137,7 +138,7 @@ impl Engine { /// Not available under `no_std`. /// /// This function can be used together with [`Engine::parse_json`] to work with JSON texts -/// without using the [`serde`](https://crates.io/crates/serde) crate (which is heavy). +/// without using the [`serde_json`](https://crates.io/crates/serde_json) crate (which is heavy). /// /// # Data types /// diff --git a/src/api/limits.rs b/src/api/limits.rs index 18eb17f1..32daf13e 100644 --- a/src/api/limits.rs +++ b/src/api/limits.rs @@ -1,12 +1,31 @@ //! Settings for [`Engine`]'s limitations. #![cfg(not(feature = "unchecked"))] -use super::default_limits; use crate::Engine; use std::num::{NonZeroU64, NonZeroUsize}; #[cfg(feature = "no_std")] use std::prelude::v1::*; +pub mod default_limits { + #[cfg(debug_assertions)] + #[cfg(not(feature = "no_function"))] + pub const MAX_CALL_STACK_DEPTH: usize = 8; + #[cfg(debug_assertions)] + pub const MAX_EXPR_DEPTH: usize = 32; + #[cfg(not(feature = "no_function"))] + #[cfg(debug_assertions)] + pub const MAX_FUNCTION_EXPR_DEPTH: usize = 16; + + #[cfg(not(debug_assertions))] + #[cfg(not(feature = "no_function"))] + pub const MAX_CALL_STACK_DEPTH: usize = 64; + #[cfg(not(debug_assertions))] + pub const MAX_EXPR_DEPTH: usize = 64; + #[cfg(not(feature = "no_function"))] + #[cfg(not(debug_assertions))] + pub const MAX_FUNCTION_EXPR_DEPTH: usize = 32; +} + /// A type containing all the limits imposed by the [`Engine`]. /// /// Not available under `unchecked`. @@ -75,12 +94,34 @@ impl Limits { impl Default for Limits { #[inline(always)] + #[must_use] fn default() -> Self { Self::new() } } impl Engine { + /// Is there a data size limit set? + #[inline] + pub(crate) const fn has_data_size_limit(&self) -> bool { + self.limits.max_string_size.is_some() + || { + #[cfg(not(feature = "no_index"))] + { + self.limits.max_array_size.is_some() + } + #[cfg(feature = "no_index")] + false + } + || { + #[cfg(not(feature = "no_object"))] + { + self.limits.max_map_size.is_some() + } + #[cfg(feature = "no_object")] + false + } + } /// Set the maximum levels of function calls allowed for a script in order to avoid /// infinite recursion and stack overflows. /// @@ -93,12 +134,14 @@ impl Engine { } /// The maximum levels of function calls allowed for a script. /// - /// Not available under `unchecked` or `no_function`. - #[cfg(not(feature = "no_function"))] + /// Zero under `no_function`. #[inline(always)] #[must_use] pub const fn max_call_levels(&self) -> usize { - self.limits.max_call_stack_depth + #[cfg(not(feature = "no_function"))] + return self.limits.max_call_stack_depth; + #[cfg(feature = "no_function")] + return 0; } /// Set the maximum number of operations allowed for a script to run to avoid /// consuming too much resources (0 for unlimited). @@ -115,10 +158,9 @@ impl Engine { #[inline] #[must_use] pub const fn max_operations(&self) -> u64 { - if let Some(n) = self.limits.max_operations { - n.get() - } else { - 0 + match self.limits.max_operations { + Some(n) => n.get(), + None => 0, } } /// Set the maximum number of imported [modules][crate::Module] allowed for a script. @@ -132,12 +174,14 @@ impl Engine { } /// The maximum number of imported [modules][crate::Module] allowed for a script. /// - /// Not available under `unchecked` or `no_module`. - #[cfg(not(feature = "no_module"))] + /// Zero under `no_module`. #[inline(always)] #[must_use] pub const fn max_modules(&self) -> usize { - self.limits.max_modules + #[cfg(not(feature = "no_module"))] + return self.limits.max_modules; + #[cfg(feature = "no_module")] + return 0; } /// Set the depth limits for expressions (0 for unlimited). /// @@ -156,29 +200,27 @@ impl Engine { self } /// The depth limit for expressions (0 for unlimited). - /// - /// Not available under `unchecked`. #[inline] #[must_use] pub const fn max_expr_depth(&self) -> usize { - if let Some(n) = self.limits.max_expr_depth { - n.get() - } else { - 0 + match self.limits.max_expr_depth { + Some(n) => n.get(), + None => 0, } } /// The depth limit for expressions in functions (0 for unlimited). /// - /// Not available under `unchecked` or `no_function`. - #[cfg(not(feature = "no_function"))] + /// Zero under `no_function`. #[inline] #[must_use] pub const fn max_function_expr_depth(&self) -> usize { - if let Some(n) = self.limits.max_function_expr_depth { - n.get() - } else { - 0 - } + #[cfg(not(feature = "no_function"))] + return match self.limits.max_function_expr_depth { + Some(n) => n.get(), + None => 0, + }; + #[cfg(feature = "no_function")] + return 0; } /// Set the maximum length of [strings][crate::ImmutableString] (0 for unlimited). /// @@ -189,15 +231,12 @@ impl Engine { self } /// The maximum length of [strings][crate::ImmutableString] (0 for unlimited). - /// - /// Not available under `unchecked`. #[inline] #[must_use] pub const fn max_string_size(&self) -> usize { - if let Some(n) = self.limits.max_string_size { - n.get() - } else { - 0 + match self.limits.max_string_size { + Some(n) => n.get(), + None => 0, } } /// Set the maximum length of [arrays][crate::Array] (0 for unlimited). @@ -211,16 +250,17 @@ impl Engine { } /// The maximum length of [arrays][crate::Array] (0 for unlimited). /// - /// Not available under `unchecked` or `no_index`. - #[cfg(not(feature = "no_index"))] + /// Zero under `no_index`. #[inline] #[must_use] pub const fn max_array_size(&self) -> usize { - if let Some(n) = self.limits.max_array_size { - n.get() - } else { - 0 - } + #[cfg(not(feature = "no_index"))] + return match self.limits.max_array_size { + Some(n) => n.get(), + None => 0, + }; + #[cfg(feature = "no_index")] + return 0; } /// Set the maximum size of [object maps][crate::Map] (0 for unlimited). /// @@ -233,15 +273,16 @@ impl Engine { } /// The maximum size of [object maps][crate::Map] (0 for unlimited). /// - /// Not available under `unchecked` or `no_object`. - #[cfg(not(feature = "no_object"))] + /// Zero under `no_object`. #[inline] #[must_use] pub const fn max_map_size(&self) -> usize { - if let Some(n) = self.limits.max_map_size { - n.get() - } else { - 0 - } + #[cfg(not(feature = "no_object"))] + return match self.limits.max_map_size { + Some(n) => n.get(), + None => 0, + }; + #[cfg(feature = "no_object")] + return 0; } } diff --git a/src/api/mod.rs b/src/api/mod.rs index 6ddf090b..c296c81e 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -43,28 +43,7 @@ use std::prelude::v1::*; pub mod default_limits { #[cfg(not(feature = "unchecked"))] - #[cfg(debug_assertions)] - #[cfg(not(feature = "no_function"))] - pub const MAX_CALL_STACK_DEPTH: usize = 8; - #[cfg(not(feature = "unchecked"))] - #[cfg(debug_assertions)] - pub const MAX_EXPR_DEPTH: usize = 32; - #[cfg(not(feature = "unchecked"))] - #[cfg(not(feature = "no_function"))] - #[cfg(debug_assertions)] - pub const MAX_FUNCTION_EXPR_DEPTH: usize = 16; - - #[cfg(not(feature = "unchecked"))] - #[cfg(not(debug_assertions))] - #[cfg(not(feature = "no_function"))] - pub const MAX_CALL_STACK_DEPTH: usize = 64; - #[cfg(not(feature = "unchecked"))] - #[cfg(not(debug_assertions))] - pub const MAX_EXPR_DEPTH: usize = 64; - #[cfg(not(feature = "unchecked"))] - #[cfg(not(feature = "no_function"))] - #[cfg(not(debug_assertions))] - pub const MAX_FUNCTION_EXPR_DEPTH: usize = 32; + pub use super::limits::default_limits::*; pub const MAX_DYNAMIC_PARAMETERS: usize = 16; } diff --git a/src/api/register.rs b/src/api/register.rs index 9095a31f..ce5c0c4a 100644 --- a/src/api/register.rs +++ b/src/api/register.rs @@ -226,12 +226,12 @@ impl Engine { #[inline(always)] pub fn register_type_with_name_raw( &mut self, - fully_qualified_type_path: impl Into, + type_path: impl Into, name: impl Into, ) -> &mut Self { // Add the pretty-print type name into the map self.global_namespace_mut() - .set_custom_type_raw(fully_qualified_type_path, name); + .set_custom_type_raw(type_path, name); self } /// Register a type iterator for an iterable type with the [`Engine`]. diff --git a/src/ast/ast.rs b/src/ast/ast.rs index f6ebb50c..d0b88e26 100644 --- a/src/ast/ast.rs +++ b/src/ast/ast.rs @@ -36,6 +36,7 @@ pub struct AST { impl Default for AST { #[inline(always)] + #[must_use] fn default() -> Self { Self::empty() } @@ -69,7 +70,7 @@ impl fmt::Debug for AST { impl AST { /// Create a new [`AST`]. #[cfg(not(feature = "internals"))] - #[inline(always)] + #[inline] #[must_use] pub(crate) fn new( statements: impl IntoIterator, @@ -89,7 +90,7 @@ impl AST { /// _(internals)_ Create a new [`AST`]. /// Exported under the `internals` feature only. #[cfg(feature = "internals")] - #[inline(always)] + #[inline] #[must_use] pub fn new( statements: impl IntoIterator, @@ -108,7 +109,7 @@ impl AST { } /// Create a new [`AST`] with a source name. #[cfg(not(feature = "internals"))] - #[inline(always)] + #[inline] #[must_use] pub(crate) fn new_with_source( statements: impl IntoIterator, @@ -126,7 +127,7 @@ impl AST { /// _(internals)_ Create a new [`AST`] with a source name. /// Exported under the `internals` feature only. #[cfg(feature = "internals")] - #[inline(always)] + #[inline] #[must_use] pub fn new_with_source( statements: impl IntoIterator, @@ -157,7 +158,7 @@ impl AST { } } /// Get the source, if any. - #[inline(always)] + #[inline] #[must_use] pub fn source(&self) -> Option<&str> { if self.source.is_empty() { @@ -664,7 +665,6 @@ impl AST { self.combine_filtered_impl(other, filter) } /// Combine one [`AST`] with another. The second [`AST`] is consumed. - #[inline] fn combine_filtered_impl( &mut self, other: Self, @@ -957,19 +957,21 @@ pub enum ASTNode<'a> { } impl<'a> From<&'a Stmt> for ASTNode<'a> { + #[inline(always)] fn from(stmt: &'a Stmt) -> Self { Self::Stmt(stmt) } } impl<'a> From<&'a Expr> for ASTNode<'a> { + #[inline(always)] fn from(expr: &'a Expr) -> Self { Self::Expr(expr) } } impl PartialEq for ASTNode<'_> { - #[inline(always)] + #[inline] fn eq(&self, other: &Self) -> bool { match (self, other) { (Self::Stmt(x), Self::Stmt(y)) => ptr::eq(*x, *y), @@ -986,8 +988,8 @@ impl ASTNode<'_> { #[must_use] pub fn position(&self) -> Position { match self { - ASTNode::Stmt(stmt) => stmt.position(), - ASTNode::Expr(expr) => expr.position(), + Self::Stmt(stmt) => stmt.position(), + Self::Expr(expr) => expr.position(), } } } diff --git a/src/ast/expr.rs b/src/ast/expr.rs index 037079e8..76c9587d 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -75,8 +75,8 @@ impl CustomExpr { /// Is this custom syntax self-terminated (i.e. no need for a semicolon terminator)? /// /// A self-terminated custom syntax always ends in `$block$`, `}` or `;` - #[must_use] #[inline(always)] + #[must_use] pub const fn is_self_terminated(&self) -> bool { self.self_terminated } @@ -134,7 +134,7 @@ impl fmt::Debug for FnCallHashes { } impl From for FnCallHashes { - #[inline(always)] + #[inline] fn from(hash: u64) -> Self { let hash = if hash == 0 { ALT_ZERO_HASH } else { hash }; @@ -148,7 +148,7 @@ impl From for FnCallHashes { impl FnCallHashes { /// Create a [`FnCallHashes`] with only the native Rust hash. - #[inline(always)] + #[inline] #[must_use] pub const fn from_native(hash: u64) -> Self { Self { @@ -158,7 +158,7 @@ impl FnCallHashes { } } /// Create a [`FnCallHashes`] with both native Rust and script function hashes. - #[inline(always)] + #[inline] #[must_use] pub const fn from_all(#[cfg(not(feature = "no_function"))] script: u64, native: u64) -> Self { Self { @@ -252,7 +252,7 @@ pub struct FloatWrapper(F); #[cfg(not(feature = "no_float"))] impl Hash for FloatWrapper { - #[inline(always)] + #[inline] fn hash(&self, state: &mut H) { self.0.to_ne_bytes().hash(state); } @@ -281,7 +281,6 @@ impl Deref for FloatWrapper { type Target = F; #[inline(always)] - #[must_use] fn deref(&self) -> &Self::Target { &self.0 } @@ -290,7 +289,6 @@ impl Deref for FloatWrapper { #[cfg(not(feature = "no_float"))] impl DerefMut for FloatWrapper { #[inline(always)] - #[must_use] fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } @@ -447,6 +445,7 @@ pub enum Expr { impl Default for Expr { #[inline(always)] + #[must_use] fn default() -> Self { Self::Unit(Position::NONE) } diff --git a/src/ast/flags.rs b/src/ast/flags.rs index 452a57e6..e26fb55c 100644 --- a/src/ast/flags.rs +++ b/src/ast/flags.rs @@ -18,7 +18,7 @@ pub enum FnAccess { impl FnAccess { /// Is this function private? - #[inline(always)] + #[inline] #[must_use] pub const fn is_private(self) -> bool { match self { @@ -27,7 +27,7 @@ impl FnAccess { } } /// Is this function public? - #[inline(always)] + #[inline] #[must_use] pub const fn is_public(self) -> bool { match self { diff --git a/src/ast/ident.rs b/src/ast/ident.rs index eb84c432..e8eb6626 100644 --- a/src/ast/ident.rs +++ b/src/ast/ident.rs @@ -40,7 +40,6 @@ impl Deref for Ident { type Target = ImmutableString; #[inline(always)] - #[must_use] fn deref(&self) -> &Self::Target { &self.name } @@ -48,7 +47,6 @@ impl Deref for Ident { impl DerefMut for Ident { #[inline(always)] - #[must_use] fn deref_mut(&mut self) -> &mut Self::Target { &mut self.name } diff --git a/src/ast/namespace.rs b/src/ast/namespace.rs index ee36d3bd..e0939384 100644 --- a/src/ast/namespace.rs +++ b/src/ast/namespace.rs @@ -72,7 +72,6 @@ impl Deref for Namespace { type Target = StaticVec; #[inline(always)] - #[must_use] fn deref(&self) -> &Self::Target { &self.path } @@ -80,14 +79,13 @@ impl Deref for Namespace { impl DerefMut for Namespace { #[inline(always)] - #[must_use] fn deref_mut(&mut self) -> &mut Self::Target { &mut self.path } } impl From> for Namespace { - #[inline(always)] + #[inline] fn from(mut path: Vec) -> Self { path.shrink_to_fit(); Self { @@ -98,7 +96,7 @@ impl From> for Namespace { } impl From> for Namespace { - #[inline(always)] + #[inline] fn from(mut path: StaticVec) -> Self { path.shrink_to_fit(); Self { index: None, path } diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs index ba6f0821..a763860c 100644 --- a/src/ast/stmt.rs +++ b/src/ast/stmt.rs @@ -209,6 +209,7 @@ impl IntoIterator for RangeCase { type IntoIter = Box>; #[inline(always)] + #[must_use] fn into_iter(self) -> Self::IntoIter { match self { Self::ExclusiveInt(r, ..) => Box::new(r), @@ -426,7 +427,6 @@ impl Deref for StmtBlock { type Target = StmtBlockContainer; #[inline(always)] - #[must_use] fn deref(&self) -> &Self::Target { &self.block } @@ -434,7 +434,6 @@ impl Deref for StmtBlock { impl DerefMut for StmtBlock { #[inline(always)] - #[must_use] fn deref_mut(&mut self) -> &mut Self::Target { &mut self.block } @@ -607,6 +606,7 @@ pub enum Stmt { impl Default for Stmt { #[inline(always)] + #[must_use] fn default() -> Self { Self::Noop(Position::NONE) } diff --git a/src/bin/rhai-dbg.rs b/src/bin/rhai-dbg.rs index a83b680c..71d5152a 100644 --- a/src/bin/rhai-dbg.rs +++ b/src/bin/rhai-dbg.rs @@ -45,7 +45,6 @@ fn print_source(lines: &[String], pos: Position, offset: usize, window: (usize, if n == line { if let Some(pos) = pos.position() { let shift = offset + line_no_len + marker.len() + 2; - println!("{0:>1$}{2:>3$}", "│ ", shift, "\x1b[36m^\x1b[39m", pos + 10); } } @@ -310,10 +309,11 @@ fn debug_callback( ["node"] => { if pos.is_none() { println!("{:?}", node); - } else if let Some(source) = source { - println!("{:?} {} @ {:?}", node, source, pos); } else { - println!("{:?} @ {:?}", node, pos); + match source { + Some(source) => println!("{:?} {} @ {:?}", node, source, pos), + None => println!("{:?} @ {:?}", node, pos), + } } println!(); } @@ -339,20 +339,14 @@ fn debug_callback( ["over" | "o"] => break Ok(DebuggerCommand::StepOver), ["next" | "n"] => break Ok(DebuggerCommand::Next), ["scope"] => println!("{}", context.scope()), - ["print" | "p", "this"] => { - if let Some(value) = context.this_ptr() { - println!("=> {:?}", value); - } else { - println!("`this` pointer is unbound."); - } - } - ["print" | "p", var_name] => { - if let Some(value) = context.scope().get_value::(var_name) { - println!("=> {:?}", value); - } else { - eprintln!("Variable not found: {}", var_name); - } - } + ["print" | "p", "this"] => match context.this_ptr() { + Some(value) => println!("=> {:?}", value), + None => println!("`this` pointer is unbound."), + }, + ["print" | "p", var_name] => match context.scope().get_value::(var_name) { + Some(value) => println!("=> {:?}", value), + None => eprintln!("Variable not found: {}", var_name), + }, ["print" | "p"] => { println!("{}", context.scope().clone_visible()); if let Some(value) = context.this_ptr() { diff --git a/src/bin/rhai-repl.rs b/src/bin/rhai-repl.rs index 00e01e8b..dcab9974 100644 --- a/src/bin/rhai-repl.rs +++ b/src/bin/rhai-repl.rs @@ -482,27 +482,30 @@ fn main() { continue; } "!!" => { - if let Some(line) = rl.history().last() { - replacement = Some(line.clone()); - replacement_index = history_offset + rl.history().len() - 1; - } else { - eprintln!("No lines history!"); + match rl.history().last() { + Some(line) => { + replacement = Some(line.clone()); + replacement_index = history_offset + rl.history().len() - 1; + } + None => eprintln!("No lines history!"), } continue; } _ if cmd.starts_with("!?") => { let text = cmd[2..].trim(); - if let Some((n, line)) = rl + let history = rl .history() .iter() .rev() .enumerate() - .find(|&(.., h)| h.contains(text)) - { - replacement = Some(line.clone()); - replacement_index = history_offset + (rl.history().len() - 1 - n); - } else { - eprintln!("History line not found: {}", text); + .find(|&(.., h)| h.contains(text)); + + match history { + Some((n, line)) => { + replacement = Some(line.clone()); + replacement_index = history_offset + (rl.history().len() - 1 - n); + } + None => eprintln!("History line not found: {}", text), } continue; } diff --git a/src/config.rs b/src/config.rs index 43d49b6a..91e4716a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,3 @@ //! Configuration settings for this Rhai build -#![allow(dead_code)] pub const AHASH_SEED: Option<[u64; 4]> = None; diff --git a/src/engine.rs b/src/engine.rs index fb905cde..323f05b5 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -190,6 +190,7 @@ impl fmt::Debug for Engine { impl Default for Engine { #[inline(always)] + #[must_use] fn default() -> Self { Self::new() } @@ -346,9 +347,9 @@ impl Engine { } /// Check a result to ensure that it is valid. + #[cfg(not(feature = "unchecked"))] #[inline] pub(crate) fn check_return_value(&self, result: RhaiResult, _pos: Position) -> RhaiResult { - #[cfg(not(feature = "unchecked"))] if let Ok(ref r) = result { self.check_data_size(r, _pos)?; } @@ -356,3 +357,81 @@ impl Engine { result } } + +#[cfg(feature = "unchecked")] +impl Engine { + /// The maximum levels of function calls allowed for a script. + #[inline(always)] + #[must_use] + pub const fn max_call_levels(&self) -> usize { + usize::MAX + } + /// The maximum number of operations allowed for a script to run (0 for unlimited). + #[inline(always)] + #[must_use] + pub const fn max_operations(&self) -> u64 { + 0 + } + /// The maximum number of imported [modules][crate::Module] allowed for a script. + #[inline(always)] + #[must_use] + pub const fn max_modules(&self) -> usize { + usize::MAX + } + /// The depth limit for expressions (0 for unlimited). + #[inline(always)] + #[must_use] + pub const fn max_expr_depth(&self) -> usize { + 0 + } + /// The depth limit for expressions in functions (0 for unlimited). + #[inline(always)] + #[must_use] + pub const fn max_function_expr_depth(&self) -> usize { + 0 + } + /// The maximum length of [strings][crate::ImmutableString] (0 for unlimited). + #[inline(always)] + #[must_use] + pub const fn max_string_size(&self) -> usize { + 0 + } + /// The maximum length of [arrays][crate::Array] (0 for unlimited). + #[inline(always)] + #[must_use] + pub const fn max_array_size(&self) -> usize { + 0 + } + /// The maximum size of [object maps][crate::Map] (0 for unlimited). + #[inline(always)] + #[must_use] + pub const fn max_map_size(&self) -> usize { + 0 + } + + /// Check if the number of operations stay within limit. + #[inline(always)] + pub(crate) const fn track_operation( + &self, + _: &crate::eval::GlobalRuntimeState, + _: Position, + ) -> crate::RhaiResultOf<()> { + Ok(()) + } + + /// Check whether the size of a [`Dynamic`] is within limits. + #[inline(always)] + pub(crate) const fn check_data_size( + &self, + _: &Dynamic, + _: Position, + ) -> crate::RhaiResultOf<()> { + Ok(()) + } + + /// Check a result to ensure that it is valid. + #[inline(always)] + pub(crate) const fn check_return_value(&self, result: RhaiResult, _: Position) -> RhaiResult { + result + } +} diff --git a/src/eval/chaining.rs b/src/eval/chaining.rs index abdd0573..78acb0ff 100644 --- a/src/eval/chaining.rs +++ b/src/eval/chaining.rs @@ -50,7 +50,7 @@ impl Engine { idx_values: &mut FnArgsVec, chain_type: ChainType, level: usize, - new_val: Option<(Dynamic, &OpAssignment)>, + new_val: &mut Option<(Dynamic, &OpAssignment)>, ) -> RhaiResultOf<(Dynamic, bool)> { let is_ref_mut = target.is_ref(); @@ -75,7 +75,7 @@ impl Engine { #[cfg(feature = "debugging")] self.run_debugger(scope, global, lib, this_ptr, _parent, level)?; - let idx_val = idx_values.pop().unwrap(); + let idx_val = &mut idx_values.pop().unwrap(); let mut idx_val_for_setter = idx_val.clone(); let idx_pos = x.lhs.start_position(); let rhs_chain = rhs.into(); @@ -119,19 +119,18 @@ impl Engine { #[cfg(feature = "debugging")] self.run_debugger(scope, global, lib, this_ptr, _parent, level)?; - let (new_val, op_info) = new_val.expect("`Some`"); - let idx_val = idx_values.pop().unwrap(); - let mut idx_val2 = idx_val.clone(); + let (new_val, op_info) = new_val.take().expect("`Some`"); + let idx_val = &mut idx_values.pop().unwrap(); + let idx = &mut idx_val.clone(); let try_setter = match self.get_indexed_mut( - global, caches, lib, target, idx_val, pos, true, false, level, + global, caches, lib, target, idx, pos, true, false, level, ) { // Indexed value is not a temp value - update directly Ok(ref mut obj_ptr) => { self.eval_op_assignment( global, caches, lib, op_info, obj_ptr, root, new_val, level, )?; - #[cfg(not(feature = "unchecked"))] self.check_data_size(obj_ptr, op_info.pos)?; None } @@ -143,11 +142,10 @@ impl Engine { }; if let Some(mut new_val) = try_setter { - let idx = &mut idx_val2; - // Is this an op-assignment? if op_info.is_op_assignment() { - let idx = &mut idx.clone(); + let idx = &mut idx_val.clone(); + // Call the index getter to get the current value if let Ok(val) = self.call_indexer_get(global, caches, lib, target, idx, level) @@ -160,15 +158,15 @@ impl Engine { )?; // Replace new value new_val = val.take_or_clone(); - #[cfg(not(feature = "unchecked"))] self.check_data_size(&new_val, op_info.pos)?; } } // Try to call index setter let new_val = &mut new_val; + self.call_indexer_set( - global, caches, lib, target, idx, new_val, is_ref_mut, level, + global, caches, lib, target, idx_val, new_val, is_ref_mut, level, )?; } @@ -179,7 +177,7 @@ impl Engine { #[cfg(feature = "debugging")] self.run_debugger(scope, global, lib, this_ptr, _parent, level)?; - let idx_val = idx_values.pop().unwrap(); + let idx_val = &mut idx_values.pop().unwrap(); self.get_indexed_mut( global, caches, lib, target, idx_val, pos, false, true, level, @@ -236,8 +234,8 @@ impl Engine { #[cfg(feature = "debugging")] self.run_debugger(scope, global, lib, this_ptr, rhs, level)?; - let index = x.2.clone().into(); - let (new_val, op_info) = new_val.expect("`Some`"); + let index = &mut x.2.clone().into(); + let (new_val, op_info) = new_val.take().expect("`Some`"); { let val_target = &mut self.get_indexed_mut( global, caches, lib, target, index, *pos, true, false, level, @@ -246,7 +244,6 @@ impl Engine { global, caches, lib, op_info, val_target, root, new_val, level, )?; } - #[cfg(not(feature = "unchecked"))] self.check_data_size(target.source(), op_info.pos)?; Ok((Dynamic::UNIT, true)) } @@ -255,7 +252,7 @@ impl Engine { #[cfg(feature = "debugging")] self.run_debugger(scope, global, lib, this_ptr, rhs, level)?; - let index = x.2.clone().into(); + let index = &mut x.2.clone().into(); let val = self.get_indexed_mut( global, caches, lib, target, index, *pos, false, false, level, )?; @@ -267,7 +264,7 @@ impl Engine { self.run_debugger(scope, global, lib, this_ptr, rhs, level)?; let ((getter, hash_get), (setter, hash_set), name) = &**x; - let (mut new_val, op_info) = new_val.expect("`Some`"); + let (mut new_val, op_info) = new_val.take().expect("`Some`"); if op_info.is_op_assignment() { let args = &mut [target.as_mut()]; @@ -368,7 +365,7 @@ impl Engine { #[cfg(feature = "debugging")] self.run_debugger(scope, global, lib, this_ptr, _node, level)?; - let index = p.2.clone().into(); + let index = &mut p.2.clone().into(); self.get_indexed_mut( global, caches, lib, target, index, pos, false, true, level, )? @@ -558,7 +555,7 @@ impl Engine { this_ptr: &mut Option<&mut Dynamic>, expr: &Expr, level: usize, - new_val: Option<(Dynamic, &OpAssignment)>, + new_val: &mut Option<(Dynamic, &OpAssignment)>, ) -> RhaiResult { let chain_type = ChainType::from(expr); let (crate::ast::BinaryExpr { lhs, rhs }, options, op_pos) = match expr { @@ -595,7 +592,7 @@ impl Engine { // All other patterns - evaluate the arguments chain _ => { self.eval_dot_index_chain_arguments( - scope, global, caches, lib, this_ptr, rhs, options, chain_type, idx_values, 0, + scope, global, caches, lib, this_ptr, rhs, options, chain_type, idx_values, level, )?; } @@ -606,9 +603,7 @@ impl Engine { Expr::Variable(x, .., var_pos) => { #[cfg(feature = "debugging")] self.run_debugger(scope, global, lib, this_ptr, lhs, level)?; - - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut global.num_operations, *var_pos)?; + self.track_operation(global, *var_pos)?; let (mut target, ..) = self.search_namespace(scope, global, lib, this_ptr, lhs, level)?; @@ -653,11 +648,9 @@ impl Engine { parent_options: ASTFlags, _parent_chain_type: ChainType, idx_values: &mut FnArgsVec, - size: usize, level: usize, ) -> RhaiResultOf<()> { - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut global.num_operations, expr.position())?; + self.track_operation(global, expr.position())?; match expr { #[cfg(not(feature = "no_object"))] @@ -731,7 +724,7 @@ impl Engine { self.eval_dot_index_chain_arguments( scope, global, caches, lib, this_ptr, rhs, *options, chain_type, idx_values, - size, level, + level, )?; if !_arg_values.is_empty() { @@ -809,14 +802,13 @@ impl Engine { caches: &mut Caches, lib: &[&Module], target: &'t mut Dynamic, - mut idx: Dynamic, + idx: &mut Dynamic, idx_pos: Position, _add_if_not_found: bool, use_indexers: bool, level: usize, ) -> RhaiResultOf> { - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut global.num_operations, Position::NONE)?; + self.track_operation(global, Position::NONE)?; match target { #[cfg(not(feature = "no_index"))] @@ -1018,7 +1010,7 @@ impl Engine { } _ if use_indexers => self - .call_indexer_get(global, caches, lib, target, &mut idx, level) + .call_indexer_get(global, caches, lib, target, idx, level) .map(Into::into), _ => Err(ERR::ErrorIndexingType( diff --git a/src/eval/data_check.rs b/src/eval/data_check.rs index 293b1d99..76cf7a5c 100644 --- a/src/eval/data_check.rs +++ b/src/eval/data_check.rs @@ -1,9 +1,9 @@ //! Data size checks during evaluation. #![cfg(not(feature = "unchecked"))] +use super::GlobalRuntimeState; use crate::types::dynamic::Union; use crate::{Dynamic, Engine, Position, RhaiResultOf, ERR}; -use std::num::NonZeroUsize; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -15,46 +15,45 @@ impl Engine { /// # Panics /// /// Panics if any interior data is shared (should never happen). - #[cfg(not(feature = "unchecked"))] pub(crate) fn calc_data_sizes(value: &Dynamic, _top: bool) -> (usize, usize, usize) { match value.0 { #[cfg(not(feature = "no_index"))] Union::Array(ref arr, ..) => { arr.iter() - .fold((0, 0, 0), |(arrays, maps, strings), value| match value.0 { + .fold((0, 0, 0), |(ax, mx, sx), value| match value.0 { Union::Array(..) => { let (a, m, s) = Self::calc_data_sizes(value, false); - (arrays + a + 1, maps + m, strings + s) + (ax + a + 1, mx + m, sx + s) } - Union::Blob(ref a, ..) => (arrays + 1 + a.len(), maps, strings), + Union::Blob(ref a, ..) => (ax + 1 + a.len(), mx, sx), #[cfg(not(feature = "no_object"))] Union::Map(..) => { let (a, m, s) = Self::calc_data_sizes(value, false); - (arrays + a + 1, maps + m, strings + s) + (ax + a + 1, mx + m, sx + s) } - Union::Str(ref s, ..) => (arrays + 1, maps, strings + s.len()), - _ => (arrays + 1, maps, strings), + Union::Str(ref s, ..) => (ax + 1, mx, sx + s.len()), + _ => (ax + 1, mx, sx), }) } #[cfg(not(feature = "no_index"))] - Union::Blob(ref arr, ..) => (arr.len(), 0, 0), + Union::Blob(ref blob, ..) => (blob.len(), 0, 0), #[cfg(not(feature = "no_object"))] Union::Map(ref map, ..) => { map.values() - .fold((0, 0, 0), |(arrays, maps, strings), value| match value.0 { + .fold((0, 0, 0), |(ax, mx, sx), value| match value.0 { #[cfg(not(feature = "no_index"))] Union::Array(..) => { let (a, m, s) = Self::calc_data_sizes(value, false); - (arrays + a, maps + m + 1, strings + s) + (ax + a, mx + m + 1, sx + s) } #[cfg(not(feature = "no_index"))] - Union::Blob(ref a, ..) => (arrays + a.len(), maps, strings), + Union::Blob(ref a, ..) => (ax + a.len(), mx, sx), Union::Map(..) => { let (a, m, s) = Self::calc_data_sizes(value, false); - (arrays + a, maps + m + 1, strings + s) + (ax + a, mx + m + 1, sx + s) } - Union::Str(ref s, ..) => (arrays, maps + 1, strings + s.len()), - _ => (arrays, maps + 1, strings), + Union::Str(ref s, ..) => (ax, mx + 1, sx + s.len()), + _ => (ax, mx + 1, sx), }) } Union::Str(ref s, ..) => (0, 0, s.len()), @@ -70,56 +69,34 @@ impl Engine { } } - /// Is there a data size limit set? - #[cfg(not(feature = "unchecked"))] - pub(crate) const fn has_data_size_limit(&self) -> bool { - let mut _limited = self.limits.max_string_size.is_some(); - - #[cfg(not(feature = "no_index"))] - { - _limited = _limited || self.limits.max_array_size.is_some(); - } - #[cfg(not(feature = "no_object"))] - { - _limited = _limited || self.limits.max_map_size.is_some(); - } - - _limited - } - /// Raise an error if any data size exceeds limit. - #[cfg(not(feature = "unchecked"))] pub(crate) fn raise_err_if_over_data_size_limit( &self, - sizes: (usize, usize, usize), + (_arr, _map, s): (usize, usize, usize), pos: Position, ) -> RhaiResultOf<()> { - let (_arr, _map, s) = sizes; - - if s > self + if self .limits .max_string_size - .map_or(usize::MAX, NonZeroUsize::get) + .map_or(false, |max| s > max.get()) { return Err(ERR::ErrorDataTooLarge("Length of string".to_string(), pos).into()); } #[cfg(not(feature = "no_index"))] - if _arr - > self - .limits - .max_array_size - .map_or(usize::MAX, NonZeroUsize::get) + if self + .limits + .max_array_size + .map_or(false, |max| _arr > max.get()) { return Err(ERR::ErrorDataTooLarge("Size of array".to_string(), pos).into()); } #[cfg(not(feature = "no_object"))] - if _map - > self - .limits - .max_map_size - .map_or(usize::MAX, NonZeroUsize::get) + if self + .limits + .max_map_size + .map_or(false, |max| _map > max.get()) { return Err(ERR::ErrorDataTooLarge("Size of object map".to_string(), pos).into()); } @@ -128,7 +105,6 @@ impl Engine { } /// Check whether the size of a [`Dynamic`] is within limits. - #[cfg(not(feature = "unchecked"))] pub(crate) fn check_data_size(&self, value: &Dynamic, pos: Position) -> RhaiResultOf<()> { // If no data size limits, just return if !self.has_data_size_limit() { @@ -143,29 +119,30 @@ impl Engine { /// Raise an error if the size of a [`Dynamic`] is out of limits (if any). /// /// Not available under `unchecked`. - #[cfg(not(feature = "unchecked"))] #[inline(always)] pub fn ensure_data_size_within_limits(&self, value: &Dynamic) -> RhaiResultOf<()> { self.check_data_size(value, Position::NONE) } /// Check if the number of operations stay within limit. - #[cfg(not(feature = "unchecked"))] - pub(crate) fn inc_operations( + pub(crate) fn track_operation( &self, - num_operations: &mut u64, + global: &mut GlobalRuntimeState, pos: Position, ) -> RhaiResultOf<()> { - *num_operations += 1; + global.num_operations += 1; // Guard against too many operations - if self.max_operations() > 0 && *num_operations > self.max_operations() { + let max = self.max_operations(); + let num_operations = global.num_operations; + + if max > 0 && num_operations > max { return Err(ERR::ErrorTooManyOperations(pos).into()); } // Report progress - only in steps if let Some(ref progress) = self.progress { - if let Some(token) = progress(*num_operations) { + if let Some(token) = progress(num_operations) { // Terminate script if progress returns a termination token return Err(ERR::ErrorTerminated(token, pos).into()); } diff --git a/src/eval/debugger.rs b/src/eval/debugger.rs index e98771e7..249b0319 100644 --- a/src/eval/debugger.rs +++ b/src/eval/debugger.rs @@ -48,6 +48,7 @@ pub enum DebuggerCommand { impl Default for DebuggerCommand { #[inline(always)] + #[must_use] fn default() -> Self { Self::Continue } @@ -495,13 +496,10 @@ impl Engine { let event = match event { Some(e) => e, - None => { - if let Some(bp) = global.debugger.is_break_point(&global.source, node) { - DebuggerEvent::BreakPoint(bp) - } else { - return Ok(None); - } - } + None => match global.debugger.is_break_point(&global.source, node) { + Some(bp) => DebuggerEvent::BreakPoint(bp), + None => return Ok(None), + }, }; self.run_debugger_raw(scope, global, lib, this_ptr, node, event, level) diff --git a/src/eval/expr.rs b/src/eval/expr.rs index cdbeb95e..ce34a8ad 100644 --- a/src/eval/expr.rs +++ b/src/eval/expr.rs @@ -318,8 +318,7 @@ impl Engine { let reset_debugger = self.run_debugger_with_reset(scope, global, lib, this_ptr, expr, level)?; - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut global.num_operations, expr.position())?; + self.track_operation(global, expr.position())?; let result = self.eval_fn_call_expr(scope, global, caches, lib, this_ptr, x, x.pos, level); @@ -337,8 +336,7 @@ impl Engine { #[cfg(feature = "debugging")] self.run_debugger(scope, global, lib, this_ptr, expr, level)?; - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut global.num_operations, expr.position())?; + self.track_operation(global, expr.position())?; return if index.is_none() && x.0.is_none() && x.3 == KEYWORD_THIS { this_ptr @@ -355,8 +353,7 @@ impl Engine { let reset_debugger = self.run_debugger_with_reset(scope, global, lib, this_ptr, expr, level)?; - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut global.num_operations, expr.position())?; + self.track_operation(global, expr.position())?; let result = match expr { // Constants @@ -410,7 +407,7 @@ impl Engine { let mut result = Ok(Dynamic::UNIT); #[cfg(not(feature = "unchecked"))] - let mut sizes = (0, 0, 0); + let mut total_data_sizes = (0, 0, 0); for item_expr in &**x { let value = match self @@ -424,19 +421,21 @@ impl Engine { }; #[cfg(not(feature = "unchecked"))] - let val_sizes = Self::calc_data_sizes(&value, true); + if self.has_data_size_limit() { + let val_sizes = Self::calc_data_sizes(&value, true); + + total_data_sizes = ( + total_data_sizes.0 + val_sizes.0, + total_data_sizes.1 + val_sizes.1, + total_data_sizes.2 + val_sizes.2, + ); + self.raise_err_if_over_data_size_limit( + total_data_sizes, + item_expr.position(), + )?; + } array.push(value); - - #[cfg(not(feature = "unchecked"))] - if self.has_data_size_limit() { - sizes = ( - sizes.0 + val_sizes.0, - sizes.1 + val_sizes.1, - sizes.2 + val_sizes.2, - ); - self.raise_err_if_over_data_size_limit(sizes, item_expr.position())?; - } } result.map(|_| array.into()) @@ -448,7 +447,7 @@ impl Engine { let mut result = Ok(Dynamic::UNIT); #[cfg(not(feature = "unchecked"))] - let mut sizes = (0, 0, 0); + let mut total_data_sizes = (0, 0, 0); for (key, value_expr) in &x.0 { let value = match self @@ -462,15 +461,20 @@ impl Engine { }; #[cfg(not(feature = "unchecked"))] - let delta = Self::calc_data_sizes(&value, true); + if self.has_data_size_limit() { + let delta = Self::calc_data_sizes(&value, true); + total_data_sizes = ( + total_data_sizes.0 + delta.0, + total_data_sizes.1 + delta.1, + total_data_sizes.2 + delta.2, + ); + self.raise_err_if_over_data_size_limit( + total_data_sizes, + value_expr.position(), + )?; + } *map.get_mut(key.as_str()).unwrap() = value; - - #[cfg(not(feature = "unchecked"))] - if self.has_data_size_limit() { - sizes = (sizes.0 + delta.0, sizes.1 + delta.1, sizes.2 + delta.2); - self.raise_err_if_over_data_size_limit(sizes, value_expr.position())?; - } } result.map(|_| map.into()) @@ -561,14 +565,12 @@ impl Engine { } #[cfg(not(feature = "no_index"))] - Expr::Index(..) => { - self.eval_dot_index_chain(scope, global, caches, lib, this_ptr, expr, level, None) - } + Expr::Index(..) => self + .eval_dot_index_chain(scope, global, caches, lib, this_ptr, expr, level, &mut None), #[cfg(not(feature = "no_object"))] - Expr::Dot(..) => { - self.eval_dot_index_chain(scope, global, caches, lib, this_ptr, expr, level, None) - } + Expr::Dot(..) => self + .eval_dot_index_chain(scope, global, caches, lib, this_ptr, expr, level, &mut None), _ => unreachable!("expression cannot be evaluated: {:?}", expr), }; diff --git a/src/eval/global_state.rs b/src/eval/global_state.rs index 20ee2614..1144b851 100644 --- a/src/eval/global_state.rs +++ b/src/eval/global_state.rs @@ -36,6 +36,7 @@ pub struct GlobalRuntimeState<'a> { /// Number of operations performed. pub num_operations: u64, /// Number of modules loaded. + #[cfg(not(feature = "no_module"))] pub num_modules_loaded: usize, /// Level of the current scope. /// @@ -85,6 +86,7 @@ impl GlobalRuntimeState<'_> { modules: crate::StaticVec::new_const(), source: Identifier::new_const(), num_operations: 0, + #[cfg(not(feature = "no_module"))] num_modules_loaded: 0, scope_level: 0, always_search_scope: false, @@ -105,10 +107,9 @@ impl GlobalRuntimeState<'_> { } else { crate::eval::DebuggerStatus::CONTINUE }, - if let Some((ref init, ..)) = engine.debugger { - init(engine) - } else { - Dynamic::UNIT + match engine.debugger { + Some((ref init, ..)) => init(engine), + None => Dynamic::UNIT, }, ), @@ -316,7 +317,6 @@ impl IntoIterator for GlobalRuntimeState<'_> { std::iter::Rev; 3]>>, >; - #[inline] fn into_iter(self) -> Self::IntoIter { self.keys .into_iter() @@ -333,10 +333,8 @@ impl<'a> IntoIterator for &'a GlobalRuntimeState<'_> { std::iter::Rev>>, >; - #[inline] fn into_iter(self) -> Self::IntoIter { - let x = self.keys.iter().rev().zip(self.modules.iter().rev()); - x + self.keys.iter().rev().zip(self.modules.iter().rev()) } } @@ -363,14 +361,14 @@ impl fmt::Debug for GlobalRuntimeState<'_> { f.field("imports", &self.keys.iter().zip(self.modules.iter())); f.field("source", &self.source) - .field("num_operations", &self.num_operations) - .field("num_modules_loaded", &self.num_modules_loaded); + .field("num_operations", &self.num_operations); #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] f.field("fn_hash_indexing", &self.fn_hash_indexing); #[cfg(not(feature = "no_module"))] - f.field("embedded_module_resolver", &self.embedded_module_resolver); + f.field("num_modules_loaded", &self.num_modules_loaded) + .field("embedded_module_resolver", &self.embedded_module_resolver); #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_function"))] diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index 58f7ce47..726b661f 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -152,7 +152,6 @@ impl Engine { let context = (self, op, None, &*global, lib, *op_pos, level).into(); let result = func(context, args).map(|_| ()); - #[cfg(not(feature = "unchecked"))] self.check_data_size(args[0], root.1)?; return result; @@ -165,10 +164,7 @@ impl Engine { match self.call_native_fn( global, caches, lib, op_assign, hash, args, true, true, *op_pos, level, ) { - Ok(_) => { - #[cfg(not(feature = "unchecked"))] - self.check_data_size(args[0], root.1)?; - } + Ok(_) => self.check_data_size(args[0], root.1)?, Err(err) if matches!(*err, ERR::ErrorFunctionNotFound(ref f, ..) if f.starts_with(op_assign)) => { // Expand to `var = var op rhs` @@ -187,15 +183,6 @@ impl Engine { *target.write_lock::().unwrap() = new_val; } - /* - if let Some(mut guard) = target.write_lock::() { - if guard.is::() { - let s = std::mem::take(&mut *guard).cast::(); - *guard = self.get_interned_string(s).into(); - } - } - */ - target.propagate_changed_value(op_info.pos) } @@ -227,8 +214,7 @@ impl Engine { // Function calls should account for a relatively larger portion of statements. if let Stmt::FnCall(x, ..) = stmt { - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut global.num_operations, stmt.position())?; + self.track_operation(global, stmt.position())?; let result = self.eval_fn_call_expr(scope, global, caches, lib, this_ptr, x, x.pos, level); @@ -245,8 +231,7 @@ impl Engine { if let Stmt::Assignment(x, ..) = stmt { let (op_info, BinaryExpr { lhs, rhs }) = &**x; - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut global.num_operations, stmt.position())?; + self.track_operation(global, stmt.position())?; let result = if let Expr::Variable(x, ..) = lhs { let rhs_result = self @@ -276,8 +261,7 @@ impl Engine { ); } - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut global.num_operations, pos)?; + self.track_operation(global, pos)?; let root = (var_name, pos); let lhs_ptr = &mut lhs_ptr; @@ -295,19 +279,25 @@ impl Engine { } else { let (op_info, BinaryExpr { lhs, rhs }) = &**x; - let rhs_result = self - .eval_expr(scope, global, caches, lib, this_ptr, rhs, level) - .map(Dynamic::flatten); + let rhs_result = self.eval_expr(scope, global, caches, lib, this_ptr, rhs, level); if let Ok(rhs_val) = rhs_result { - let rhs_val = if rhs_val.is::() { - self.get_interned_string(rhs_val.cast::()) - .into() + // Check if the result is a string. If so, intern it. + #[cfg(not(feature = "no_closure"))] + let is_string = !rhs_val.is_shared() && rhs_val.is::(); + #[cfg(feature = "no_closure")] + let is_string = rhs_val.is::(); + + let rhs_val = if is_string { + self.get_interned_string( + rhs_val.into_immutable_string().expect("`ImmutableString`"), + ) + .into() } else { - rhs_val + rhs_val.flatten() }; - let _new_val = Some((rhs_val, op_info)); + let _new_val = &mut Some((rhs_val, op_info)); // Must be either `var[index] op= val` or `var.prop op= val` match lhs { @@ -342,8 +332,7 @@ impl Engine { return result; } - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut global.num_operations, stmt.position())?; + self.track_operation(global, stmt.position())?; let result = match stmt { // No-op @@ -515,8 +504,7 @@ impl Engine { let (.., body) = &**x; if body.is_empty() { - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut global.num_operations, body.position())?; + self.track_operation(global, body.position())?; } else { match self .eval_stmt_block(scope, global, caches, lib, this_ptr, body, true, level) @@ -671,10 +659,7 @@ impl Engine { *scope.get_mut_by_index(index).write_lock().unwrap() = value; - #[cfg(not(feature = "unchecked"))] - if let Err(err) = self - .inc_operations(&mut global.num_operations, statements.position()) - { + if let Err(err) = self.track_operation(global, statements.position()) { loop_result = Err(err); break; } @@ -741,7 +726,7 @@ impl Engine { #[cfg(feature = "no_object")] _ => { - err.take_position(); + let _ = err.take_position(); err.to_string().into() } #[cfg(not(feature = "no_object"))] @@ -927,7 +912,6 @@ impl Engine { let (expr, export) = &**x; // Guard against too many modules - #[cfg(not(feature = "unchecked"))] if global.num_modules_loaded >= self.max_modules() { return Err(ERR::ErrorTooManyModules(*_pos).into()); } diff --git a/src/eval/target.rs b/src/eval/target.rs index 3b47533d..5a441520 100644 --- a/src/eval/target.rs +++ b/src/eval/target.rs @@ -399,7 +399,6 @@ impl Deref for Target<'_> { type Target = Dynamic; #[inline] - #[must_use] fn deref(&self) -> &Dynamic { match self { Self::RefMut(r) => r, @@ -425,7 +424,6 @@ impl AsRef for Target<'_> { impl DerefMut for Target<'_> { #[inline] - #[must_use] fn deref_mut(&mut self) -> &mut Dynamic { match self { Self::RefMut(r) => r, diff --git a/src/func/builtin.rs b/src/func/builtin.rs index 075706fe..6b16366f 100644 --- a/src/func/builtin.rs +++ b/src/func/builtin.rs @@ -298,7 +298,7 @@ pub fn get_builtin_binary_op_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Option< ($x:ty, $xx:ident, $y:ty, $yy:ident) => { if (type1, type2) == (TypeId::of::<$x>(), TypeId::of::<$y>()) { #[cfg(not(feature = "unchecked"))] - use crate::packages::arithmetic::decimal_functions::*; + use crate::packages::arithmetic::decimal_functions::builtin::*; #[cfg(not(feature = "unchecked"))] match op { @@ -697,7 +697,7 @@ pub fn get_builtin_op_assignment_fn(op: &Token, x: &Dynamic, y: &Dynamic) -> Opt ($x:ident, $xx:ident, $y:ty, $yy:ident) => { if (type1, type2) == (TypeId::of::<$x>(), TypeId::of::<$y>()) { #[cfg(not(feature = "unchecked"))] - use crate::packages::arithmetic::decimal_functions::*; + use crate::packages::arithmetic::decimal_functions::builtin::*; #[cfg(not(feature = "unchecked"))] return match op { diff --git a/src/func/call.rs b/src/func/call.rs index ea6349fe..c093c237 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -358,8 +358,7 @@ impl Engine { pos: Position, level: usize, ) -> RhaiResultOf<(Dynamic, bool)> { - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut global.num_operations, pos)?; + self.track_operation(global, pos)?; let parent_source = global.source.clone(); let op_assign = if is_op_assign { @@ -1232,8 +1231,7 @@ impl Engine { target = target.into_owned(); } - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut global.num_operations, _pos)?; + self.track_operation(global, _pos)?; #[cfg(not(feature = "no_closure"))] let target_is_shared = target.is_shared(); @@ -1312,8 +1310,7 @@ impl Engine { let (target, _pos) = self.search_scope_only(scope, global, lib, this_ptr, first_arg, level)?; - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut global.num_operations, _pos)?; + self.track_operation(global, _pos)?; #[cfg(not(feature = "no_closure"))] let target_is_shared = target.is_shared(); @@ -1350,9 +1347,7 @@ impl Engine { let mut func = match module.get_qualified_fn(hash) { // Then search native Rust functions None => { - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut global.num_operations, pos)?; - + self.track_operation(global, pos)?; let hash_params = calc_fn_params_hash(args.iter().map(|a| a.type_id())); let hash_qualified_fn = combine_hashes(hash, hash_params); @@ -1384,8 +1379,7 @@ impl Engine { })); let hash_qualified_fn = combine_hashes(hash, hash_params); - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut global.num_operations, pos)?; + self.track_operation(global, pos)?; if let Some(f) = module.get_qualified_fn(hash_qualified_fn) { func = Some(f); @@ -1461,8 +1455,7 @@ impl Engine { _pos: Position, level: usize, ) -> RhaiResult { - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut global.num_operations, _pos)?; + self.track_operation(global, _pos)?; let script = script.trim(); diff --git a/src/func/func.rs b/src/func/func.rs index f74873c1..7d211879 100644 --- a/src/func/func.rs +++ b/src/func/func.rs @@ -46,6 +46,7 @@ pub trait Func { /// func(123, "hello")? == false; // call the anonymous function /// # Ok(()) /// # } + #[must_use] fn create_from_ast(self, ast: AST, entry_point: &str) -> Self::Output; /// Create a Rust closure from a script. @@ -79,6 +80,7 @@ pub trait Func { /// # Ok(()) /// # } /// ``` + #[must_use] fn create_from_script(self, script: &str, entry_point: &str) -> ParseResult; } diff --git a/src/func/hashing.rs b/src/func/hashing.rs index 53eac8c3..5f6a806c 100644 --- a/src/func/hashing.rs +++ b/src/func/hashing.rs @@ -41,6 +41,7 @@ pub struct StraightHasher(u64); impl Hasher for StraightHasher { #[inline(always)] + #[must_use] fn finish(&self) -> u64 { self.0 } @@ -66,6 +67,7 @@ impl BuildHasher for StraightHasherBuilder { type Hasher = StraightHasher; #[inline(always)] + #[must_use] fn build_hasher(&self) -> Self::Hasher { StraightHasher(ALT_ZERO_HASH) } @@ -75,10 +77,11 @@ impl BuildHasher for StraightHasherBuilder { #[inline(always)] #[must_use] pub fn get_hasher() -> ahash::AHasher { - if let Some([seed1, seed2, seed3, seed4]) = config::AHASH_SEED { - ahash::RandomState::with_seeds(seed1, seed2, seed3, seed4).build_hasher() - } else { - ahash::AHasher::default() + match config::AHASH_SEED { + Some([seed1, seed2, seed3, seed4]) if seed1 | seed2 | seed3 | seed4 != 0 => { + ahash::RandomState::with_seeds(seed1, seed2, seed3, seed4).build_hasher() + } + _ => ahash::AHasher::default(), } } diff --git a/src/func/register.rs b/src/func/register.rs index 37cc6bd0..928b08b0 100644 --- a/src/func/register.rs +++ b/src/func/register.rs @@ -86,8 +86,11 @@ pub trait RegisterNativeFunction { /// _(metadata)_ Get the type name of this function's return value. /// Exported under the `metadata` feature only. #[cfg(feature = "metadata")] + #[inline(always)] #[must_use] - fn return_type_name() -> &'static str; + fn return_type_name() -> &'static str { + std::any::type_name::() + } } const EXPECT_ARGS: &str = "arguments"; @@ -138,7 +141,6 @@ macro_rules! def_register { #[inline(always)] fn param_types() -> Box<[TypeId]> { vec![$(TypeId::of::<$par>()),*].into_boxed_slice() } #[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> Box<[&'static str]> { vec![$(std::any::type_name::<$param>()),*].into_boxed_slice() } #[cfg(feature = "metadata")] #[inline(always)] fn return_type() -> TypeId { TypeId::of::() } - #[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::() } #[inline(always)] fn into_callable_function(self) -> CallableFunction { CallableFunction::$abi(Shared::new(move |_ctx: NativeCallContext, args: &mut FnCallArgs| { // The arguments are assumed to be of the correct number and types! @@ -164,7 +166,6 @@ macro_rules! def_register { #[inline(always)] fn param_types() -> Box<[TypeId]> { vec![$(TypeId::of::<$par>()),*].into_boxed_slice() } #[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> Box<[&'static str]> { vec![$(std::any::type_name::<$param>()),*].into_boxed_slice() } #[cfg(feature = "metadata")] #[inline(always)] fn return_type() -> TypeId { TypeId::of::() } - #[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::() } #[inline(always)] fn into_callable_function(self) -> CallableFunction { CallableFunction::$abi(Shared::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| { // The arguments are assumed to be of the correct number and types! diff --git a/src/func/script.rs b/src/func/script.rs index 8ccc876b..c387b3dc 100644 --- a/src/func/script.rs +++ b/src/func/script.rs @@ -63,11 +63,9 @@ impl Engine { assert!(fn_def.params.len() == args.len()); - #[cfg(not(feature = "unchecked"))] - self.inc_operations(&mut global.num_operations, pos)?; + self.track_operation(global, pos)?; // Check for stack overflow - #[cfg(not(feature = "unchecked"))] if level > self.max_call_levels() { return Err(ERR::ErrorStackOverflow(pos).into()); } @@ -251,12 +249,9 @@ impl Engine { // Then check sub-modules || self.global_sub_modules.values().any(|m| m.contains_qualified_fn(hash_script)); - if !result { - if cache.filter.is_absent(hash_script) { - cache.filter.mark(hash_script); - } else { - cache.map.insert(hash_script, None); - } + if !result && !cache.filter.is_absent_and_set(hash_script) { + // Do not cache "one-hit wonders" + cache.map.insert(hash_script, None); } result diff --git a/src/module/mod.rs b/src/module/mod.rs index fb94e590..fe48d77f 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -196,6 +196,7 @@ pub struct Module { impl Default for Module { #[inline(always)] + #[must_use] fn default() -> Self { Self::new() } @@ -493,12 +494,12 @@ impl Module { #[inline(always)] pub fn set_custom_type_raw( &mut self, - type_name: impl Into, + type_path: impl Into, name: impl Into, ) -> &mut Self { self.custom_types .get_or_insert_with(CustomTypesCollection::new) - .add(type_name, name); + .add(type_path, name); self } /// Get the display name of a registered custom type. @@ -1741,10 +1742,9 @@ impl Module { } if let Some(ref variables) = other.variables { - if let Some(ref mut m) = self.variables { - m.extend(variables.iter().map(|(k, v)| (k.clone(), v.clone()))); - } else { - self.variables = other.variables.clone(); + match self.variables { + Some(ref mut m) => m.extend(variables.iter().map(|(k, v)| (k.clone(), v.clone()))), + None => self.variables = other.variables.clone(), } } @@ -1766,10 +1766,9 @@ impl Module { self.dynamic_functions_filter += &other.dynamic_functions_filter; if let Some(ref type_iterators) = other.type_iterators { - if let Some(ref mut t) = self.type_iterators { - t.extend(type_iterators.iter().map(|(&k, v)| (k, v.clone()))); - } else { - self.type_iterators = other.type_iterators.clone(); + match self.type_iterators { + Some(ref mut t) => t.extend(type_iterators.iter().map(|(&k, v)| (k, v.clone()))), + None => self.type_iterators = other.type_iterators.clone(), } } self.all_functions = None; diff --git a/src/module/resolvers/collection.rs b/src/module/resolvers/collection.rs index 7aab4bd0..fd6780d4 100644 --- a/src/module/resolvers/collection.rs +++ b/src/module/resolvers/collection.rs @@ -42,7 +42,7 @@ impl ModuleResolversCollection { /// ``` #[inline(always)] #[must_use] - pub fn new() -> Self { + pub const fn new() -> Self { Self(Vec::new()) } /// Append a [module resolver][ModuleResolver] to the end. @@ -112,6 +112,7 @@ impl IntoIterator for ModuleResolversCollection { type IntoIter = IntoIter>; #[inline(always)] + #[must_use] fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } diff --git a/src/module/resolvers/file.rs b/src/module/resolvers/file.rs index 729e772f..d68ddfd1 100644 --- a/src/module/resolvers/file.rs +++ b/src/module/resolvers/file.rs @@ -56,6 +56,7 @@ pub struct FileModuleResolver { impl Default for FileModuleResolver { #[inline(always)] + #[must_use] fn default() -> Self { Self::new() } @@ -321,10 +322,9 @@ impl FileModuleResolver { let scope = Scope::new(); - let m: Shared<_> = if let Some(global) = global { - Module::eval_ast_as_new_raw(engine, scope, global, &ast) - } else { - Module::eval_ast_as_new(scope, &ast, engine) + let m: Shared<_> = match global { + Some(global) => Module::eval_ast_as_new_raw(engine, scope, global, &ast), + None => Module::eval_ast_as_new(scope, &ast, engine), } .map_err(|err| Box::new(ERR::ErrorInModule(path.to_string(), err, pos)))? .into(); diff --git a/src/module/resolvers/stat.rs b/src/module/resolvers/stat.rs index 4019fc6d..8338a43a 100644 --- a/src/module/resolvers/stat.rs +++ b/src/module/resolvers/stat.rs @@ -134,6 +134,7 @@ impl IntoIterator for StaticModuleResolver { type IntoIter = IntoIter>; #[inline(always)] + #[must_use] fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } diff --git a/src/optimizer.rs b/src/optimizer.rs index c46cf43f..a0f187c9 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -38,6 +38,7 @@ pub enum OptimizationLevel { impl Default for OptimizationLevel { #[inline(always)] + #[must_use] fn default() -> Self { Self::Simple } @@ -550,13 +551,14 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b // switch const { case if condition => stmt, _ => def } => if condition { stmt } else { def } optimize_expr(&mut b.condition, state, false); - let else_stmt = if let Some(index) = def_case { - let mut def_stmt = - Stmt::Expr(mem::take(&mut expressions[*index].expr).into()); - optimize_stmt(&mut def_stmt, state, true); - def_stmt.into() - } else { - StmtBlock::NONE + let else_stmt = match def_case { + Some(index) => { + let mut def_stmt = + Stmt::Expr(mem::take(&mut expressions[*index].expr).into()); + optimize_stmt(&mut def_stmt, state, true); + def_stmt.into() + } + _ => StmtBlock::NONE, }; *stmt = Stmt::If( @@ -615,13 +617,14 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b // switch const { range if condition => stmt, _ => def } => if condition { stmt } else { def } optimize_expr(&mut condition, state, false); - let else_stmt = if let Some(index) = def_case { - let mut def_stmt = - Stmt::Expr(mem::take(&mut expressions[*index].expr).into()); - optimize_stmt(&mut def_stmt, state, true); - def_stmt.into() - } else { - StmtBlock::NONE + let else_stmt = match def_case { + Some(index) => { + let mut def_stmt = + Stmt::Expr(mem::take(&mut expressions[*index].expr).into()); + optimize_stmt(&mut def_stmt, state, true); + def_stmt.into() + } + _ => StmtBlock::NONE, }; let if_stmt = @@ -664,12 +667,13 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b // Promote the default case state.set_dirty(); - if let Some(index) = def_case { - let mut def_stmt = Stmt::Expr(mem::take(&mut expressions[*index].expr).into()); - optimize_stmt(&mut def_stmt, state, true); - *stmt = def_stmt; - } else { - *stmt = StmtBlock::empty(*pos).into(); + match def_case { + Some(index) => { + let mut def_stmt = Stmt::Expr(mem::take(&mut expressions[*index].expr).into()); + optimize_stmt(&mut def_stmt, state, true); + *stmt = def_stmt; + } + _ => *stmt = StmtBlock::empty(*pos).into(), } } // switch diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs index 2cba468e..3dafc1ae 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -488,39 +488,29 @@ mod f64_functions { #[cfg(feature = "decimal")] #[export_module] pub mod decimal_functions { - use num_traits::Pow; - use rust_decimal::{prelude::Zero, Decimal, MathematicalOps}; + use rust_decimal::{prelude::Zero, Decimal}; - #[rhai_fn(skip, return_raw)] - pub fn add(x: Decimal, y: Decimal) -> RhaiResultOf { - if cfg!(not(feature = "unchecked")) { + #[cfg(not(feature = "unchecked"))] + pub mod builtin { + use rust_decimal::MathematicalOps; + + #[rhai_fn(return_raw)] + pub fn add(x: Decimal, y: Decimal) -> RhaiResultOf { x.checked_add(y) .ok_or_else(|| make_err(format!("Addition overflow: {x} + {y}"))) - } else { - Ok(x + y) } - } - #[rhai_fn(skip, return_raw)] - pub fn subtract(x: Decimal, y: Decimal) -> RhaiResultOf { - if cfg!(not(feature = "unchecked")) { + #[rhai_fn(return_raw)] + pub fn subtract(x: Decimal, y: Decimal) -> RhaiResultOf { x.checked_sub(y) .ok_or_else(|| make_err(format!("Subtraction overflow: {x} - {y}"))) - } else { - Ok(x - y) } - } - #[rhai_fn(skip, return_raw)] - pub fn multiply(x: Decimal, y: Decimal) -> RhaiResultOf { - if cfg!(not(feature = "unchecked")) { + #[rhai_fn(return_raw)] + pub fn multiply(x: Decimal, y: Decimal) -> RhaiResultOf { x.checked_mul(y) .ok_or_else(|| make_err(format!("Multiplication overflow: {x} * {y}"))) - } else { - Ok(x * y) } - } - #[rhai_fn(skip, return_raw)] - pub fn divide(x: Decimal, y: Decimal) -> RhaiResultOf { - if cfg!(not(feature = "unchecked")) { + #[rhai_fn(return_raw)] + pub fn divide(x: Decimal, y: Decimal) -> RhaiResultOf { // Detect division by zero if y == Decimal::zero() { Err(make_err(format!("Division by zero: {x} / {y}"))) @@ -528,26 +518,16 @@ pub mod decimal_functions { x.checked_div(y) .ok_or_else(|| make_err(format!("Division overflow: {x} / {y}"))) } - } else { - Ok(x / y) } - } - #[rhai_fn(skip, return_raw)] - pub fn modulo(x: Decimal, y: Decimal) -> RhaiResultOf { - if cfg!(not(feature = "unchecked")) { + #[rhai_fn(return_raw)] + pub fn modulo(x: Decimal, y: Decimal) -> RhaiResultOf { x.checked_rem(y) .ok_or_else(|| make_err(format!("Modulo division by zero or overflow: {x} % {y}"))) - } else { - Ok(x % y) } - } - #[rhai_fn(skip, return_raw)] - pub fn power(x: Decimal, y: Decimal) -> RhaiResultOf { - if cfg!(not(feature = "unchecked")) { + #[rhai_fn(return_raw)] + pub fn power(x: Decimal, y: Decimal) -> RhaiResultOf { x.checked_powd(y) .ok_or_else(|| make_err(format!("Exponential overflow: {x} ** {y}"))) - } else { - Ok(x.pow(y)) } } #[rhai_fn(name = "-")] diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 72e2347e..7e639275 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -236,9 +236,8 @@ pub mod array_functions { } let check_sizes = match item.0 { - crate::types::dynamic::Union::Array(..) | crate::types::dynamic::Union::Str(..) => { - true - } + crate::types::dynamic::Union::Str(..) => true, + crate::types::dynamic::Union::Array(..) => true, #[cfg(not(feature = "no_object"))] crate::types::dynamic::Union::Map(..) => true, _ => false, diff --git a/src/packages/blob_basic.rs b/src/packages/blob_basic.rs index 4b6639c2..4ddd68b6 100644 --- a/src/packages/blob_basic.rs +++ b/src/packages/blob_basic.rs @@ -79,7 +79,6 @@ pub mod blob_functions { let _ctx = ctx; // Check if blob will be over max size limit - #[cfg(not(feature = "unchecked"))] if _ctx.engine().max_array_size() > 0 && len > _ctx.engine().max_array_size() { return Err( crate::ERR::ErrorDataTooLarge("Size of BLOB".to_string(), Position::NONE).into(), @@ -364,7 +363,6 @@ pub mod blob_functions { let _ctx = ctx; // Check if blob will be over max size limit - #[cfg(not(feature = "unchecked"))] if _ctx.engine().max_array_size() > 0 && len > _ctx.engine().max_array_size() { return Err( crate::ERR::ErrorDataTooLarge("Size of BLOB".to_string(), Position::NONE).into(), diff --git a/src/packages/lang_core.rs b/src/packages/lang_core.rs index 5eaa5ea3..d423691d 100644 --- a/src/packages/lang_core.rs +++ b/src/packages/lang_core.rs @@ -88,7 +88,6 @@ mod core_functions { #[cfg(feature = "f32_float")] std::thread::sleep(std::time::Duration::from_secs_f32(seconds)); } - /// Block the current thread for a particular number of `seconds`. #[cfg(not(feature = "no_std"))] pub fn sleep(seconds: INT) { @@ -97,6 +96,23 @@ mod core_functions { } std::thread::sleep(std::time::Duration::from_secs(seconds as u64)); } + + /// Parse a JSON string into a value. + /// + /// # Example + /// + /// ```rhai + /// let m = parse_json(`{"a":1, "b":2, "c":3}`); + /// + /// print(m); // prints #{"a":1, "b":2, "c":3} + /// ``` + #[cfg(not(feature = "no_index"))] + #[cfg(not(feature = "no_object"))] + #[cfg(feature = "metadata")] + #[rhai_fn(return_raw)] + pub fn parse_json(_ctx: NativeCallContext, json: &str) -> RhaiResultOf { + serde_json::from_str(json).map_err(|err| err.to_string().into()) + } } #[cfg(not(feature = "no_function"))] diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index 955b8395..3360be62 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -2,7 +2,7 @@ use crate::engine::OP_EQUALS; use crate::plugin::*; -use crate::{def_package, format_map_as_json, Dynamic, ImmutableString, Map, RhaiResultOf, INT}; +use crate::{def_package, Dynamic, ImmutableString, Map, NativeCallContext, RhaiResultOf, INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -82,10 +82,11 @@ mod map_functions { /// print(m); // prints "#{a: 1, b: 42, c: 3, x: 0}" /// ``` pub fn set(map: &mut Map, property: &str, value: Dynamic) { - if let Some(value_ref) = map.get_mut(property) { - *value_ref = value; - } else { - map.insert(property.into(), value); + match map.get_mut(property) { + Some(value_ref) => *value_ref = value, + _ => { + map.insert(property.into(), value); + } } } /// Clear the object map. @@ -304,6 +305,9 @@ mod map_functions { /// print(m.to_json()); // prints {"a":1, "b":2, "c":3} /// ``` pub fn to_json(map: &mut Map) -> String { - format_map_as_json(map) + #[cfg(feature = "metadata")] + return serde_json::to_string(map).unwrap_or_else(|_| "ERROR".into()); + #[cfg(not(feature = "metadata"))] + return crate::format_map_as_json(map); } } diff --git a/src/packages/mod.rs b/src/packages/mod.rs index ab4df033..5a63e86b 100644 --- a/src/packages/mod.rs +++ b/src/packages/mod.rs @@ -45,11 +45,14 @@ pub use time_basic::BasicTimePackage; pub trait Package { /// Initialize the package. /// Functions should be registered into `module` here. + #[cold] fn init(module: &mut Module); /// Initialize the package with an [`Engine`]. /// /// Perform tasks such as registering custom operators/syntax. + #[cold] + #[inline] #[allow(unused_variables)] fn init_engine(engine: &mut Engine) {} @@ -65,6 +68,8 @@ pub trait Package { /// /// package.register_into_engine(&mut engine); /// ``` + #[cold] + #[inline] fn register_into_engine(&self, engine: &mut Engine) -> &Self { Self::init_engine(engine); engine.register_global_module(self.as_shared_module()); @@ -84,6 +89,8 @@ pub trait Package { /// package.register_into_engine_as(&mut engine, "core"); /// ``` #[cfg(not(feature = "no_module"))] + #[cold] + #[inline] fn register_into_engine_as(&self, engine: &mut Engine, name: &str) -> &Self { Self::init_engine(engine); engine.register_static_module(name, self.as_shared_module()); @@ -133,7 +140,6 @@ macro_rules! def_package { fn as_shared_module(&self) -> $crate::Shared<$crate::Module> { self.0.clone() } - #[inline] fn init($lib: &mut $crate::Module) { $($( $(#[$base_meta])* { <$base_pkg>::init($lib); } @@ -141,7 +147,6 @@ macro_rules! def_package { $block } - #[inline] fn init_engine(_engine: &mut $crate::Engine) { $($( $(#[$base_meta])* { <$base_pkg>::init_engine(_engine); } @@ -156,6 +161,7 @@ macro_rules! def_package { impl Default for $package { #[inline(always)] + #[must_use] fn default() -> Self { Self::new() } @@ -193,12 +199,16 @@ macro_rules! def_package { } impl Default for $package { + #[inline(always)] + #[must_use] fn default() -> Self { Self::new() } } impl $package { + #[inline] + #[must_use] pub fn new() -> Self { let mut module = $root::Module::new(); ::init(&mut module); @@ -229,12 +239,16 @@ macro_rules! def_package { } impl Default for $package { + #[inline(always)] + #[must_use] fn default() -> Self { Self::new() } } impl $package { + #[inline] + #[must_use] pub fn new() -> Self { let mut module = $root::Module::new(); ::init(&mut module); diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 93e8edf4..72c4a6ca 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -239,10 +239,9 @@ mod string_functions { /// Clear the string, making it empty. pub fn clear(string: &mut ImmutableString) { if !string.is_empty() { - if let Some(s) = string.get_mut() { - s.clear(); - } else { - *string = ImmutableString::new(); + match string.get_mut() { + Some(s) => s.clear(), + _ => *string = ImmutableString::new(), } } } @@ -287,17 +286,20 @@ mod string_functions { /// print(text); // prints "hello" /// ``` pub fn trim(string: &mut ImmutableString) { - if let Some(s) = string.get_mut() { - let trimmed = s.trim(); + match string.get_mut() { + Some(s) => { + let trimmed = s.trim(); - if trimmed != s { - *s = trimmed.into(); + if trimmed != s { + *s = trimmed.into(); + } } - } else { - let trimmed = string.trim(); + None => { + let trimmed = string.trim(); - if trimmed != string { - *string = trimmed.into(); + if trimmed != string { + *string = trimmed.into(); + } } } } @@ -1216,7 +1218,6 @@ mod string_functions { let _ctx = ctx; // Check if string will be over max size limit - #[cfg(not(feature = "unchecked"))] if _ctx.engine().max_string_size() > 0 && len > _ctx.engine().max_string_size() { return Err(crate::ERR::ErrorDataTooLarge( "Length of string".to_string(), @@ -1234,7 +1235,6 @@ mod string_functions { p.push(character); } - #[cfg(not(feature = "unchecked"))] if _ctx.engine().max_string_size() > 0 && string.len() > _ctx.engine().max_string_size() { return Err(crate::ERR::ErrorDataTooLarge( @@ -1278,7 +1278,6 @@ mod string_functions { let _ctx = ctx; // Check if string will be over max size limit - #[cfg(not(feature = "unchecked"))] if _ctx.engine().max_string_size() > 0 && len > _ctx.engine().max_string_size() { return Err(crate::ERR::ErrorDataTooLarge( "Length of string".to_string(), @@ -1303,7 +1302,6 @@ mod string_functions { } } - #[cfg(not(feature = "unchecked"))] if _ctx.engine().max_string_size() > 0 && string.len() > _ctx.engine().max_string_size() { return Err(crate::ERR::ErrorDataTooLarge( diff --git a/src/parser.rs b/src/parser.rs index 6cdf3d18..798067c9 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -34,10 +34,6 @@ pub type ParseResult = Result; type FnLib = StraightHashMap>; -const KEYWORD_SEMICOLON: &str = Token::SemiColon.literal_syntax(); - -const KEYWORD_CLOSE_BRACE: &str = Token::RightBrace.literal_syntax(); - /// Invalid variable name that acts as a search barrier in a [`Scope`]. const SCOPE_SEARCH_BARRIER_MARKER: &str = "$ BARRIER $"; @@ -110,7 +106,7 @@ impl fmt::Debug for ParseState<'_> { impl<'e> ParseState<'e> { /// Create a new [`ParseState`]. - #[inline(always)] + #[inline] #[must_use] pub fn new( engine: &Engine, @@ -183,7 +179,6 @@ impl<'e> ParseState<'e> { /// /// * `is_func_name`: `true` if the variable is actually the name of a function /// (in which case it will be converted into a function pointer). - #[inline] #[must_use] pub fn access_var( &mut self, @@ -235,7 +230,6 @@ impl<'e> ParseState<'e> { /// /// Panics when called under `no_module`. #[cfg(not(feature = "no_module"))] - #[inline] #[must_use] pub fn find_module(&self, name: &str) -> Option { self.imports @@ -258,7 +252,7 @@ impl<'e> ParseState<'e> { /// Get an interned property getter, creating one if it is not yet interned. #[cfg(not(feature = "no_object"))] - #[inline(always)] + #[inline] #[must_use] pub fn get_interned_getter( &mut self, @@ -273,7 +267,7 @@ impl<'e> ParseState<'e> { /// Get an interned property setter, creating one if it is not yet interned. #[cfg(not(feature = "no_object"))] - #[inline(always)] + #[inline] #[must_use] pub fn get_interned_setter( &mut self, @@ -289,31 +283,33 @@ impl<'e> ParseState<'e> { /// A type that encapsulates all the settings for a particular parsing function. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -struct ParseSettings { +pub(crate) struct ParseSettings { /// Is the construct being parsed located at global level? - at_global_level: bool, + pub at_global_level: bool, /// Is the construct being parsed located inside a function definition? #[cfg(not(feature = "no_function"))] - in_fn_scope: bool, + pub in_fn_scope: bool, /// Is the construct being parsed located inside a closure definition? #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_closure"))] - in_closure: bool, + pub in_closure: bool, /// Is the construct being parsed located inside a breakable loop? - is_breakable: bool, + pub is_breakable: bool, /// Allow statements in blocks? - allow_statements: bool, + pub allow_statements: bool, + /// Allow unquoted map properties? + pub allow_unquoted_map_properties: bool, /// Language options in effect (overrides Engine options). - options: LangOptions, + pub options: LangOptions, /// Current expression nesting level. - level: usize, + pub level: usize, /// Current position. - pos: Position, + pub pos: Position, } impl ParseSettings { /// Create a new `ParseSettings` with one higher expression level. - #[inline(always)] + #[inline] #[must_use] pub const fn level_up(&self) -> Self { Self { @@ -417,7 +413,6 @@ impl Expr { } /// Make sure that the next expression is not a statement expression (i.e. wrapped in `{}`). -#[inline] fn ensure_not_statement_expr( input: &mut TokenStream, type_name: &(impl ToString + ?Sized), @@ -429,7 +424,6 @@ fn ensure_not_statement_expr( } /// Make sure that the next expression is not a mis-typed assignment (i.e. `a = b` instead of `a == b`). -#[inline] fn ensure_not_assignment(input: &mut TokenStream) -> ParseResult<()> { match input.peek().expect(NEVER_ENDS) { (Token::Equals, pos) => Err(LexError::ImproperSymbol( @@ -446,7 +440,6 @@ fn ensure_not_assignment(input: &mut TokenStream) -> ParseResult<()> { /// # Panics /// /// Panics if the next token is not the expected one. -#[inline] fn eat_token(input: &mut TokenStream, expected_token: Token) -> Position { let (t, pos) = input.next().expect(NEVER_ENDS); @@ -462,7 +455,6 @@ fn eat_token(input: &mut TokenStream, expected_token: Token) -> Position { } /// Match a particular [token][Token], consuming it if matched. -#[inline] fn match_token(input: &mut TokenStream, token: Token) -> (bool, Position) { let (t, pos) = input.peek().expect(NEVER_ENDS); if *t == token { @@ -473,7 +465,6 @@ fn match_token(input: &mut TokenStream, token: Token) -> (bool, Position) { } /// Parse a variable name. -#[inline] fn parse_var_name(input: &mut TokenStream) -> ParseResult<(SmartString, Position)> { match input.next().expect(NEVER_ENDS) { // Variable name @@ -491,7 +482,6 @@ fn parse_var_name(input: &mut TokenStream) -> ParseResult<(SmartString, Position /// Parse a symbol. #[cfg(not(feature = "no_custom_syntax"))] -#[inline] fn parse_symbol(input: &mut TokenStream) -> ParseResult<(SmartString, Position)> { match input.next().expect(NEVER_ENDS) { // Symbol @@ -912,7 +902,6 @@ impl Engine { loop { const MISSING_RBRACKET: &str = "to end this array literal"; - #[cfg(not(feature = "unchecked"))] if self.max_array_size() > 0 && array.len() >= self.max_array_size() { return Err(PERR::LiteralTooLarge( "Size of array literal".to_string(), @@ -1004,6 +993,9 @@ impl Engine { } let (name, pos) = match input.next().expect(NEVER_ENDS) { + (Token::Identifier(..), pos) if !settings.allow_unquoted_map_properties => { + return Err(PERR::PropertyExpected.into_err(pos)) + } (Token::Identifier(s) | Token::StringConstant(s), pos) => { if map.iter().any(|(p, ..)| **p == *s) { return Err(PERR::DuplicatedProperty(s.to_string()).into_err(pos)); @@ -1046,7 +1038,6 @@ impl Engine { } }; - #[cfg(not(feature = "unchecked"))] if self.max_map_size() > 0 && map.len() >= self.max_map_size() { return Err(PERR::LiteralTooLarge( "Number of properties in object map literal".to_string(), @@ -2473,6 +2464,8 @@ impl Engine { pos: Position, ) -> ParseResult { use crate::api::custom_syntax::markers::*; + const KEYWORD_SEMICOLON: &str = Token::SemiColon.literal_syntax(); + const KEYWORD_CLOSE_BRACE: &str = Token::RightBrace.literal_syntax(); let mut settings = settings; let mut inputs = StaticVec::new_const(); @@ -3334,6 +3327,7 @@ impl Engine { in_closure: false, is_breakable: false, allow_statements: true, + allow_unquoted_map_properties: settings.allow_unquoted_map_properties, level: 0, options, pos, @@ -3801,6 +3795,7 @@ impl Engine { &self, input: &mut TokenStream, state: &mut ParseState, + process_settings: impl Fn(&mut ParseSettings), _optimization_level: OptimizationLevel, ) -> ParseResult { let mut functions = StraightHashMap::default(); @@ -3810,7 +3805,7 @@ impl Engine { #[cfg(not(feature = "no_function"))] options.remove(LangOptions::ANON_FN); - let settings = ParseSettings { + let mut settings = ParseSettings { at_global_level: true, #[cfg(not(feature = "no_function"))] in_fn_scope: false, @@ -3819,10 +3814,13 @@ impl Engine { in_closure: false, is_breakable: false, allow_statements: false, + allow_unquoted_map_properties: true, level: 0, options, - pos: Position::NONE, + pos: Position::START, }; + process_settings(&mut settings); + let expr = self.parse_expr(input, state, &mut functions, settings)?; assert!(functions.is_empty()); @@ -3861,25 +3859,27 @@ impl Engine { &self, input: &mut TokenStream, state: &mut ParseState, + process_settings: impl Fn(&mut ParseSettings), ) -> ParseResult<(StmtBlockContainer, StaticVec>)> { let mut statements = StmtBlockContainer::new_const(); let mut functions = StraightHashMap::default(); + let mut settings = ParseSettings { + at_global_level: true, + #[cfg(not(feature = "no_function"))] + in_fn_scope: false, + #[cfg(not(feature = "no_function"))] + #[cfg(not(feature = "no_closure"))] + in_closure: false, + is_breakable: false, + allow_statements: true, + allow_unquoted_map_properties: true, + options: self.options, + level: 0, + pos: Position::START, + }; + process_settings(&mut settings); while !input.peek().expect(NEVER_ENDS).0.is_eof() { - let settings = ParseSettings { - at_global_level: true, - #[cfg(not(feature = "no_function"))] - in_fn_scope: false, - #[cfg(not(feature = "no_function"))] - #[cfg(not(feature = "no_closure"))] - in_closure: false, - is_breakable: false, - allow_statements: true, - options: self.options, - level: 0, - pos: Position::NONE, - }; - let stmt = self.parse_stmt(input, state, &mut functions, settings)?; if stmt.is_noop() { @@ -3926,7 +3926,7 @@ impl Engine { state: &mut ParseState, _optimization_level: OptimizationLevel, ) -> ParseResult { - let (statements, _lib) = self.parse_global_level(input, state)?; + let (statements, _lib) = self.parse_global_level(input, state, |_| {})?; #[cfg(not(feature = "no_optimize"))] return Ok(crate::optimizer::optimize_into_ast( diff --git a/src/serde/deserialize.rs b/src/serde/deserialize.rs index e6c5ae3a..2534a1d5 100644 --- a/src/serde/deserialize.rs +++ b/src/serde/deserialize.rs @@ -215,10 +215,9 @@ impl<'de> Deserialize<'de> for Scope<'de> { where A: SeqAccess<'de>, { - let mut scope = if let Some(size) = access.size_hint() { - Scope::with_capacity(size) - } else { - Scope::new() + let mut scope = match access.size_hint() { + Some(size) => Scope::with_capacity(size), + None => Scope::new(), }; while let Some(ScopeEntry { diff --git a/src/serde/serialize.rs b/src/serde/serialize.rs index 818b0e9e..69a280ef 100644 --- a/src/serde/serialize.rs +++ b/src/serde/serialize.rs @@ -37,10 +37,9 @@ impl Serialize for Dynamic { Union::Decimal(ref x, ..) => { use rust_decimal::prelude::ToPrimitive; - if let Some(v) = x.to_f64() { - ser.serialize_f64(v) - } else { - ser.serialize_str(&x.to_string()) + match x.to_f64() { + Some(v) => ser.serialize_f64(v), + None => ser.serialize_str(&x.to_string()), } } #[cfg(feature = "decimal")] @@ -48,10 +47,9 @@ impl Serialize for Dynamic { Union::Decimal(ref x, ..) => { use rust_decimal::prelude::ToPrimitive; - if let Some(v) = x.to_f32() { - ser.serialize_f32(v) - } else { - ser.serialize_str(&x.to_string()) + match x.to_f32() { + Some(v) => ser.serialize_f32(v), + _ => ser.serialize_str(&x.to_string()), } } diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 81893066..6aa0ec43 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -32,7 +32,7 @@ pub struct TokenizerControlBlock { impl TokenizerControlBlock { /// Create a new `TokenizerControlBlock`. - #[inline(always)] + #[inline] #[must_use] pub const fn new() -> Self { Self { @@ -97,7 +97,7 @@ impl Position { /// # Panics /// /// Panics if `line` is zero. - #[inline(always)] + #[inline] #[must_use] pub const fn new(line: u16, position: u16) -> Self { assert!(line != 0, "line cannot be zero"); @@ -220,6 +220,7 @@ impl Position { impl Default for Position { #[inline(always)] + #[must_use] fn default() -> Self { Self::START } @@ -299,6 +300,8 @@ pub struct Span { } impl Default for Span { + #[inline(always)] + #[must_use] fn default() -> Self { Self::NONE } @@ -315,7 +318,7 @@ impl Span { Self { start, end } } /// Is this [`Span`] non-existent? - #[inline(always)] + #[inline] #[must_use] pub const fn is_none(&self) -> bool { self.start.is_none() && self.end.is_none() diff --git a/src/types/dynamic.rs b/src/types/dynamic.rs index 79e63abf..6572dbe4 100644 --- a/src/types/dynamic.rs +++ b/src/types/dynamic.rs @@ -153,7 +153,6 @@ impl<'d, T: Any + Clone> Deref for DynamicWriteLock<'d, T> { type Target = T; #[inline] - #[must_use] fn deref(&self) -> &Self::Target { match self.0 { DynamicWriteLockInner::Reference(ref reference) => *reference, @@ -165,7 +164,6 @@ impl<'d, T: Any + Clone> Deref for DynamicWriteLock<'d, T> { impl<'d, T: Any + Clone> DerefMut for DynamicWriteLock<'d, T> { #[inline] - #[must_use] fn deref_mut(&mut self) -> &mut Self::Target { match self.0 { DynamicWriteLockInner::Reference(ref mut reference) => *reference, diff --git a/src/types/error.rs b/src/types/error.rs index ba9b7351..89bbedd8 100644 --- a/src/types/error.rs +++ b/src/types/error.rs @@ -251,9 +251,9 @@ impl> From for EvalAltResult { impl> From for Box { #[cold] - #[inline(never)] + #[inline(always)] fn from(err: T) -> Self { - EvalAltResult::ErrorRuntime(err.as_ref().to_string().into(), Position::NONE).into() + Into::::into(err).into() } } diff --git a/src/types/immutable_string.rs b/src/types/immutable_string.rs index 7353f151..f6dba61a 100644 --- a/src/types/immutable_string.rs +++ b/src/types/immutable_string.rs @@ -53,7 +53,6 @@ impl Deref for ImmutableString { type Target = SmartString; #[inline(always)] - #[must_use] fn deref(&self) -> &Self::Target { &self.0 } @@ -148,6 +147,7 @@ impl FromStr for ImmutableString { type Err = (); #[inline(always)] + #[must_use] fn from_str(s: &str) -> Result { let s: SmartString = s.into(); Ok(Self(s.into())) @@ -156,6 +156,7 @@ impl FromStr for ImmutableString { impl FromIterator for ImmutableString { #[inline] + #[must_use] fn from_iter>(iter: T) -> Self { Self(iter.into_iter().collect::().into()) } @@ -163,6 +164,7 @@ impl FromIterator for ImmutableString { impl<'a> FromIterator<&'a char> for ImmutableString { #[inline] + #[must_use] fn from_iter>(iter: T) -> Self { Self(iter.into_iter().copied().collect::().into()) } @@ -170,6 +172,7 @@ impl<'a> FromIterator<&'a char> for ImmutableString { impl<'a> FromIterator<&'a str> for ImmutableString { #[inline] + #[must_use] fn from_iter>(iter: T) -> Self { Self(iter.into_iter().collect::().into()) } @@ -177,6 +180,7 @@ impl<'a> FromIterator<&'a str> for ImmutableString { impl FromIterator for ImmutableString { #[inline] + #[must_use] fn from_iter>(iter: T) -> Self { Self(iter.into_iter().collect::().into()) } @@ -184,6 +188,7 @@ impl FromIterator for ImmutableString { impl FromIterator for ImmutableString { #[inline] + #[must_use] fn from_iter>(iter: T) -> Self { Self(iter.into_iter().collect::().into()) } diff --git a/src/types/interner.rs b/src/types/interner.rs index 7ae35a35..5832dbe0 100644 --- a/src/types/interner.rs +++ b/src/types/interner.rs @@ -39,6 +39,7 @@ pub struct StringsInterner<'a> { impl Default for StringsInterner<'_> { #[inline(always)] + #[must_use] fn default() -> Self { Self::new() } diff --git a/src/types/scope.rs b/src/types/scope.rs index 169830b4..a3787421 100644 --- a/src/types/scope.rs +++ b/src/types/scope.rs @@ -128,7 +128,7 @@ impl IntoIterator for Scope<'_> { type Item = (String, Dynamic, Vec); type IntoIter = Box>; - #[inline] + #[must_use] fn into_iter(self) -> Self::IntoIter { Box::new( self.values @@ -143,7 +143,7 @@ impl<'a> IntoIterator for &'a Scope<'_> { type Item = (&'a Identifier, &'a Dynamic, &'a Vec); type IntoIter = Box + 'a>; - #[inline] + #[must_use] fn into_iter(self) -> Self::IntoIter { Box::new( self.values @@ -719,7 +719,6 @@ impl Scope<'_> { scope } /// Get an iterator to entries in the [`Scope`]. - #[inline] #[allow(dead_code)] pub(crate) fn into_iter(self) -> impl Iterator)> { self.names diff --git a/tests/build_type.rs b/tests/build_type.rs index 05b23bc2..4844a3b4 100644 --- a/tests/build_type.rs +++ b/tests/build_type.rs @@ -49,6 +49,8 @@ fn build_type() -> Result<(), Box> { type Item = INT; type IntoIter = std::vec::IntoIter; + #[inline] + #[must_use] fn into_iter(self) -> Self::IntoIter { vec![self.x, self.y, self.z].into_iter() } diff --git a/tests/for.rs b/tests/for.rs index 62112a12..92e68af1 100644 --- a/tests/for.rs +++ b/tests/for.rs @@ -337,6 +337,8 @@ impl IntoIterator for MyIterableType { type Item = char; type IntoIter = std::vec::IntoIter; + #[inline] + #[must_use] fn into_iter(self) -> Self::IntoIter { self.0.chars().collect::>().into_iter() } diff --git a/tests/maps.rs b/tests/maps.rs index 3cb414db..2620738d 100644 --- a/tests/maps.rs +++ b/tests/maps.rs @@ -276,6 +276,11 @@ fn test_map_json() -> Result<(), Box> { EvalAltResult::ErrorMismatchOutputType(..) )); + assert!(matches!( + *engine.parse_json("{a:42}", true).expect_err("should error"), + EvalAltResult::ErrorParsing(..) + )); + assert!(matches!( *engine .parse_json("#{a:123}", true)