diff --git a/CHANGELOG.md b/CHANGELOG.md index 47fecdfb8..1dec812e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,17 +4,28 @@ Rhai Release Notes Version 1.18.0 ============== +Bug fixes +--------- + +* The position of an undefined operation call now points to the operator instead of the first operand. + Deprecated API's ---------------- * The plugin macros `export_fn`, `register_exported_fn!`, `set_exported_fn!` and `set_exported_global_fn!` are deprecated because they do not add value over existing direct API's. +New features +------------ + +* New options `Engine::set_max_strings_interned` and `Engine::max_strings_interned` are added to limit the maximum number of strings interned in the `Engine`'s string interner. + Enhancements ------------ * `FuncRegistration::in_global_namespace` and `FuncRegistration::in_internal_namespace` are added to avoid pulling in `FnNamespace`. * Array/BLOB/string iterators are defined also within the `BasicIteratorPackage` in addition to the regular array/BLOB/string packages. * `LexError::Runtime` is added for use with `Engine::on_parse_token`. +* Shared values under `sync` are now handled more elegantly -- instead of deadlocking and hanging indefinitely, it spins for a number of tries (waiting one second between each), then errors out. Version 1.17.2 @@ -26,25 +37,6 @@ Bug fixes * The engine no longer crashes when accessing a property or indexed item from a shared value returned from a variables resolver. -Version 1.18.0 -============== - -Bug fixes ---------- - -* The engine no longer crashes when accessing a property or indexed item from a shared value returned from a variables resolver. - -Deprecated API's ----------------- - -* The plugin macros `export_fn`, `register_exported_fn!`, `set_exported_fn!` and `set_exported_global_fn!` are deprecated because they do not add value over existing direct API's. - -Enhancements ------------- - -* `FuncRegistration::in_global_namespace` and `FuncRegistration::in_internal_namespace` are added to avoid pulling in `FnNamespace`. - - Version 1.17.1 ============== diff --git a/Cargo.toml b/Cargo.toml index e24e50633..038539f4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ categories = ["no-std", "embedded", "wasm", "parser-implementations"] [dependencies] smallvec = { version = "1.7.0", default-features = false, features = ["union", "const_new", "const_generics"] } thin-vec = { version = "0.2.13", default-features = false } -ahash = { version = "0.8.2", default-features = false, features = ["compile-time-rng"] } +ahash = { version = "0.8.2,<=0.8.7", default-features = false, features = ["compile-time-rng"] } # `ahash` bumps MSRV to 1.72 from 0.8.8 onwards num-traits = { version = "0.2.0", default-features = false } once_cell = { version = "1.7.0", default-features = false, features = ["race"] } bitflags = { version = "2.0.0", default-features = false } diff --git a/src/api/compile.rs b/src/api/compile.rs index 00f7d3c75..f52629793 100644 --- a/src/api/compile.rs +++ b/src/api/compile.rs @@ -2,7 +2,6 @@ use crate::func::native::locked_write; use crate::parser::{ParseResult, ParseState}; -use crate::types::StringsInterner; use crate::{Engine, OptimizationLevel, Scope, AST}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -220,15 +219,11 @@ impl Engine { ) -> ParseResult { let (stream, tc) = self.lex(scripts.as_ref()); - let mut interner; - let mut guard; - let interned_strings = if let Some(ref interner) = self.interned_strings { - guard = locked_write(interner); - &mut *guard - } else { - interner = StringsInterner::new(); - &mut interner - }; + let guard = &mut self + .interned_strings + .as_ref() + .and_then(|interner| locked_write(interner)); + let interned_strings = guard.as_deref_mut(); let input = &mut stream.peekable(); let lib = &mut <_>::default(); @@ -304,15 +299,11 @@ impl Engine { let scripts = [script]; let (stream, t) = self.lex(&scripts); - let mut interner; - let mut guard; - let interned_strings = if let Some(ref interner) = self.interned_strings { - guard = locked_write(interner); - &mut *guard - } else { - interner = StringsInterner::new(); - &mut interner - }; + let guard = &mut self + .interned_strings + .as_ref() + .and_then(|interner| locked_write(interner)); + let interned_strings = guard.as_deref_mut(); let input = &mut stream.peekable(); let lib = &mut <_>::default(); diff --git a/src/api/custom_syntax.rs b/src/api/custom_syntax.rs index 13255e0f3..c18e1c634 100644 --- a/src/api/custom_syntax.rs +++ b/src/api/custom_syntax.rs @@ -106,8 +106,8 @@ impl Expression<'_> { pub fn get_string_value(&self) -> Option<&str> { match self.0 { #[cfg(not(feature = "no_module"))] - Expr::Variable(x, ..) if !x.1.is_empty() => None, - Expr::Variable(x, ..) => Some(&x.3), + Expr::Variable(x, ..) if !x.2.is_empty() => None, + Expr::Variable(x, ..) => Some(&x.1), #[cfg(not(feature = "no_function"))] Expr::ThisPtr(..) => Some(crate::engine::KEYWORD_THIS), Expr::StringConstant(x, ..) => Some(x), @@ -138,7 +138,7 @@ impl Expression<'_> { Expr::CharConstant(x, ..) => reify! { *x => Option }, Expr::StringConstant(x, ..) => reify! { x.clone() => Option }, - Expr::Variable(x, ..) => reify! { x.3.clone() => Option }, + Expr::Variable(x, ..) => reify! { x.1.clone() => Option }, Expr::BoolConstant(x, ..) => reify! { *x => Option }, Expr::Unit(..) => reify! { () => Option }, diff --git a/src/api/eval.rs b/src/api/eval.rs index b1433eb4f..7d42eea7f 100644 --- a/src/api/eval.rs +++ b/src/api/eval.rs @@ -4,7 +4,6 @@ use crate::eval::{Caches, GlobalRuntimeState}; use crate::func::native::locked_write; use crate::parser::ParseState; use crate::types::dynamic::Variant; -use crate::types::StringsInterner; use crate::{Dynamic, Engine, Position, RhaiResult, RhaiResultOf, Scope, AST, ERR}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -115,15 +114,11 @@ impl Engine { ) -> RhaiResultOf { let scripts = [script]; let ast = { - let mut interner; - let mut guard; - let interned_strings = if let Some(ref interner) = self.interned_strings { - guard = locked_write(interner); - &mut *guard - } else { - interner = StringsInterner::new(); - &mut interner - }; + let guard = &mut self + .interned_strings + .as_ref() + .and_then(|interner| locked_write(interner)); + let interned_strings = guard.as_deref_mut(); let (stream, tc) = self.lex(&scripts); diff --git a/src/api/formatting.rs b/src/api/formatting.rs index a334ce2d5..69f464655 100644 --- a/src/api/formatting.rs +++ b/src/api/formatting.rs @@ -1,7 +1,7 @@ //! Module that provide formatting services to the [`Engine`]. +use crate::func::locked_write; use crate::packages::iter_basic::{BitRange, CharsStream, StepRange}; use crate::parser::{ParseResult, ParseState}; -use crate::types::StringsInterner; use crate::{ Engine, ExclusiveRange, FnPtr, ImmutableString, InclusiveRange, Position, RhaiError, SmartString, ERR, @@ -264,11 +264,16 @@ impl Engine { tc.borrow_mut().compressed = Some(String::new()); stream.state.last_token = Some(SmartString::new_const()); - let mut interner = StringsInterner::new(); + + let guard = &mut self + .interned_strings + .as_ref() + .and_then(|interner| locked_write(interner)); + let interned_strings = guard.as_deref_mut(); let input = &mut stream.peekable(); let lib = &mut <_>::default(); - let mut state = ParseState::new(None, &mut interner, input, tc, lib); + let mut state = ParseState::new(None, interned_strings, input, tc, lib); let mut _ast = self.parse( &mut state, diff --git a/src/api/json.rs b/src/api/json.rs index 7b5d25248..f4f0dbcb5 100644 --- a/src/api/json.rs +++ b/src/api/json.rs @@ -5,7 +5,6 @@ use crate::func::native::locked_write; use crate::parser::{ParseSettingFlags, ParseState}; use crate::tokenizer::Token; use crate::types::dynamic::Union; -use crate::types::StringsInterner; use crate::{Dynamic, Engine, LexError, Map, RhaiResultOf}; use std::fmt::Write; #[cfg(feature = "no_std")] @@ -118,15 +117,11 @@ impl Engine { ); let ast = { - let mut interner; - let mut guard; - let interned_strings = if let Some(ref interner) = self.interned_strings { - guard = locked_write(interner); - &mut *guard - } else { - interner = StringsInterner::new(); - &mut interner - }; + let guard = &mut self + .interned_strings + .as_ref() + .and_then(|interner| locked_write(interner)); + let interned_strings = guard.as_deref_mut(); let input = &mut stream.peekable(); let lib = &mut <_>::default(); @@ -221,7 +216,10 @@ fn format_dynamic_as_json(result: &mut String, value: &Dynamic) { *result += "]"; } #[cfg(not(feature = "no_closure"))] - Union::Shared(ref v, _, _) => format_dynamic_as_json(result, &crate::func::locked_read(v)), + Union::Shared(ref v, _, _) => { + let value = &*crate::func::locked_read(v).unwrap(); + format_dynamic_as_json(result, value) + } _ => write!(result, "{value:?}").unwrap(), } } diff --git a/src/api/limits.rs b/src/api/limits.rs index c561ba372..ee0e4fad8 100644 --- a/src/api/limits.rs +++ b/src/api/limits.rs @@ -35,6 +35,8 @@ pub mod default_limits { /// Not available under `no_function`. #[cfg(not(feature = "no_function"))] pub const MAX_FUNCTION_EXPR_DEPTH: usize = 32; + /// Maximum number of strings interned. + pub const MAX_STRINGS_INTERNED: usize = 1024; } /// A type containing all the limits imposed by the [`Engine`]. diff --git a/src/api/mod.rs b/src/api/mod.rs index c4c44c7d5..db8398457 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -33,6 +33,8 @@ pub mod definitions; pub mod deprecated; +use crate::func::{locked_read, locked_write}; +use crate::types::StringsInterner; use crate::{Dynamic, Engine, Identifier}; #[cfg(feature = "no_std")] @@ -46,9 +48,34 @@ pub mod default_limits { /// Maximum number of parameters in functions with [`Dynamic`][crate::Dynamic] support. pub const MAX_DYNAMIC_PARAMETERS: usize = 16; + /// Maximum number of strings interned. + pub const MAX_STRINGS_INTERNED: usize = 256; } impl Engine { + /// Set the maximum number of strings to be interned. + #[inline(always)] + pub fn set_max_strings_interned(&mut self, max: usize) -> &mut Self { + if max == 0 { + self.interned_strings = None; + } else if let Some(ref interner) = self.interned_strings { + if let Some(mut guard) = locked_write(interner) { + guard.set_max(max); + } + } else { + self.interned_strings = Some(StringsInterner::new(max).into()); + } + self + } + /// The maximum number of strings to be interned. + #[inline(always)] + #[must_use] + pub fn max_strings_interned(&self) -> usize { + self.interned_strings.as_ref().map_or(0, |interner| { + locked_read(interner).map_or(0, |guard| guard.max()) + }) + } + /// The module resolution service used by the [`Engine`]. /// /// Not available under `no_module`. diff --git a/src/api/run.rs b/src/api/run.rs index f21a1b3f7..3831a8855 100644 --- a/src/api/run.rs +++ b/src/api/run.rs @@ -3,7 +3,6 @@ use crate::eval::Caches; use crate::func::native::locked_write; use crate::parser::ParseState; -use crate::types::StringsInterner; use crate::{Engine, RhaiResultOf, Scope, AST}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -61,15 +60,11 @@ impl Engine { let ast = { let (stream, tc) = self.lex(&scripts); - let mut interner; - let mut guard; - let interned_strings = if let Some(ref interner) = self.interned_strings { - guard = locked_write(interner); - &mut *guard - } else { - interner = StringsInterner::new(); - &mut interner - }; + let guard = &mut self + .interned_strings + .as_ref() + .and_then(|interner| locked_write(interner)); + let interned_strings = guard.as_deref_mut(); let input = &mut stream.peekable(); let lib = &mut <_>::default(); diff --git a/src/ast/expr.rs b/src/ast/expr.rs index 334304420..8870b1b29 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -1,6 +1,6 @@ //! Module defining script expressions. -use super::{ASTFlags, ASTNode, Ident, Namespace, Stmt, StmtBlock}; +use super::{ASTFlags, ASTNode, Ident, Stmt, StmtBlock}; use crate::engine::KEYWORD_FN_PTR; use crate::tokenizer::Token; use crate::types::dynamic::Union; @@ -184,7 +184,8 @@ impl FnCallHashes { #[derive(Clone, Hash)] pub struct FnCallExpr { /// Namespace of the function, if any. - pub namespace: Namespace, + #[cfg(not(feature = "no_module"))] + pub namespace: super::Namespace, /// Function name. pub name: ImmutableString, /// Pre-calculated hashes. @@ -202,13 +203,14 @@ impl fmt::Debug for FnCallExpr { #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut ff = f.debug_struct("FnCallExpr"); + #[cfg(not(feature = "no_module"))] if !self.namespace.is_empty() { ff.field("namespace", &self.namespace); } ff.field("hash", &self.hashes) .field("name", &self.name) .field("args", &self.args); - if self.op_token.is_some() { + if self.is_operator_call() { ff.field("op_token", &self.op_token); } if self.capture_parent_scope { @@ -221,12 +223,19 @@ impl fmt::Debug for FnCallExpr { impl FnCallExpr { /// Does this function call contain a qualified namespace? /// - /// Always `false` under `no_module`. + /// Not available under [`no_module`] + #[cfg(not(feature = "no_module"))] #[inline(always)] #[must_use] pub fn is_qualified(&self) -> bool { !self.namespace.is_empty() } + /// Is this function call an operator expression? + #[inline(always)] + #[must_use] + pub fn is_operator_call(&self) -> bool { + self.op_token.is_some() + } /// Convert this into an [`Expr::FnCall`]. #[inline(always)] #[must_use] @@ -276,13 +285,15 @@ pub enum Expr { ), /// () Unit(Position), - /// Variable access - (optional long index, namespace, namespace hash, variable name), optional short index, position + /// Variable access - (optional long index, variable name, namespace, namespace hash), optional short index, position /// /// The short index is [`u8`] which is used when the index is <= 255, which should be /// the vast majority of cases (unless there are more than 255 variables defined!). /// This is to avoid reading a pointer redirection during each variable access. Variable( - Box<(Option, Namespace, u64, ImmutableString)>, + #[cfg(not(feature = "no_module"))] + Box<(Option, ImmutableString, super::Namespace, u64)>, + #[cfg(feature = "no_module")] Box<(Option, ImmutableString)>, Option, Position, ), @@ -371,16 +382,16 @@ impl fmt::Debug for Expr { f.write_str("Variable(")?; #[cfg(not(feature = "no_module"))] - if !x.1.is_empty() { + if !x.2.is_empty() { write!(f, "{}{}", x.1, crate::engine::NAMESPACE_SEPARATOR)?; - let pos = x.1.position(); + let pos = x.2.position(); if !pos.is_none() { display_pos = pos; } } - f.write_str(&x.3)?; + f.write_str(&x.1)?; #[cfg(not(feature = "no_module"))] - if let Some(n) = x.1.index { + if let Some(n) = x.2.index { write!(f, " #{n}")?; } if let Some(n) = i.map_or_else(|| x.0, |n| NonZeroUsize::new(n.get() as usize)) { @@ -495,18 +506,20 @@ impl Expr { s.into() } - // Fn - Self::FnCall(ref x, ..) - if !x.is_qualified() && x.args.len() == 1 && x.name == KEYWORD_FN_PTR => - { + // Qualified function call + #[cfg(not(feature = "no_module"))] + Self::FnCall(x, ..) if x.is_qualified() => return None, + + // Function call + Self::FnCall(x, ..) if x.args.len() == 1 && x.name == KEYWORD_FN_PTR => { match x.args[0] { Self::StringConstant(ref s, ..) => FnPtr::new(s.clone()).ok()?.into(), _ => return None, } } - // Binary operators - Self::FnCall(x, ..) if !x.is_qualified() && x.args.len() == 2 => { + // Binary operator call + Self::FnCall(x, ..) if x.args.len() == 2 => { pub const OP_EXCLUSIVE_RANGE: &str = Token::ExclusiveRange.literal_syntax(); pub const OP_INCLUSIVE_RANGE: &str = Token::InclusiveRange.literal_syntax(); @@ -559,7 +572,8 @@ impl Expr { Union::FnPtr(f, ..) if !f.is_curried() => Self::FnCall( FnCallExpr { - namespace: Namespace::NONE, + #[cfg(not(feature = "no_module"))] + namespace: super::Namespace::NONE, name: KEYWORD_FN_PTR.into(), hashes: FnCallHashes::from_hash(calc_fn_hash(None, f.fn_name(), 1)), args: once(Self::StringConstant(f.fn_name().into(), pos)).collect(), @@ -581,8 +595,8 @@ impl Expr { pub(crate) fn get_variable_name(&self, _non_qualified: bool) -> Option<&str> { match self { #[cfg(not(feature = "no_module"))] - Self::Variable(x, ..) if _non_qualified && !x.1.is_empty() => None, - Self::Variable(x, ..) => Some(&x.3), + Self::Variable(x, ..) if _non_qualified && !x.2.is_empty() => None, + Self::Variable(x, ..) => Some(&x.1), _ => None, } } @@ -661,10 +675,10 @@ impl Expr { match self { #[cfg(not(feature = "no_module"))] Self::Variable(x, ..) => { - if x.1.is_empty() { + if x.2.is_empty() { self.position() } else { - x.1.position() + x.2.position() } } diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 69a63d244..dd1a582e9 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -6,7 +6,6 @@ pub mod expr; pub mod flags; pub mod ident; pub mod namespace; -pub mod namespace_none; pub mod script_fn; pub mod stmt; @@ -18,8 +17,6 @@ pub use flags::{ASTFlags, FnAccess}; pub use ident::Ident; #[cfg(not(feature = "no_module"))] pub use namespace::Namespace; -#[cfg(feature = "no_module")] -pub use namespace_none::Namespace; #[cfg(not(feature = "no_function"))] pub use script_fn::{ScriptFnMetadata, ScriptFuncDef}; pub use stmt::{ diff --git a/src/ast/namespace_none.rs b/src/ast/namespace_none.rs deleted file mode 100644 index eafe1c522..000000000 --- a/src/ast/namespace_none.rs +++ /dev/null @@ -1,22 +0,0 @@ -//! Namespace reference type. -#![cfg(feature = "no_module")] - -#[cfg(feature = "no_std")] -use std::prelude::v1::*; - -/// _(internals)_ A chain of [module][crate::Module] names to namespace-qualify a variable or function call. -/// Exported under the `internals` feature only. -/// -/// Not available under `no_module`. -#[derive(Debug, Clone, Eq, PartialEq, Default, Hash)] -pub struct Namespace; - -impl Namespace { - /// Constant for no namespace. - pub const NONE: Self = Self; - - #[inline(always)] - pub const fn is_empty(&self) -> bool { - true - } -} diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs index f271e334e..de8f7d8b0 100644 --- a/src/ast/stmt.rs +++ b/src/ast/stmt.rs @@ -913,11 +913,16 @@ impl Stmt { Self::Expr(e) => match &**e { Expr::Stmt(s) => s.iter().all(Self::is_block_dependent), - Expr::FnCall(x, ..) => !x.is_qualified() && x.name == KEYWORD_EVAL, + #[cfg(not(feature = "no_module"))] + Expr::FnCall(x, ..) if x.is_qualified() => false, + Expr::FnCall(x, ..) => x.name == KEYWORD_EVAL, _ => false, }, - Self::FnCall(x, ..) => !x.is_qualified() && x.name == KEYWORD_EVAL, + #[cfg(not(feature = "no_module"))] + Self::FnCall(x, ..) if x.is_qualified() => false, + + Self::FnCall(x, ..) => x.name == KEYWORD_EVAL, #[cfg(not(feature = "no_module"))] Self::Import(..) | Self::Export(..) => true, diff --git a/src/engine.rs b/src/engine.rs index d974d4aec..176b73735 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,5 +1,6 @@ //! Main module defining the script evaluation [`Engine`]. +use crate::api::default_limits::MAX_STRINGS_INTERNED; use crate::api::options::LangOptions; use crate::func::native::{ locked_write, OnDebugCallback, OnDefVarCallback, OnParseTokenCallback, OnPrintCallback, @@ -96,7 +97,7 @@ pub struct Engine { pub(crate) module_resolver: Option>, /// Strings interner. - pub(crate) interned_strings: Option>>, + pub(crate) interned_strings: Option>, /// A set of symbols to disable. pub(crate) disabled_symbols: BTreeSet, @@ -279,7 +280,8 @@ impl Engine { Some(Box::new(crate::module::resolvers::FileModuleResolver::new())); } - engine.interned_strings = Some(Locked::new(StringsInterner::new()).into()); + // Turn on the strings interner + engine.set_max_strings_interned(MAX_STRINGS_INTERNED); // default print/debug implementations #[cfg(not(feature = "no_std"))] @@ -296,6 +298,7 @@ impl Engine { })); } + // Register the standard package engine.register_global_module(StandardPackage::new().as_shared_module()); engine @@ -326,7 +329,7 @@ impl Engine { string: impl AsRef + Into, ) -> ImmutableString { match self.interned_strings { - Some(ref interner) => locked_write(interner).get(string), + Some(ref interner) => locked_write(interner).unwrap().get(string), None => string.into(), } } diff --git a/src/eval/cache.rs b/src/eval/cache.rs index 8feedb05a..a45548165 100644 --- a/src/eval/cache.rs +++ b/src/eval/cache.rs @@ -26,7 +26,7 @@ pub struct FnResolutionCache { /// Hash map containing cached functions. pub dict: StraightHashMap>, /// Bloom filter to avoid caching "one-hit wonders". - pub filter: BloomFilterU64, + pub bloom_filter: BloomFilterU64, } impl FnResolutionCache { @@ -35,7 +35,7 @@ impl FnResolutionCache { #[allow(dead_code)] pub fn clear(&mut self) { self.dict.clear(); - self.filter.clear(); + self.bloom_filter.clear(); } } diff --git a/src/eval/chaining.rs b/src/eval/chaining.rs index 50fe50f4f..f91fc65d5 100644 --- a/src/eval/chaining.rs +++ b/src/eval/chaining.rs @@ -462,6 +462,7 @@ impl Engine { match (expr, ChainType::from(parent)) { #[cfg(not(feature = "no_object"))] (Expr::MethodCall(x, ..), ChainType::Dotting) => { + #[cfg(not(feature = "no_module"))] debug_assert!( !x.is_qualified(), "method call in dot chain should not be namespace-qualified" @@ -491,6 +492,7 @@ impl Engine { #[cfg(not(feature = "no_object"))] (Expr::MethodCall(x, ..), ChainType::Dotting) => { + #[cfg(not(feature = "no_module"))] debug_assert!( !x.is_qualified(), "method call in dot chain should not be namespace-qualified" @@ -735,6 +737,7 @@ impl Engine { } // xxx.fn_name(arg_expr_list) (Expr::MethodCall(x, pos), None, ..) => { + #[cfg(not(feature = "no_module"))] debug_assert!( !x.is_qualified(), "method call in dot chain should not be namespace-qualified" @@ -937,6 +940,7 @@ impl Engine { } // {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr Expr::MethodCall(ref x, pos) => { + #[cfg(not(feature = "no_module"))] debug_assert!( !x.is_qualified(), "method call in dot chain should not be namespace-qualified" @@ -1058,6 +1062,7 @@ impl Engine { } // xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr Expr::MethodCall(ref f, pos) => { + #[cfg(not(feature = "no_module"))] debug_assert!( !f.is_qualified(), "method call in dot chain should not be namespace-qualified" diff --git a/src/eval/expr.rs b/src/eval/expr.rs index 9f5b4b76f..515536e45 100644 --- a/src/eval/expr.rs +++ b/src/eval/expr.rs @@ -63,16 +63,19 @@ impl Engine { Expr::Variable(_, Some(i), ..) => i.get() as usize, Expr::Variable(v, None, ..) => { + #[cfg(not(feature = "no_module"))] + debug_assert!(v.2.is_empty(), "variable should not be namespace-qualified"); + // Scripted function with the same name #[cfg(not(feature = "no_function"))] if let Some(fn_def) = global .lib .iter() .flat_map(|m| m.iter_script_fn()) - .find_map(|(_, _, f, _, func)| if f == v.3 { Some(func) } else { None }) + .find_map(|(_, _, f, _, func)| if f == v.1 { Some(func) } else { None }) { let val: Dynamic = crate::FnPtr { - name: v.3.clone(), + name: v.1.clone(), curry: <_>::default(), environ: None, fn_def: Some(fn_def.clone()), @@ -156,14 +159,9 @@ impl Engine { self.search_scope_only(global, caches, scope, this_ptr, expr) } Expr::Variable(v, None, ..) => match &**v { - // Normal variable access - (_, ns, ..) if ns.is_empty() => { - self.search_scope_only(global, caches, scope, this_ptr, expr) - } - // Qualified variable access #[cfg(not(feature = "no_module"))] - (_, ns, hash_var, var_name) => { + (_, var_name, ns, hash_var) if !ns.is_empty() => { // foo:bar::baz::VARIABLE if let Some(module) = self.search_imports(global, ns) { return module.get_qualified_var(*hash_var).map_or_else( @@ -188,8 +186,9 @@ impl Engine { #[cfg(not(feature = "no_function"))] if ns.path.len() == 1 && ns.root() == crate::engine::KEYWORD_GLOBAL { if let Some(ref constants) = global.constants { - if let Some(value) = - crate::func::locked_write(constants).get_mut(var_name.as_str()) + if let Some(value) = crate::func::locked_write(constants) + .unwrap() + .get_mut(var_name.as_str()) { let mut target: Target = value.clone().into(); // Module variables are constant @@ -210,8 +209,8 @@ impl Engine { Err(ERR::ErrorModuleNotFound(ns.to_string(), ns.position()).into()) } - #[cfg(feature = "no_module")] - _ => unreachable!("Invalid expression {:?}", expr), + // Normal variable access + _ => self.search_scope_only(global, caches, scope, this_ptr, expr), }, _ => unreachable!("Expr::Variable expected but gets {:?}", expr), } diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index 3e8a5e9e2..496ed1a0d 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -336,7 +336,7 @@ impl Engine { // Cannot assign to temp result from expression if is_temp_result { return Err(ERR::ErrorAssignmentToConstant( - x.3.to_string(), + x.1.to_string(), lhs.position(), ) .into()); @@ -450,6 +450,7 @@ impl Engine { crate::Locked::new(std::collections::BTreeMap::new()), ) })) + .unwrap() .insert(var_name.name.clone(), value.clone()); } @@ -849,7 +850,7 @@ impl Engine { if scope.len() >= self.max_variables() { return Err(ERR::ErrorTooManyVariables(catch_var.position()).into()); } - scope.push(x.3.clone(), err_value); + scope.push(x.1.clone(), err_value); } let this_ptr = this_ptr.as_deref_mut(); diff --git a/src/func/call.rs b/src/func/call.rs index b5f3c0ab4..87797d592 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -222,7 +222,7 @@ impl Engine { func: f.clone(), source: s.cloned(), }; - return if cache.filter.is_absent_and_set(hash) { + return if cache.bloom_filter.is_absent_and_set(hash) { // Do not cache "one-hit wonders" *local_entry = Some(new_entry); local_entry.as_ref() @@ -298,7 +298,7 @@ impl Engine { }), }); - return if cache.filter.is_absent_and_set(hash) { + return if cache.bloom_filter.is_absent_and_set(hash) { // Do not cache "one-hit wonders" *local_entry = builtin; local_entry.as_ref() diff --git a/src/func/native.rs b/src/func/native.rs index 116893d3d..65ea13eba 100644 --- a/src/func/native.rs +++ b/src/func/native.rs @@ -524,12 +524,30 @@ pub fn shared_take(value: Shared) -> T { #[inline(always)] #[must_use] #[allow(dead_code)] -pub fn locked_read(value: &Locked) -> LockGuard { +pub fn locked_read(value: &Locked) -> Option> { #[cfg(not(feature = "sync"))] - return value.borrow(); + return value.try_borrow().ok(); #[cfg(feature = "sync")] - return value.read().unwrap(); + #[cfg(feature = "unchecked")] + return value.read().ok(); + + #[cfg(feature = "sync")] + #[cfg(not(feature = "unchecked"))] + { + // Spin-lock for a short while before giving up + for _ in 0..10 { + match value.try_read() { + Ok(guard) => return Some(guard), + Err(std::sync::TryLockError::WouldBlock) => { + std::thread::sleep(std::time::Duration::from_secs(1)) + } + Err(_) => return None, + } + } + + return None; + } } /// _(internals)_ Lock a [`Locked`] resource for mutable access. @@ -537,12 +555,30 @@ pub fn locked_read(value: &Locked) -> LockGuard { #[inline(always)] #[must_use] #[allow(dead_code)] -pub fn locked_write(value: &Locked) -> LockGuardMut { +pub fn locked_write(value: &Locked) -> Option> { #[cfg(not(feature = "sync"))] - return value.borrow_mut(); + return value.try_borrow_mut().ok(); #[cfg(feature = "sync")] - return value.write().unwrap(); + #[cfg(feature = "unchecked")] + return value.write().ok(); + + #[cfg(feature = "sync")] + #[cfg(not(feature = "unchecked"))] + { + // Spin-lock for a short while before giving up + for _ in 0..10 { + match value.try_write() { + Ok(guard) => return Some(guard), + Err(std::sync::TryLockError::WouldBlock) => { + std::thread::sleep(std::time::Duration::from_secs(1)) + } + Err(_) => return None, + } + } + + return None; + } } /// General Rust function trail object. diff --git a/src/func/script.rs b/src/func/script.rs index 719593fd9..fec54ab58 100644 --- a/src/func/script.rs +++ b/src/func/script.rs @@ -238,7 +238,7 @@ impl Engine { // Then check sub-modules || self.global_sub_modules.values().any(|m| m.contains_qualified_fn(hash_script)); - if !res && !cache.filter.is_absent_and_set(hash_script) { + if !res && !cache.bloom_filter.is_absent_and_set(hash_script) { // Do not cache "one-hit wonders" cache.dict.insert(hash_script, None); } diff --git a/src/module/resolvers/file.rs b/src/module/resolvers/file.rs index 04511ffd3..2795a80f5 100644 --- a/src/module/resolvers/file.rs +++ b/src/module/resolvers/file.rs @@ -48,10 +48,18 @@ pub const RHAI_SCRIPT_EXTENSION: &str = "rhai"; /// ``` #[derive(Debug)] pub struct FileModuleResolver { + /// Base path of the directory holding script files. base_path: Option, + /// File extension of script files, default `.rhai`. extension: Identifier, + /// Is the cache enabled? cache_enabled: bool, + /// [`Scope`] holding variables for compiling scripts. scope: Scope<'static>, + /// Internal cache of resolved modules. + /// + /// The cache is wrapped in interior mutability because [`resolve`][FileModuleResolver::resolve] + /// is immutable. cache: Locked>, } @@ -239,12 +247,14 @@ impl FileModuleResolver { if !self.cache_enabled { return false; } - locked_read(&self.cache).contains_key(path.as_ref()) + locked_read(&self.cache) + .unwrap() + .contains_key(path.as_ref()) } /// Empty the internal cache. #[inline] pub fn clear_cache(&mut self) -> &mut Self { - locked_write(&self.cache).clear(); + locked_write(&self.cache).unwrap().clear(); self } /// Remove the specified path from internal cache. @@ -254,6 +264,7 @@ impl FileModuleResolver { #[must_use] pub fn clear_cache_for_path(&mut self, path: impl AsRef) -> Option { locked_write(&self.cache) + .unwrap() .remove_entry(path.as_ref()) .map(|(.., v)| v) } @@ -298,7 +309,7 @@ impl FileModuleResolver { let file_path = self.get_file_path(path, source_path); if self.is_cache_enabled() { - if let Some(module) = locked_read(&self.cache).get(&file_path) { + if let Some(module) = locked_read(&self.cache).unwrap().get(&file_path) { return Ok(module.clone()); } } @@ -319,7 +330,9 @@ impl FileModuleResolver { .into(); if self.is_cache_enabled() { - locked_write(&self.cache).insert(file_path, m.clone()); + locked_write(&self.cache) + .unwrap() + .insert(file_path, m.clone()); } Ok(m) diff --git a/src/optimizer.rs b/src/optimizer.rs index a88f7021d..5a52e6033 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -405,7 +405,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b fn is_variable_access(expr: &Expr, _non_qualified: bool) -> bool { match expr { #[cfg(not(feature = "no_module"))] - Expr::Variable(x, ..) if _non_qualified && !x.1.is_empty() => false, + Expr::Variable(x, ..) if _non_qualified && !x.2.is_empty() => false, Expr::Variable(..) => true, _ => false, } @@ -879,6 +879,29 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b } } +// Convert a constant argument into [`Expr::DynamicConstant`]. +fn move_constant_arg(arg_expr: &mut Expr) -> bool { + match arg_expr { + Expr::DynamicConstant(..) + | Expr::Unit(..) + | Expr::StringConstant(..) + | Expr::CharConstant(..) + | Expr::BoolConstant(..) + | Expr::IntegerConstant(..) => false, + #[cfg(not(feature = "no_float"))] + Expr::FloatConstant(..) => false, + + _ => { + if let Some(value) = arg_expr.get_literal_value() { + *arg_expr = Expr::DynamicConstant(value.into(), arg_expr.start_position()); + true + } else { + false + } + } + } +} + /// Optimize an [expression][Expr]. fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { // These keywords are handled specially @@ -1117,17 +1140,21 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { } } + // nnn::id(args ..) -> optimize function call arguments + #[cfg(not(feature = "no_module"))] + Expr::FnCall(x, ..) if x.is_qualified() => x.args.iter_mut().for_each(|arg_expr| { + optimize_expr(arg_expr, state, false); + + if move_constant_arg(arg_expr) { + state.set_dirty(); + } + }), // eval! Expr::FnCall(x, ..) if x.name == KEYWORD_EVAL => { state.propagate_constants = false; } // Fn - Expr::FnCall(x, pos) - if !x.is_qualified() // Non-qualified - && x.args.len() == 1 - && x.name == KEYWORD_FN_PTR - && x.constant_args() - => { + Expr::FnCall(x, pos) if x.args.len() == 1 && x.name == KEYWORD_FN_PTR && x.constant_args() => { let fn_name = match x.args[0] { Expr::StringConstant(ref s, ..) => s.clone().into(), _ => Dynamic::UNIT @@ -1141,12 +1168,10 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { } } // curry(FnPtr, constants...) - Expr::FnCall(x, pos) - if !x.is_qualified() // Non-qualified - && x.args.len() >= 2 - && x.name == KEYWORD_FN_PTR_CURRY - && matches!(x.args[0], Expr::DynamicConstant(ref v, ..) if v.is_fnptr()) - && x.constant_args() + Expr::FnCall(x, pos) if x.args.len() >= 2 + && x.name == KEYWORD_FN_PTR_CURRY + && matches!(x.args[0], Expr::DynamicConstant(ref v, ..) if v.is_fnptr()) + && x.constant_args() => { let mut fn_ptr = x.args[0].get_literal_value().unwrap().cast::(); fn_ptr.extend(x.args.iter().skip(1).map(|arg_expr| arg_expr.get_literal_value().unwrap())); @@ -1160,10 +1185,8 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { } // Call built-in operators - Expr::FnCall(x, pos) - if !x.is_qualified() // Non-qualified - && state.optimization_level == OptimizationLevel::Simple // simple optimizations - && x.constant_args() // all arguments are constants + Expr::FnCall(x, pos) if state.optimization_level == OptimizationLevel::Simple // simple optimizations + && x.constant_args() // all arguments are constants => { let arg_values = &mut x.args.iter().map(|arg_expr| arg_expr.get_literal_value().unwrap()).collect::>(); let arg_types = arg_values.iter().map(Dynamic::type_id).collect::>(); @@ -1182,7 +1205,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { return; } // Overloaded operators can override built-in. - _ if x.args.len() == 2 && x.op_token.is_some() && (state.engine.fast_operators() || !state.engine.has_native_fn_override(x.hashes.native(), &arg_types)) => { + _ if x.args.len() == 2 && x.is_operator_call() && (state.engine.fast_operators() || !state.engine.has_native_fn_override(x.hashes.native(), &arg_types)) => { if let Some((f, ctx)) = get_builtin_binary_op_fn(x.op_token.as_ref().unwrap(), &arg_values[0], &arg_values[1]) { let context = ctx.then(|| (state.engine, x.name.as_str(), None, &state.global, *pos).into()); let (first, second) = arg_values.split_first_mut().unwrap(); @@ -1197,29 +1220,17 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { _ => () } - x.args.iter_mut().for_each(|arg_expr| optimize_expr(arg_expr, state, false)); - - // Move constant arguments - x.args.iter_mut().for_each(|arg_expr| match arg_expr { - Expr::DynamicConstant(..) | Expr::Unit(..) - | Expr::StringConstant(..) | Expr::CharConstant(..) - | Expr::BoolConstant(..) | Expr::IntegerConstant(..) => (), - - #[cfg(not(feature = "no_float"))] - Expr:: FloatConstant(..) => (), - - _ => if let Some(value) = arg_expr.get_literal_value() { - state.set_dirty(); - *arg_expr = Expr::DynamicConstant(value.into(), arg_expr.start_position()); - }, - }); + x.args.iter_mut().for_each(|arg_expr| { + optimize_expr(arg_expr, state, false); + if move_constant_arg(arg_expr) { + state.set_dirty(); + } + }); } // Eagerly call functions - Expr::FnCall(x, pos) - if !x.is_qualified() // non-qualified - && state.optimization_level == OptimizationLevel::Full // full optimizations - && x.constant_args() // all arguments are constants + Expr::FnCall(x, pos) if state.optimization_level == OptimizationLevel::Full // full optimizations + && x.constant_args() // all arguments are constants => { // First search for script-defined functions (can override built-in) let _has_script_fn = false; @@ -1249,29 +1260,17 @@ 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, ..) => x.args.iter_mut().for_each(|arg_expr| { optimize_expr(arg_expr, state, false); - - // Move constant arguments - match arg_expr { - Expr::DynamicConstant(..) | Expr::Unit(..) - | Expr::StringConstant(..) | Expr::CharConstant(..) - | Expr::BoolConstant(..) | Expr::IntegerConstant(..) => (), - - #[cfg(not(feature = "no_float"))] - Expr:: FloatConstant(..) => (), - - _ => if let Some(value) = arg_expr.get_literal_value() { - state.set_dirty(); - *arg_expr = Expr::DynamicConstant(value.into(), arg_expr.start_position()); - }, + if move_constant_arg(arg_expr) { + state.set_dirty(); } }), // constant-name #[cfg(not(feature = "no_module"))] - Expr::Variable(x, ..) if !x.1.is_empty() => (), - Expr::Variable(x, .., pos) if state.propagate_constants && state.find_literal_constant(&x.3).is_some() => { + Expr::Variable(x, ..) if !x.2.is_empty() => (), + Expr::Variable(x, .., pos) if state.propagate_constants && state.find_literal_constant(&x.1).is_some() => { // Replace constant with value - *expr = Expr::from_dynamic(state.find_literal_constant(&x.3).unwrap().clone(), *pos); + *expr = Expr::from_dynamic(state.find_literal_constant(&x.1).unwrap().clone(), *pos); state.set_dirty(); } diff --git a/src/parser.rs b/src/parser.rs index 86c3b064f..c1d3e6bec 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -3,7 +3,7 @@ use crate::api::options::LangOptions; use crate::ast::{ ASTFlags, BinaryExpr, CaseBlocksList, Expr, FlowControl, FnCallExpr, FnCallHashes, Ident, - Namespace, OpAssignment, RangeCase, ScriptFuncDef, Stmt, StmtBlock, StmtBlockContainer, + OpAssignment, RangeCase, ScriptFuncDef, Stmt, StmtBlock, StmtBlockContainer, SwitchCasesCollection, }; use crate::engine::{Precedence, OP_CONTAINS, OP_NOT}; @@ -62,7 +62,7 @@ pub struct ParseState<'a, 't, 's, 'f> { /// Controls whether parsing of an expression should stop given the next token. pub expr_filter: fn(&Token) -> bool, /// Strings interner. - pub interned_strings: &'s mut StringsInterner, + pub interned_strings: Option<&'s mut StringsInterner>, /// External [scope][Scope] with constants. pub external_constants: Option<&'a Scope<'a>>, /// Global runtime state. @@ -124,7 +124,7 @@ impl<'a, 't, 's, 'f> ParseState<'a, 't, 's, 'f> { #[must_use] pub fn new( external_constants: Option<&'a Scope>, - interned_strings: &'s mut StringsInterner, + interned_strings: Option<&'s mut StringsInterner>, input: &'t mut TokenStream<'a>, tokenizer_control: TokenizerControl, #[cfg(not(feature = "no_function"))] lib: &'f mut FnLib, @@ -208,7 +208,7 @@ impl<'a, 't, 's, 'f> ParseState<'a, 't, 's, 'f> { #[cfg(not(feature = "no_closure"))] if self.allow_capture { if !is_func_name && index == 0 && !self.external_vars.iter().any(|v| v.name == name) { - let name = self.interned_strings.get(name); + let name = self.get_interned_string(name); self.external_vars.push(Ident { name, pos: _pos }); } } else { @@ -247,7 +247,10 @@ impl<'a, 't, 's, 'f> ParseState<'a, 't, 's, 'f> { &mut self, text: impl AsRef + Into, ) -> ImmutableString { - self.interned_strings.get(text) + match self.interned_strings { + Some(ref mut interner) => interner.get(text), + None => text.into(), + } } /// Get an interned property getter, creating one if it is not yet interned. @@ -258,11 +261,14 @@ impl<'a, 't, 's, 'f> ParseState<'a, 't, 's, 'f> { &mut self, text: impl AsRef + Into, ) -> ImmutableString { - self.interned_strings.get_with_mapper( - b'g', - |s| crate::engine::make_getter(s.as_ref()).into(), - text, - ) + match self.interned_strings { + Some(ref mut interner) => interner.get_with_mapper( + b'g', + |s| crate::engine::make_getter(s.as_ref()).into(), + text, + ), + None => crate::engine::make_getter(text.as_ref()).into(), + } } /// Get an interned property setter, creating one if it is not yet interned. @@ -273,11 +279,14 @@ impl<'a, 't, 's, 'f> ParseState<'a, 't, 's, 'f> { &mut self, text: impl AsRef + Into, ) -> ImmutableString { - self.interned_strings.get_with_mapper( - b's', - |s| crate::engine::make_setter(s.as_ref()).into(), - text, - ) + match self.interned_strings { + Some(ref mut interner) => interner.get_with_mapper( + b's', + |s| crate::engine::make_setter(s.as_ref()).into(), + text, + ), + None => crate::engine::make_setter(text.as_ref()).into(), + } } } @@ -392,9 +401,9 @@ impl Expr { fn into_property(self, state: &mut ParseState) -> Self { match self { #[cfg(not(feature = "no_module"))] - Self::Variable(x, ..) if !x.1.is_empty() => unreachable!("qualified property"), + Self::Variable(x, ..) if !x.2.is_empty() => unreachable!("qualified property"), Self::Variable(x, .., pos) => { - let ident = x.3.clone(); + let ident = x.1.clone(); let getter = state.get_interned_getter(&ident); let hash_get = calc_fn_hash(None, &getter, 1); let setter = state.get_interned_setter(&ident); @@ -638,7 +647,7 @@ impl Engine { id: ImmutableString, no_args: bool, capture_parent_scope: bool, - namespace: Namespace, + #[cfg(not(feature = "no_module"))] mut namespace: crate::ast::Namespace, ) -> ParseResult { let (token, token_pos) = if no_args { &(Token::RightParen, Position::NONE) @@ -646,7 +655,6 @@ impl Engine { state.input.peek().unwrap() }; - let mut _namespace = namespace; let mut args = FnArgsVec::new(); match token { @@ -667,10 +675,10 @@ impl Engine { } #[cfg(not(feature = "no_module"))] - let hash = if _namespace.is_empty() { + let hash = if namespace.is_empty() { calc_fn_hash(None, &id, 0) } else { - let root = _namespace.root(); + let root = namespace.root(); let index = state.find_module(root); let is_global = false; @@ -685,13 +693,13 @@ impl Engine { && !self.global_sub_modules.contains_key(root) { return Err( - PERR::ModuleUndefined(root.into()).into_err(_namespace.position()) + PERR::ModuleUndefined(root.into()).into_err(namespace.position()) ); } - _namespace.index = index; + namespace.index = index; - calc_fn_hash(_namespace.path.iter().map(Ident::as_str), &id, 0) + calc_fn_hash(namespace.path.iter().map(Ident::as_str), &id, 0) }; #[cfg(feature = "no_module")] let hash = calc_fn_hash(None, &id, 0); @@ -708,7 +716,8 @@ impl Engine { name: state.get_interned_string(id), capture_parent_scope, op_token: None, - namespace: _namespace, + #[cfg(not(feature = "no_module"))] + namespace, hashes, args, } @@ -733,10 +742,10 @@ impl Engine { eat_token(state.input, &Token::RightParen); #[cfg(not(feature = "no_module"))] - let hash = if _namespace.is_empty() { + let hash = if namespace.is_empty() { calc_fn_hash(None, &id, args.len()) } else { - let root = _namespace.root(); + let root = namespace.root(); let index = state.find_module(root); #[cfg(not(feature = "no_function"))] @@ -752,13 +761,13 @@ impl Engine { && !self.global_sub_modules.contains_key(root) { return Err( - PERR::ModuleUndefined(root.into()).into_err(_namespace.position()) + PERR::ModuleUndefined(root.into()).into_err(namespace.position()) ); } - _namespace.index = index; + namespace.index = index; - calc_fn_hash(_namespace.path.iter().map(Ident::as_str), &id, args.len()) + calc_fn_hash(namespace.path.iter().map(Ident::as_str), &id, args.len()) }; #[cfg(feature = "no_module")] let hash = calc_fn_hash(None, &id, args.len()); @@ -775,7 +784,8 @@ impl Engine { name: state.get_interned_string(id), capture_parent_scope, op_token: None, - namespace: _namespace, + #[cfg(not(feature = "no_module"))] + namespace, hashes, args, } @@ -1465,18 +1475,14 @@ impl Engine { #[cfg(not(feature = "no_function"))] Token::Pipe | Token::Or if settings.has_option(LangOptions::ANON_FN) => { // Build new parse state - let new_interner = &mut StringsInterner::new(); let new_state = &mut ParseState::new( state.external_constants, - new_interner, + state.interned_strings.as_deref_mut(), state.input, state.tokenizer_control.clone(), state.lib, ); - // We move the strings interner to the new parse state object by swapping it... - std::mem::swap(state.interned_strings, new_state.interned_strings); - #[cfg(not(feature = "no_module"))] { // Do not allow storing an index to a globally-imported module @@ -1509,9 +1515,6 @@ impl Engine { let result = self.parse_anon_fn(new_state, new_settings.level_up()?); - // Restore the strings interner by swapping it back - std::mem::swap(state.interned_strings, new_state.interned_strings); - let (expr, fn_def, _externals) = result?; #[cfg(not(feature = "no_closure"))] @@ -1627,7 +1630,8 @@ impl Engine { // Identifier Token::Identifier(..) => { - let ns = Namespace::NONE; + #[cfg(not(feature = "no_module"))] + let ns = crate::ast::Namespace::NONE; let s = match state.input.next().unwrap() { (Token::Identifier(s), ..) => s, @@ -1641,7 +1645,10 @@ impl Engine { state.allow_capture = true; Expr::Variable( - (None, ns, 0, state.get_interned_string(*s)).into(), + #[cfg(not(feature = "no_module"))] + (None, state.get_interned_string(*s), ns, 0).into(), + #[cfg(feature = "no_module")] + (None, state.get_interned_string(*s)).into(), None, settings.pos, ) @@ -1661,7 +1668,7 @@ impl Engine { state.allow_capture = true; let name = state.get_interned_string(*s); - Expr::Variable((None, ns, 0, name).into(), None, settings.pos) + Expr::Variable((None, name, ns, 0).into(), None, settings.pos) } // Normal variable access _ => { @@ -1684,14 +1691,23 @@ impl Engine { .and_then(|x| u8::try_from(x.get()).ok()) .and_then(NonZeroU8::new); let name = state.get_interned_string(*s); - Expr::Variable((index, ns, 0, name).into(), short_index, settings.pos) + + Expr::Variable( + #[cfg(not(feature = "no_module"))] + (index, name, ns, 0).into(), + #[cfg(feature = "no_module")] + (index, name).into(), + short_index, + settings.pos, + ) } } } // Reserved keyword or symbol Token::Reserved(..) => { - let ns = Namespace::NONE; + #[cfg(not(feature = "no_module"))] + let ns = crate::ast::Namespace::NONE; let s = match state.input.next().unwrap() { (Token::Reserved(s), ..) => s, @@ -1704,7 +1720,10 @@ impl Engine { if is_reserved_keyword_or_symbol(&s).1 => { Expr::Variable( - (None, ns, 0, state.get_interned_string(*s)).into(), + #[cfg(not(feature = "no_module"))] + (None, state.get_interned_string(*s), ns, 0).into(), + #[cfg(feature = "no_module")] + (None, state.get_interned_string(*s)).into(), None, settings.pos, ) @@ -1767,7 +1786,7 @@ impl Engine { lhs = match (lhs, tail_token) { // Qualified function call with ! #[cfg(not(feature = "no_module"))] - (Expr::Variable(x, ..), Token::Bang) if !x.1.is_empty() => { + (Expr::Variable(x, ..), Token::Bang) if !x.2.is_empty() => { return match state.input.peek().unwrap() { (Token::LeftParen | Token::Unit, ..) => { Err(LexError::UnexpectedInput(Token::Bang.into()).into_err(tail_pos)) @@ -1794,18 +1813,42 @@ impl Engine { let no_args = state.input.next().unwrap().0 == Token::Unit; - let (.., ns, _, name) = *x; + #[cfg(not(feature = "no_module"))] + let (_, name, ns, ..) = *x; + #[cfg(feature = "no_module")] + let (_, name) = *x; + settings.pos = pos; - self.parse_fn_call(state, settings, name, no_args, true, ns)? + self.parse_fn_call( + state, + settings, + name, + no_args, + true, + #[cfg(not(feature = "no_module"))] + ns, + )? } // Function call (Expr::Variable(x, .., pos), t @ (Token::LeftParen | Token::Unit)) => { - let (.., ns, _, name) = *x; + #[cfg(not(feature = "no_module"))] + let (_, name, ns, ..) = *x; + #[cfg(feature = "no_module")] + let (_, name) = *x; + let no_args = t == Token::Unit; settings.pos = pos; - self.parse_fn_call(state, settings, name, no_args, false, ns)? + self.parse_fn_call( + state, + settings, + name, + no_args, + false, + #[cfg(not(feature = "no_module"))] + ns, + )? } // Disallowed module separator #[cfg(not(feature = "no_module"))] @@ -1822,14 +1865,14 @@ impl Engine { #[cfg(not(feature = "no_module"))] (Expr::Variable(x, .., pos), Token::DoubleColon) => { let (id2, pos2) = parse_var_name(state.input)?; - let (.., mut namespace, _, name) = *x; + let (_, name, mut namespace, ..) = *x; let var_name_def = Ident { name, pos }; namespace.path.push(var_name_def); let var_name = state.get_interned_string(id2); - Expr::Variable((None, namespace, 0, var_name).into(), None, pos2) + Expr::Variable((None, var_name, namespace, 0).into(), None, pos2) } // Indexing #[cfg(not(feature = "no_index"))] @@ -1888,16 +1931,16 @@ impl Engine { // Cache the hash key for namespace-qualified variables #[cfg(not(feature = "no_module"))] let namespaced_variable = match lhs { - Expr::Variable(ref mut x, ..) if !x.1.is_empty() => Some(&mut **x), + Expr::Variable(ref mut x, ..) if !x.2.is_empty() => Some(&mut **x), Expr::Index(ref mut x, ..) | Expr::Dot(ref mut x, ..) => match x.lhs { - Expr::Variable(ref mut x, ..) if !x.1.is_empty() => Some(&mut **x), + Expr::Variable(ref mut x, ..) if !x.2.is_empty() => Some(&mut **x), _ => None, }, _ => None, }; #[cfg(not(feature = "no_module"))] - if let Some((.., namespace, hash, name)) = namespaced_variable { + if let Some((.., name, namespace, hash)) = namespaced_variable { if !namespace.is_empty() { *hash = crate::calc_var_hash(namespace.path.iter().map(Ident::as_str), name); @@ -1970,7 +2013,8 @@ impl Engine { // Call negative function expr => Ok(FnCallExpr { - namespace: Namespace::NONE, + #[cfg(not(feature = "no_module"))] + namespace: crate::ast::Namespace::NONE, name: state.get_interned_string("-"), hashes: FnCallHashes::from_native_only(calc_fn_hash(None, "-", 1)), args: IntoIterator::into_iter([expr]).collect(), @@ -1992,7 +2036,8 @@ impl Engine { // Call plus function expr => Ok(FnCallExpr { - namespace: Namespace::NONE, + #[cfg(not(feature = "no_module"))] + namespace: crate::ast::Namespace::NONE, name: state.get_interned_string("+"), hashes: FnCallHashes::from_native_only(calc_fn_hash(None, "+", 1)), args: IntoIterator::into_iter([expr]).collect(), @@ -2008,7 +2053,8 @@ impl Engine { let pos = eat_token(state.input, &Token::Bang); Ok(FnCallExpr { - namespace: Namespace::NONE, + #[cfg(not(feature = "no_module"))] + namespace: crate::ast::Namespace::NONE, name: state.get_interned_string("!"), hashes: FnCallHashes::from_native_only(calc_fn_hash(None, "!", 1)), args: { @@ -2076,7 +2122,7 @@ impl Engine { } // var (indexed) = rhs Expr::Variable(ref x, i, var_pos) => { - let (index, .., name) = &**x; + let (index, name, ..) = &**x; let index = i.map_or_else( || index.expect("long or short index must be `Some`").get(), |n| n.get() as usize, @@ -2158,7 +2204,7 @@ impl Engine { } // lhs.module::id - syntax error #[cfg(not(feature = "no_module"))] - (.., Expr::Variable(x, ..)) if !x.1.is_empty() => unreachable!("lhs.ns::id"), + (.., Expr::Variable(x, ..)) if !x.2.is_empty() => unreachable!("lhs.ns::id"), // lhs.id (lhs, var_expr @ Expr::Variable(..)) => { let rhs = var_expr.into_property(state); @@ -2228,7 +2274,7 @@ impl Engine { match x.lhs { // lhs.module::id.dot_rhs or lhs.module::id[idx_rhs] - syntax error #[cfg(not(feature = "no_module"))] - Expr::Variable(x, ..) if !x.1.is_empty() => unreachable!("lhs.ns::id..."), + Expr::Variable(x, ..) if !x.2.is_empty() => unreachable!("lhs.ns::id..."), // lhs.module::func().dot_rhs or lhs.module::func()[idx_rhs] - syntax error #[cfg(not(feature = "no_module"))] Expr::FnCall(f, ..) if f.is_qualified() => { @@ -2367,7 +2413,8 @@ impl Engine { let native_only = !is_valid_function_name(&op); let mut op_base = FnCallExpr { - namespace: Namespace::NONE, + #[cfg(not(feature = "no_module"))] + namespace: crate::ast::Namespace::NONE, name: state.get_interned_string(&op), hashes: FnCallHashes::from_native_only(hash), args: IntoIterator::into_iter([root, rhs]).collect(), @@ -2376,19 +2423,6 @@ impl Engine { }; root = match op_token { - // '!=' defaults to true when passed invalid operands - Token::NotEqualsTo => op_base.into_fn_call_expr(pos), - - // Comparison operators default to false when passed invalid operands - Token::EqualsTo - | Token::LessThan - | Token::LessThanEqualsTo - | Token::GreaterThan - | Token::GreaterThanEqualsTo => { - let pos = op_base.args[0].start_position(); - op_base.into_fn_call_expr(pos) - } - Token::Or => { let rhs = op_base.args[1].take().ensure_bool_expr()?; let lhs = op_base.args[0].take().ensure_bool_expr()?; @@ -2407,7 +2441,6 @@ impl Engine { Token::In | Token::NotIn => { // Swap the arguments let (lhs, rhs) = op_base.args.split_first_mut().unwrap(); - let pos = lhs.start_position(); std::mem::swap(lhs, &mut rhs[0]); // Convert into a call to `contains` @@ -2420,7 +2453,8 @@ impl Engine { } else { // Put a `!` call in front let not_base = FnCallExpr { - namespace: Namespace::NONE, + #[cfg(not(feature = "no_module"))] + namespace: crate::ast::Namespace::NONE, name: state.get_interned_string(OP_NOT), hashes: FnCallHashes::from_native_only(calc_fn_hash(None, OP_NOT, 1)), args: IntoIterator::into_iter([fn_call]).collect(), @@ -2441,10 +2475,7 @@ impl Engine { op_base.into_fn_call_expr(pos) } - _ => { - let pos = op_base.args[0].start_position(); - op_base.into_fn_call_expr(pos) - } + _ => op_base.into_fn_call_expr(pos), }; } } @@ -2508,11 +2539,17 @@ impl Engine { let (name, pos) = parse_var_name(state.input)?; let name = state.get_interned_string(name); - let ns = Namespace::NONE; - segments.push(name.clone()); tokens.push(state.get_interned_string(CUSTOM_SYNTAX_MARKER_IDENT)); - inputs.push(Expr::Variable((None, ns, 0, name).into(), None, pos)); + + inputs.push(Expr::Variable( + #[cfg(not(feature = "no_module"))] + (None, name, crate::ast::Namespace::NONE, 0).into(), + #[cfg(feature = "no_module")] + (None, name).into(), + None, + pos, + )); } CUSTOM_SYNTAX_MARKER_SYMBOL => { let (symbol, pos) = match state.input.next().unwrap() { @@ -3270,7 +3307,7 @@ impl Engine { // Build new parse state let new_state = &mut ParseState::new( state.external_constants, - state.interned_strings, + state.interned_strings.as_deref_mut(), state.input, state.tokenizer_control.clone(), state.lib, @@ -3488,7 +3525,10 @@ impl Engine { state.stack.pop(); Expr::Variable( - (None, <_>::default(), 0, catch_var.name).into(), + #[cfg(not(feature = "no_module"))] + (None, catch_var.name, <_>::default(), 0).into(), + #[cfg(feature = "no_module")] + (None, catch_var.name).into(), None, catch_var.pos, ) @@ -3662,12 +3702,16 @@ impl Engine { Some(n) if !is_func => u8::try_from(n.get()).ok().and_then(NonZeroU8::new), _ => None, }; - Expr::Variable((index, <_>::default(), 0, name).into(), idx, pos) + #[cfg(not(feature = "no_module"))] + return Expr::Variable((index, name, <_>::default(), 0).into(), idx, pos); + #[cfg(feature = "no_module")] + return Expr::Variable((index, name).into(), idx, pos); }), ); let expr = FnCallExpr { - namespace: Namespace::NONE, + #[cfg(not(feature = "no_module"))] + namespace: crate::ast::Namespace::NONE, name: state.get_interned_string(crate::engine::KEYWORD_FN_PTR_CURRY), hashes: FnCallHashes::from_native_only(calc_fn_hash( None, diff --git a/src/tokenizer.rs b/src/tokenizer.rs index c671da5f4..c8c627316 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -2655,10 +2655,6 @@ impl Engine { self.lex_raw(inputs, Some(token_mapper)) } /// Tokenize an input text stream with an optional mapping function. - /// - /// # Panics - /// - /// Panics if there are no input streams. #[inline] #[must_use] pub(crate) fn lex_raw<'a>( diff --git a/src/types/dynamic.rs b/src/types/dynamic.rs index 6a2d91566..7343a97c8 100644 --- a/src/types/dynamic.rs +++ b/src/types/dynamic.rs @@ -112,7 +112,7 @@ enum DynamicReadLockInner<'d, T: Clone> { /// A simple reference to a non-shared value. Reference(&'d T), - /// A read guard to a shared value. + /// A read guard to a _shared_ value. #[cfg(not(feature = "no_closure"))] Guard(crate::func::native::LockGuard<'d, Dynamic>), } @@ -146,7 +146,7 @@ enum DynamicWriteLockInner<'d, T: Clone> { /// A simple mutable reference to a non-shared value. Reference(&'d mut T), - /// A write guard to a shared value. + /// A write guard to a _shared_ value. #[cfg(not(feature = "no_closure"))] Guard(crate::func::native::LockGuardMut<'d, Dynamic>), } @@ -336,7 +336,7 @@ impl Dynamic { Union::Variant(ref v, ..) => (***v).type_id(), #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, ..) => (*crate::func::locked_read(cell)).type_id(), + Union::Shared(ref cell, ..) => (*crate::func::locked_read(cell).unwrap()).type_id(), } } /// Get the name of the type of the value held by this [`Dynamic`]. @@ -370,7 +370,7 @@ impl Dynamic { Union::Variant(ref v, ..) => (***v).type_name(), #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, ..) => (*crate::func::locked_read(cell)).type_name(), + Union::Shared(ref cell, ..) => (*crate::func::locked_read(cell).unwrap()).type_name(), } } } @@ -409,7 +409,7 @@ impl Hash for Dynamic { } #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, ..) => (*crate::func::locked_read(cell)).hash(state), + Union::Shared(ref cell, ..) => (*crate::func::locked_read(cell).unwrap()).hash(state), Union::Variant(ref v, ..) => { let _value_any = (***v).as_any(); @@ -546,16 +546,10 @@ impl fmt::Display for Dynamic { #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) if cfg!(feature = "unchecked") => { - #[cfg(not(feature = "sync"))] - match cell.try_borrow() { - Ok(v) => { - fmt::Display::fmt(&*v, f)?; - f.write_str(" (shared)") - } - Err(_) => f.write_str(""), + match crate::func::locked_read(cell) { + Some(v) => write!(f, "{} (shared)", *v), + _ => f.write_str(""), } - #[cfg(feature = "sync")] - fmt::Display::fmt(&*cell.read().unwrap(), f) } #[cfg(not(feature = "no_closure"))] Union::Shared(..) => { @@ -565,36 +559,24 @@ impl fmt::Display for Dynamic { use std::collections::HashSet; // Avoid infinite recursion for shared values in a reference loop. - fn display_fmt( - value: &Dynamic, + fn display_fmt_print( f: &mut fmt::Formatter<'_>, + value: &Dynamic, dict: &mut HashSet<*const Dynamic>, ) -> fmt::Result { match value.0 { #[cfg(not(feature = "no_closure"))] - #[cfg(not(feature = "sync"))] - Union::Shared(ref cell, ..) => match cell.try_borrow() { - Ok(v) => { + Union::Shared(ref cell, ..) => match crate::func::locked_read(cell) { + Some(v) => { if dict.insert(value) { - display_fmt(&v, f, dict)?; + display_fmt_print(f, &v, dict)?; f.write_str(" (shared)") } else { f.write_str("") } } - Err(_) => f.write_str(""), + _ => f.write_str(""), }, - #[cfg(not(feature = "no_closure"))] - #[cfg(feature = "sync")] - Union::Shared(ref cell, ..) => { - let v = cell.read().unwrap(); - - if dict.insert(value) { - display_fmt(&*v, f, dict) - } else { - f.write_str("") - } - } #[cfg(not(feature = "no_index"))] Union::Array(ref arr, ..) => { dict.insert(value); @@ -604,7 +586,7 @@ impl fmt::Display for Dynamic { if i > 0 { f.write_str(", ")?; } - display_fmt(v, f, dict)?; + display_fmt_print(f, v, dict)?; } f.write_str("]") } @@ -619,7 +601,7 @@ impl fmt::Display for Dynamic { } fmt::Display::fmt(k, f)?; f.write_str(": ")?; - display_fmt(v, f, dict)?; + display_fmt_print(f, v, dict)?; } f.write_str("}") } @@ -627,7 +609,7 @@ impl fmt::Display for Dynamic { } } - display_fmt(self, f, &mut <_>::default()) + display_fmt_print(f, self, &mut <_>::default()) } } } @@ -724,16 +706,10 @@ impl fmt::Debug for Dynamic { #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) if cfg!(feature = "unchecked") => { - #[cfg(not(feature = "sync"))] - match cell.try_borrow() { - Ok(v) => { - fmt::Debug::fmt(&*v, f)?; - f.write_str(" (shared)") - } - Err(_) => f.write_str(""), + match crate::func::locked_read(cell) { + Some(v) => write!(f, "{:?} (shared)", *v), + _ => f.write_str(""), } - #[cfg(feature = "sync")] - fmt::Debug::fmt(&*cell.read().unwrap(), f) } #[cfg(not(feature = "no_closure"))] Union::Shared(..) => { @@ -743,37 +719,24 @@ impl fmt::Debug for Dynamic { use std::collections::HashSet; // Avoid infinite recursion for shared values in a reference loop. - fn debug_fmt( - value: &Dynamic, + fn debug_fmt_print( f: &mut fmt::Formatter<'_>, + value: &Dynamic, dict: &mut HashSet<*const Dynamic>, ) -> fmt::Result { match value.0 { #[cfg(not(feature = "no_closure"))] - #[cfg(not(feature = "sync"))] - Union::Shared(ref cell, ..) => match cell.try_borrow() { - Ok(v) => { + Union::Shared(ref cell, ..) => match crate::func::locked_read(cell) { + Some(v) => { if dict.insert(value) { - debug_fmt(&v, f, dict)?; + debug_fmt_print(f, &v, dict)?; f.write_str(" (shared)") } else { f.write_str("") } } - Err(_) => f.write_str(""), + _ => f.write_str(""), }, - #[cfg(not(feature = "no_closure"))] - #[cfg(feature = "sync")] - Union::Shared(ref cell, ..) => { - let v = cell.read().unwrap(); - - if dict.insert(value) { - debug_fmt(&*v, f, dict)?; - f.write_str(" (shared)") - } else { - f.write_str("") - } - } #[cfg(not(feature = "no_index"))] Union::Array(ref arr, ..) => { dict.insert(value); @@ -783,7 +746,7 @@ impl fmt::Debug for Dynamic { if i > 0 { f.write_str(", ")?; } - debug_fmt(v, f, dict)?; + debug_fmt_print(f, v, dict)?; } f.write_str("]") } @@ -798,7 +761,7 @@ impl fmt::Debug for Dynamic { } fmt::Debug::fmt(k, f)?; f.write_str(": ")?; - debug_fmt(v, f, dict)?; + debug_fmt_print(f, v, dict)?; } f.write_str("}") } @@ -814,7 +777,7 @@ impl fmt::Debug for Dynamic { fmt::Debug::fmt(fnptr.fn_name(), f)?; for curry in &fnptr.curry { f.write_str(", ")?; - debug_fmt(curry, f, dict)?; + debug_fmt_print(f, curry, dict)?; } f.write_str(")") } @@ -822,7 +785,7 @@ impl fmt::Debug for Dynamic { } } - debug_fmt(self, f, &mut <_>::default()) + debug_fmt_print(f, self, &mut <_>::default()) } } } @@ -1178,15 +1141,22 @@ impl Dynamic { /// /// This safe-guards constant values from being modified within Rust functions. /// - /// # Shared Values + /// # Shared Value /// /// If a [`Dynamic`] holds a _shared_ value, then it is read-only only if the shared value /// itself is read-only. + /// + /// Under the `sync` feature, a _shared_ value may deadlock. + /// Otherwise, the data may currently be borrowed for write (so its access mode cannot be determined). + /// + /// Under these circumstances, `false` is returned. + /// + /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[must_use] pub fn is_read_only(&self) -> bool { #[cfg(not(feature = "no_closure"))] if let Union::Shared(ref cell, ..) = self.0 { - return match crate::func::locked_read(cell).access_mode() { + return match crate::func::locked_read(cell).map_or(ReadWrite, |v| v.access_mode()) { ReadWrite => false, ReadOnly => true, }; @@ -1198,6 +1168,18 @@ impl Dynamic { } } /// Can this [`Dynamic`] be hashed? + /// + /// # Shared Value + /// + /// If a [`Dynamic`] holds a _shared_ value, then it is hashable only if the shared value + /// itself is hashable. + /// + /// Under the `sync` feature, a _shared_ value may deadlock. + /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). + /// + /// Under these circumstances, `false` is returned. + /// + /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[must_use] pub(crate) fn is_hashable(&self) -> bool { match self.0 { @@ -1269,7 +1251,9 @@ impl Dynamic { } #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, ..) => crate::func::locked_read(cell).is_hashable(), + Union::Shared(ref cell, ..) => { + crate::func::locked_read(cell).map_or(false, |v| v.is_hashable()) + } } } /// Create a [`Dynamic`] from any type. A [`Dynamic`] value is simply returned as is. @@ -1410,17 +1394,23 @@ impl Dynamic { } /// Convert the [`Dynamic`] value into specific type. /// - /// Casting to a [`Dynamic`] just returns as is, but if it contains a shared value, - /// it is cloned into a [`Dynamic`] with a normal value. + /// Casting to a [`Dynamic`] simply returns itself. /// - /// Returns [`None`] if types mismatched. + /// # Errors /// - /// # Panics or Deadlocks + /// Returns [`None`] if types mismatch. /// - /// Under the `sync` feature, this call may deadlock, or [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1). - /// Otherwise, this call panics if the data is currently borrowed for write. + /// # Shared Value + /// + /// If the [`Dynamic`] is a _shared_ value, it returns the shared value if there are no + /// outstanding references, or a cloned copy otherwise. /// - /// These normally shouldn't occur since most operations in Rhai is single-threaded. + /// Under the `sync` feature, a _shared_ value may deadlock. + /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). + /// + /// Under these circumstances, the cast also fails. + /// + /// These normally shouldn't occur since most operations in Rhai are single-threaded. /// /// # Example /// @@ -1439,10 +1429,23 @@ impl Dynamic { } /// Convert the [`Dynamic`] value into specific type. /// - /// Casting to a [`Dynamic`] just returns as is, but if it contains a shared value, - /// it is cloned into a [`Dynamic`] with a normal value. + /// Casting to a [`Dynamic`] simply returns itself. + /// + /// # Errors + /// + /// Returns itself as an error if types mismatch. /// - /// Returns itself if types mismatched. + /// # Shared Value + /// + /// If the [`Dynamic`] is a _shared_ value, it returns the shared value if there are no + /// outstanding references, or a cloned copy otherwise. + /// + /// Under the `sync` feature, a _shared_ value may deadlock. + /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). + /// + /// Under these circumstances, the cast also fails. + /// + /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[allow(unused_mut)] pub(crate) fn try_cast_raw(mut self) -> Result { // Coded this way in order to maximally leverage potentials for dead-code removal. @@ -1544,24 +1547,29 @@ impl Dynamic { Union::Variant(v, ..) if TypeId::of::() == (**v).type_id() => { Ok((*v).as_boxed_any().downcast().map(|x| *x).unwrap()) } - #[cfg(not(feature = "no_closure"))] - Union::Shared(..) => unreachable!("Union::Shared case should be already handled"), _ => Err(self), } } /// Convert the [`Dynamic`] value into a specific type. /// - /// Casting to a [`Dynamic`] just returns as is, but if it contains a shared value, - /// it is cloned into a [`Dynamic`] with a normal value. + /// Casting to a [`Dynamic`] just returns as is. /// - /// # Panics or Deadlocks + /// # Panics /// /// Panics if the cast fails (e.g. the type of the actual value is not the same as the specified type). /// - /// Under the `sync` feature, this call may deadlock, or [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1). - /// Otherwise, this call panics if the data is currently borrowed for write. + /// # Shared Value + /// + /// If the [`Dynamic`] is a _shared_ value, it returns the shared value if there are no + /// outstanding references, or a cloned copy otherwise. /// - /// These normally shouldn't occur since most operations in Rhai is single-threaded. + /// Under the `sync` feature, a _shared_ value may deadlock. + /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). + /// + /// Under these circumstances, the _shared_ value is simply cloned, which means that the returned + /// value is also shared. + /// + /// These normally shouldn't occur since most operations in Rhai are single-threaded. /// /// # Example /// @@ -1595,20 +1603,23 @@ impl Dynamic { } /// Clone the [`Dynamic`] value and convert it into a specific type. /// - /// Casting to a [`Dynamic`] just returns as is, but if it contains a shared value, - /// it is cloned into a [`Dynamic`] with a normal value. + /// Casting to a [`Dynamic`] just returns as is. /// - /// Returns [`None`] if types mismatched. - /// - /// # Panics or Deadlocks + /// # Panics /// /// Panics if the cast fails (e.g. the type of the actual value is not the /// same as the specified type). /// - /// Under the `sync` feature, this call may deadlock, or [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1). - /// Otherwise, this call panics if the data is currently borrowed for write. + /// # Shared Value + /// + /// If the [`Dynamic`] is a _shared_ value, a cloned copy of the shared value is returned. + /// + /// Under the `sync` feature, a _shared_ value may deadlock. + /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// - /// These normally shouldn't occur since most operations in Rhai is single-threaded. + /// Under these circumstances, the _shared_ value is simply cloned. + /// + /// This normally shouldn't occur since most operations in Rhai are single-threaded. /// /// # Example /// @@ -1627,38 +1638,66 @@ impl Dynamic { } /// Flatten the [`Dynamic`] and clone it. /// - /// If the [`Dynamic`] is not a shared value, it returns a cloned copy. + /// If the [`Dynamic`] is not a _shared_ value, a cloned copy is returned. + /// + /// If the [`Dynamic`] is a _shared_ value, a cloned copy of the shared value is returned. + /// + /// # Shared Value + /// + /// Under the `sync` feature, a _shared_ value may deadlock. + /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). /// - /// If the [`Dynamic`] is a shared value, it returns a cloned copy of the shared value. + /// Under these circumstances, the _shared_ value is simply cloned. + /// + /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[inline] pub fn flatten_clone(&self) -> Self { match self.0 { #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, ..) => crate::func::locked_read(cell).clone(), + Union::Shared(ref cell, ..) => { + crate::func::locked_read(cell).map_or_else(|| self.clone(), |v| v.flatten_clone()) + } _ => self.clone(), } } /// Flatten the [`Dynamic`]. /// - /// If the [`Dynamic`] is not a shared value, it returns itself. + /// If the [`Dynamic`] is not a _shared_ value, it simply returns itself. + /// + /// If the [`Dynamic`] is a _shared_ value, it returns the shared value if there are no + /// outstanding references, or a cloned copy otherwise. /// - /// If the [`Dynamic`] is a shared value, it returns the shared value if there are no - /// outstanding references, or a cloned copy. + /// # Shared Value + /// + /// Under the `sync` feature, a _shared_ value may deadlock. + /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). + /// + /// Under these circumstances, the _shared_ value is simply cloned, meaning that the result + /// value will also be _shared_. + /// + /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[inline] pub fn flatten(self) -> Self { match self.0 { #[cfg(not(feature = "no_closure"))] - Union::Shared(cell, ..) => crate::func::native::shared_try_take(cell).map_or_else( - |ref cell| crate::func::locked_read(cell).clone(), + Union::Shared(cell, tag, access) => match crate::func::native::shared_try_take(cell) { + // If there are no outstanding references, consume the shared value and return it #[cfg(not(feature = "sync"))] - crate::Locked::into_inner, + Ok(value) => value.into_inner().flatten(), #[cfg(feature = "sync")] - |value| value.into_inner().unwrap(), - ), + Ok(value) => value.into_inner().unwrap().flatten(), + // If there are outstanding references, return a cloned copy + Err(cell) => { + if let Some(guard) = crate::func::locked_read(&cell) { + return guard.flatten_clone(); + } + Dynamic(Union::Shared(cell, tag, access)) + } + }, _ => self, } } - /// Is the [`Dynamic`] a shared value that is locked? + /// Is the [`Dynamic`] a _shared_ value that is locked? /// /// Not available under `no_closure`. /// @@ -1682,28 +1721,35 @@ impl Dynamic { false } /// Get a reference of a specific type to the [`Dynamic`]. + /// /// Casting to [`Dynamic`] just returns a reference to it. /// /// Returns [`None`] if the cast fails. /// - /// # Panics or Deadlocks When Value is Shared + /// # Shared Value /// - /// Under the `sync` feature, this call may deadlock, or [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1). - /// Otherwise, this call panics if the data is currently borrowed for write. + /// Under the `sync` feature, a _shared_ value may deadlock. + /// Otherwise, this call also fails if the data is currently borrowed for write. + /// + /// Under these circumstances, [`None`] is also returned. + /// + /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[inline] pub fn read_lock(&self) -> Option> { match self.0 { #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) => { - let value = crate::func::locked_read(cell); - - return if (*value).type_id() != TypeId::of::() - && TypeId::of::() != TypeId::of::() - { - None + if let Some(guard) = crate::func::locked_read(cell) { + return if (*guard).type_id() != TypeId::of::() + && TypeId::of::() != TypeId::of::() + { + None + } else { + Some(DynamicReadLock(DynamicReadLockInner::Guard(guard))) + }; } else { - Some(DynamicReadLock(DynamicReadLockInner::Guard(value))) - }; + return None; + } } _ => (), } @@ -1713,28 +1759,35 @@ impl Dynamic { .map(DynamicReadLock) } /// Get a mutable reference of a specific type to the [`Dynamic`]. + /// /// Casting to [`Dynamic`] just returns a mutable reference to it. /// /// Returns [`None`] if the cast fails. /// - /// # Panics or Deadlocks When Value is Shared + /// # Shared Value /// - /// Under the `sync` feature, this call may deadlock, or [panic](https://doc.rust-lang.org/std/sync/struct.RwLock.html#panics-1). - /// Otherwise, this call panics if the data is currently borrowed for write. + /// Under the `sync` feature, a _shared_ value may deadlock. + /// Otherwise, this call also fails if the data is currently borrowed for write. + /// + /// Under these circumstances, [`None`] is also returned. + /// + /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[inline] pub fn write_lock(&mut self) -> Option> { match self.0 { #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) => { - let guard = crate::func::locked_write(cell); - - return if (*guard).type_id() != TypeId::of::() - && TypeId::of::() != TypeId::of::() - { - None + if let Some(guard) = crate::func::locked_write(cell) { + return if (*guard).type_id() != TypeId::of::() + && TypeId::of::() != TypeId::of::() + { + None + } else { + Some(DynamicWriteLock(DynamicWriteLockInner::Guard(guard))) + }; } else { - Some(DynamicWriteLock(DynamicWriteLockInner::Guard(guard))) - }; + return None; + } } _ => (), } @@ -1744,9 +1797,14 @@ impl Dynamic { .map(DynamicWriteLock) } /// Get a reference of a specific type to the [`Dynamic`]. + /// /// Casting to [`Dynamic`] just returns a reference to it. /// - /// Returns [`None`] if the cast fails, or if the value is _shared_. + /// Returns [`None`] if the cast fails. + /// + /// # Shared Value + /// + /// Returns [`None`] also if the value is _shared_. #[inline] #[must_use] pub(crate) fn downcast_ref(&self) -> Option<&T> { @@ -1842,9 +1900,14 @@ impl Dynamic { } } /// Get a mutable reference of a specific type to the [`Dynamic`]. + /// /// Casting to [`Dynamic`] just returns a mutable reference to it. /// - /// Returns [`None`] if the cast fails, or if the value is _shared_. + /// Returns [`None`] if the cast fails. + /// + /// # Shared Value + /// + /// Returns [`None`] also if the value is _shared_. #[inline] #[must_use] pub(crate) fn downcast_mut(&mut self) -> Option<&mut T> { @@ -1941,34 +2004,61 @@ impl Dynamic { } /// Return `true` if the [`Dynamic`] holds a `()`. + /// + /// # Shared Value + /// + /// Under the `sync` feature, a _shared_ value may deadlock. + /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). + /// + /// Under these circumstances, `false` is returned. + /// + /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[inline] #[must_use] pub fn is_unit(&self) -> bool { match self.0 { Union::Unit(..) => true, #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, ..) => { - matches!(crate::func::locked_read(cell).0, Union::Unit(..)) - } + Union::Shared(ref cell, ..) => crate::func::locked_read(cell) + .map(|v| matches!(v.0, Union::Unit(..))) + .unwrap_or(false), _ => false, } } /// Return `true` if the [`Dynamic`] holds the system integer type [`INT`]. + /// + /// # Shared Value + /// + /// Under the `sync` feature, a _shared_ value may deadlock. + /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). + /// + /// Under these circumstances, `false` is returned. + /// + /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[inline] #[must_use] pub fn is_int(&self) -> bool { match self.0 { Union::Int(..) => true, #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, ..) => { - matches!(crate::func::locked_read(cell).0, Union::Int(..)) - } + Union::Shared(ref cell, ..) => crate::func::locked_read(cell) + .map(|v| matches!(v.0, Union::Int(..))) + .unwrap_or(false), _ => false, } } /// Return `true` if the [`Dynamic`] holds the system floating-point type [`FLOAT`][crate::FLOAT]. /// /// Not available under `no_float`. + /// + /// # Shared Value + /// + /// Under the `sync` feature, a _shared_ value may deadlock. + /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). + /// + /// Under these circumstances, `false` is returned. + /// + /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[cfg(not(feature = "no_float"))] #[inline] #[must_use] @@ -1976,15 +2066,23 @@ impl Dynamic { match self.0 { Union::Float(..) => true, #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, ..) => { - matches!(crate::func::locked_read(cell).0, Union::Float(..)) - } + Union::Shared(ref cell, ..) => crate::func::locked_read(cell) + .map(|v| matches!(v.0, Union::Float(..))) + .unwrap_or(false), _ => false, } } /// _(decimal)_ Return `true` if the [`Dynamic`] holds a [`Decimal`][rust_decimal::Decimal]. - /// /// Exported under the `decimal` feature only. + /// + /// # Shared Value + /// + /// Under the `sync` feature, a _shared_ value may deadlock. + /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). + /// + /// Under these circumstances, `false` is returned. + /// + /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[cfg(feature = "decimal")] #[inline] #[must_use] @@ -1992,54 +2090,90 @@ impl Dynamic { match self.0 { Union::Decimal(..) => true, #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, ..) => { - matches!(crate::func::locked_read(cell).0, Union::Decimal(..)) - } + Union::Shared(ref cell, ..) => crate::func::locked_read(cell) + .map(|v| matches!(v.0, Union::Decimal(..))) + .unwrap_or(false), _ => false, } } /// Return `true` if the [`Dynamic`] holds a [`bool`]. + /// + /// # Shared Value + /// + /// Under the `sync` feature, a _shared_ value may deadlock. + /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). + /// + /// Under these circumstances, `false` is returned. + /// + /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[inline] #[must_use] pub fn is_bool(&self) -> bool { match self.0 { Union::Bool(..) => true, #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, ..) => { - matches!(crate::func::locked_read(cell).0, Union::Bool(..)) - } + Union::Shared(ref cell, ..) => crate::func::locked_read(cell) + .map(|v| matches!(v.0, Union::Bool(..))) + .unwrap_or(false), _ => false, } } /// Return `true` if the [`Dynamic`] holds a [`char`]. + /// + /// # Shared Value + /// + /// Under the `sync` feature, a _shared_ value may deadlock. + /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). + /// + /// Under these circumstances, `false` is returned. + /// + /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[inline] #[must_use] pub fn is_char(&self) -> bool { match self.0 { Union::Char(..) => true, #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, ..) => { - matches!(crate::func::locked_read(cell).0, Union::Char(..)) - } + Union::Shared(ref cell, ..) => crate::func::locked_read(cell) + .map(|v| matches!(v.0, Union::Char(..))) + .unwrap_or(false), _ => false, } } /// Return `true` if the [`Dynamic`] holds an [`ImmutableString`]. + /// + /// # Shared Value + /// + /// Under the `sync` feature, a _shared_ value may deadlock. + /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). + /// + /// Under these circumstances, `false` is returned. + /// + /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[inline] #[must_use] pub fn is_string(&self) -> bool { match self.0 { Union::Str(..) => true, #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, ..) => { - matches!(crate::func::locked_read(cell).0, Union::Str(..)) - } + Union::Shared(ref cell, ..) => crate::func::locked_read(cell) + .map(|v| matches!(v.0, Union::Str(..))) + .unwrap_or(false), _ => false, } } /// Return `true` if the [`Dynamic`] holds an [`Array`][crate::Array]. /// /// Not available under `no_index`. + /// + /// # Shared Value + /// + /// Under the `sync` feature, a _shared_ value may deadlock. + /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). + /// + /// Under these circumstances, `false` is returned. + /// + /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[cfg(not(feature = "no_index"))] #[inline] #[must_use] @@ -2047,15 +2181,24 @@ impl Dynamic { match self.0 { Union::Array(..) => true, #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, ..) => { - matches!(crate::func::locked_read(cell).0, Union::Array(..)) - } + Union::Shared(ref cell, ..) => crate::func::locked_read(cell) + .map(|v| matches!(v.0, Union::Array(..))) + .unwrap_or(false), _ => false, } } /// Return `true` if the [`Dynamic`] holds a [`Blob`][crate::Blob]. /// /// Not available under `no_index`. + /// + /// # Shared Value + /// + /// Under the `sync` feature, a _shared_ value may deadlock. + /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). + /// + /// Under these circumstances, `false` is returned. + /// + /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[cfg(not(feature = "no_index"))] #[inline] #[must_use] @@ -2063,15 +2206,24 @@ impl Dynamic { match self.0 { Union::Blob(..) => true, #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, ..) => { - matches!(crate::func::locked_read(cell).0, Union::Blob(..)) - } + Union::Shared(ref cell, ..) => crate::func::locked_read(cell) + .map(|v| matches!(v.0, Union::Blob(..))) + .unwrap_or(false), _ => false, } } /// Return `true` if the [`Dynamic`] holds a [`Map`][crate::Map]. /// /// Not available under `no_object`. + /// + /// # Shared Value + /// + /// Under the `sync` feature, a _shared_ value may deadlock. + /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). + /// + /// Under these circumstances, `false` is returned. + /// + /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[cfg(not(feature = "no_object"))] #[inline] #[must_use] @@ -2079,28 +2231,46 @@ impl Dynamic { match self.0 { Union::Map(..) => true, #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, ..) => { - matches!(crate::func::locked_read(cell).0, Union::Map(..)) - } + Union::Shared(ref cell, ..) => crate::func::locked_read(cell) + .map(|v| matches!(v.0, Union::Map(..))) + .unwrap_or(false), _ => false, } } /// Return `true` if the [`Dynamic`] holds a [`FnPtr`]. + /// + /// # Shared Value + /// + /// Under the `sync` feature, a _shared_ value may deadlock. + /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). + /// + /// Under these circumstances, `false` is returned. + /// + /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[inline] #[must_use] pub fn is_fnptr(&self) -> bool { match self.0 { Union::FnPtr(..) => true, #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, ..) => { - matches!(crate::func::locked_read(cell).0, Union::FnPtr(..)) - } + Union::Shared(ref cell, ..) => crate::func::locked_read(cell) + .map(|v| matches!(v.0, Union::FnPtr(..))) + .unwrap_or(false), _ => false, } } /// Return `true` if the [`Dynamic`] holds a [timestamp][Instant]. /// /// Not available under `no_time`. + /// + /// # Shared Value + /// + /// Under the `sync` feature, a _shared_ value may deadlock. + /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). + /// + /// Under these circumstances, `false` is returned. + /// + /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[cfg(not(feature = "no_time"))] #[inline] #[must_use] @@ -2108,146 +2278,282 @@ impl Dynamic { match self.0 { Union::TimeStamp(..) => true, #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, ..) => { - matches!(crate::func::locked_read(cell).0, Union::TimeStamp(..)) - } + Union::Shared(ref cell, ..) => crate::func::locked_read(cell) + .map(|v| matches!(v.0, Union::TimeStamp(..))) + .unwrap_or(false), _ => false, } } /// Cast the [`Dynamic`] as a unit `()`. - /// Returns the name of the actual type if the cast fails. + /// + /// # Errors + /// + /// Returns the name of the actual type as an error if the cast fails. + /// + /// # Shared Value + /// + /// Under the `sync` feature, a _shared_ value may deadlock. + /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). + /// + /// Under these circumstances, the cast also fails. + /// + /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[inline] pub fn as_unit(&self) -> Result<(), &'static str> { match self.0 { Union::Unit(..) => Ok(()), #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, ..) => match crate::func::locked_read(cell).0 { - Union::Unit(..) => Ok(()), - _ => Err(cell.type_name()), - }, + Union::Shared(ref cell, ..) => crate::func::locked_read(cell) + .and_then(|guard| match guard.0 { + Union::Unit(..) => Some(()), + _ => None, + }) + .ok_or_else(|| cell.type_name()), _ => Err(self.type_name()), } } /// Cast the [`Dynamic`] as the system integer type [`INT`]. - /// Returns the name of the actual type if the cast fails. + /// + /// # Errors + /// + /// Returns the name of the actual type as an error if the cast fails. + /// + /// # Shared Value + /// + /// Under the `sync` feature, a _shared_ value may deadlock. + /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). + /// + /// Under these circumstances, the cast also fails. + /// + /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[inline] pub fn as_int(&self) -> Result { match self.0 { Union::Int(n, ..) => Ok(n), #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, ..) => match crate::func::locked_read(cell).0 { - Union::Int(n, ..) => Ok(n), - _ => Err(cell.type_name()), - }, + Union::Shared(ref cell, ..) => crate::func::locked_read(cell) + .and_then(|guard| match guard.0 { + Union::Int(n, ..) => Some(n), + _ => None, + }) + .ok_or_else(|| cell.type_name()), _ => Err(self.type_name()), } } /// Cast the [`Dynamic`] as the system floating-point type [`FLOAT`][crate::FLOAT]. - /// Returns the name of the actual type if the cast fails. /// /// Not available under `no_float`. + /// + /// # Errors + /// + /// Returns the name of the actual type as an error if the cast fails. + /// + /// # Shared Value + /// + /// Under the `sync` feature, a _shared_ value may deadlock. + /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). + /// + /// Under these circumstances, the cast also fails. + /// + /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[cfg(not(feature = "no_float"))] #[inline] pub fn as_float(&self) -> Result { match self.0 { Union::Float(n, ..) => Ok(*n), #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, ..) => match crate::func::locked_read(cell).0 { - Union::Float(n, ..) => Ok(*n), - _ => Err(cell.type_name()), - }, + Union::Shared(ref cell, ..) => crate::func::locked_read(cell) + .and_then(|guard| match guard.0 { + Union::Float(n, ..) => Some(*n), + _ => None, + }) + .ok_or_else(|| cell.type_name()), _ => Err(self.type_name()), } } /// _(decimal)_ Cast the [`Dynamic`] as a [`Decimal`][rust_decimal::Decimal]. - /// Returns the name of the actual type if the cast fails. - /// /// Exported under the `decimal` feature only. + /// + /// # Errors + /// + /// Returns the name of the actual type as an error if the cast fails. + /// + /// # Shared Value + /// + /// Under the `sync` feature, a _shared_ value may deadlock. + /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). + /// + /// Under these circumstances, the cast also fails. + /// + /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[cfg(feature = "decimal")] #[inline] pub fn as_decimal(&self) -> Result { match self.0 { Union::Decimal(ref n, ..) => Ok(**n), #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, ..) => match crate::func::locked_read(cell).0 { - Union::Decimal(ref n, ..) => Ok(**n), - _ => Err(cell.type_name()), - }, + Union::Shared(ref cell, ..) => crate::func::locked_read(cell) + .and_then(|guard| match guard.0 { + Union::Decimal(ref n, ..) => Some(**n), + _ => None, + }) + .ok_or_else(|| cell.type_name()), _ => Err(self.type_name()), } } /// Cast the [`Dynamic`] as a [`bool`]. - /// Returns the name of the actual type if the cast fails. + /// + /// # Errors + /// + /// Returns the name of the actual type as an error if the cast fails. + /// + /// # Shared Value + /// + /// Under the `sync` feature, a _shared_ value may deadlock. + /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). + /// + /// Under these circumstances, the cast also fails. + /// + /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[inline] pub fn as_bool(&self) -> Result { match self.0 { Union::Bool(b, ..) => Ok(b), #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, ..) => match crate::func::locked_read(cell).0 { - Union::Bool(b, ..) => Ok(b), - _ => Err(cell.type_name()), - }, + Union::Shared(ref cell, ..) => crate::func::locked_read(cell) + .and_then(|guard| match guard.0 { + Union::Bool(b, ..) => Some(b), + _ => None, + }) + .ok_or_else(|| cell.type_name()), _ => Err(self.type_name()), } } /// Cast the [`Dynamic`] as a [`char`]. - /// Returns the name of the actual type if the cast fails. + /// + /// # Errors + /// + /// Returns the name of the actual type as an error if the cast fails. + /// + /// # Shared Value + /// + /// Under the `sync` feature, a _shared_ value may deadlock. + /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). + /// + /// Under these circumstances, the cast also fails. + /// + /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[inline] pub fn as_char(&self) -> Result { match self.0 { Union::Char(c, ..) => Ok(c), #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, ..) => match crate::func::locked_read(cell).0 { - Union::Char(c, ..) => Ok(c), - _ => Err(cell.type_name()), - }, + Union::Shared(ref cell, ..) => crate::func::locked_read(cell) + .and_then(|guard| match guard.0 { + Union::Char(c, ..) => Some(c), + _ => None, + }) + .ok_or_else(|| cell.type_name()), _ => Err(self.type_name()), } } /// Convert the [`Dynamic`] into a [`String`]. + /// /// If there are other references to the same string, a cloned copy is returned. - /// Returns the name of the actual type if the cast fails. + /// + /// # Errors + /// + /// Returns the name of the actual type as an error if the cast fails. + /// + /// # Shared Value + /// + /// Under the `sync` feature, a _shared_ value may deadlock. + /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). + /// + /// Under these circumstances, the cast also fails. + /// + /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[inline] pub fn into_string(self) -> Result { self.into_immutable_string() .map(ImmutableString::into_owned) } /// Convert the [`Dynamic`] into an [`ImmutableString`]. - /// Returns the name of the actual type if the cast fails. + /// + /// # Errors + /// + /// Returns the name of the actual type as an error if the cast fails. + /// + /// # Shared Value + /// + /// Under the `sync` feature, a _shared_ value may deadlock. + /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). + /// + /// Under these circumstances, the cast also fails. + /// + /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[inline] pub fn into_immutable_string(self) -> Result { match self.0 { Union::Str(s, ..) => Ok(s), #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, ..) => match crate::func::locked_read(cell).0 { - Union::Str(ref s, ..) => Ok(s.clone()), - _ => Err(cell.type_name()), - }, + Union::Shared(ref cell, ..) => crate::func::locked_read(cell) + .and_then(|guard| match guard.0 { + Union::Str(ref s, ..) => Some(s.clone()), + _ => None, + }) + .ok_or_else(|| cell.type_name()), _ => Err(self.type_name()), } } /// Convert the [`Dynamic`] into an [`Array`][crate::Array]. - /// Returns the name of the actual type if the cast fails. /// /// Not available under `no_index`. + /// + /// # Errors + /// + /// Returns the name of the actual type as an error if the cast fails. + /// + /// # Shared Value + /// + /// Under the `sync` feature, a _shared_ value may deadlock. + /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). + /// + /// Under these circumstances, the cast also fails. + /// + /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[cfg(not(feature = "no_index"))] #[inline(always)] pub fn into_array(self) -> Result { match self.0 { Union::Array(a, ..) => Ok(*a), #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, ..) => match crate::func::locked_read(cell).0 { - Union::Array(ref a, ..) => Ok(a.as_ref().clone()), - _ => Err(cell.type_name()), - }, + Union::Shared(ref cell, ..) => crate::func::locked_read(cell) + .and_then(|guard| match guard.0 { + Union::Array(ref a, ..) => Some(a.as_ref().clone()), + _ => None, + }) + .ok_or_else(|| cell.type_name()), _ => Err(self.type_name()), } } /// Convert the [`Dynamic`] into a [`Vec`]. - /// Returns the name of the actual type if any cast fails. /// /// Not available under `no_index`. + /// + /// # Errors + /// + /// Returns the name of the actual type as an error if the cast fails. + /// + /// # Shared Value + /// + /// Under the `sync` feature, a _shared_ value may deadlock. + /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). + /// + /// Under these circumstances, the cast also fails. + /// + /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[cfg(not(feature = "no_index"))] #[inline(always)] pub fn into_typed_array(self) -> Result, &'static str> { @@ -2272,48 +2578,49 @@ impl Dynamic { Ok(reify! { *b => !!! Vec }) } #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, ..) => { - match crate::func::locked_read(cell).0 { - Union::Array(ref a, ..) => { - a.iter() - .map(|v| { - #[cfg(not(feature = "no_closure"))] - let typ = if v.is_shared() { - // Avoid panics/deadlocks with shared values - "" - } else { - v.type_name() - }; - #[cfg(feature = "no_closure")] - let typ = v.type_name(); - - v.read_lock::().ok_or(typ).map(|v| v.clone()) - }) - .collect() - } + Union::Shared(ref cell, ..) => crate::func::locked_read(cell) + .and_then(|guard| match guard.0 { + Union::Array(ref a, ..) => a + .iter() + .map(|v| v.read_lock::().map(|v| v.clone())) + .collect(), Union::Blob(ref b, ..) if TypeId::of::() == TypeId::of::() => { - Ok(reify! { b.clone() => !!! Vec }) + Some(reify! { b.clone() => !!! Vec }) } - _ => Err(cell.type_name()), - } - } + _ => None, + }) + .ok_or_else(|| cell.type_name()), _ => Err(self.type_name()), } } /// Convert the [`Dynamic`] into a [`Blob`][crate::Blob]. - /// Returns the name of the actual type if the cast fails. /// /// Not available under `no_index`. + /// + /// # Errors + /// + /// Returns the name of the actual type as an error if the cast fails. + /// + /// # Shared Value + /// + /// Under the `sync` feature, a _shared_ value may deadlock. + /// Otherwise, the data may currently be borrowed for write (so its type cannot be determined). + /// + /// Under these circumstances, the cast also fails. + /// + /// These normally shouldn't occur since most operations in Rhai are single-threaded. #[cfg(not(feature = "no_index"))] #[inline(always)] pub fn into_blob(self) -> Result { match self.0 { Union::Blob(b, ..) => Ok(*b), #[cfg(not(feature = "no_closure"))] - Union::Shared(ref cell, ..) => match crate::func::locked_read(cell).0 { - Union::Blob(ref b, ..) => Ok(b.as_ref().clone()), - _ => Err(cell.type_name()), - }, + Union::Shared(ref cell, ..) => crate::func::locked_read(cell) + .and_then(|guard| match guard.0 { + Union::Blob(ref b, ..) => Some(b.as_ref().clone()), + _ => None, + }) + .ok_or_else(|| cell.type_name()), _ => Err(self.type_name()), } } @@ -2321,6 +2628,8 @@ impl Dynamic { /// Recursively scan for [`Dynamic`] values within this [`Dynamic`] (e.g. items in an array or map), /// calling a filter function on each. /// + /// # Shared Value + /// /// Shared values are _NOT_ scanned. #[inline] #[allow(clippy::only_used_in_recursion)] diff --git a/src/types/interner.rs b/src/types/interner.rs index d1f874cf4..c63f16e0d 100644 --- a/src/types/interner.rs +++ b/src/types/interner.rs @@ -15,30 +15,21 @@ use std::{ ops::AddAssign, }; -/// Maximum number of strings interned. -pub const MAX_INTERNED_STRINGS: usize = 1024; - /// Maximum length of strings interned. -pub const MAX_STRING_LEN: usize = 24; +pub const MAX_STRING_LEN: usize = 32; /// _(internals)_ A cache for interned strings. /// Exported under the `internals` feature only. #[derive(Clone)] pub struct StringsInterner { + /// Maximum number of strings to be interned. + max_strings_interned: usize, /// Cached strings. cache: StraightHashMap, /// Bloom filter to avoid caching "one-hit wonders". bloom_filter: BloomFilterU64, } -impl Default for StringsInterner { - #[inline(always)] - #[must_use] - fn default() -> Self { - Self::new() - } -} - impl fmt::Debug for StringsInterner { #[cold] #[inline(never)] @@ -51,8 +42,9 @@ impl StringsInterner { /// Create a new [`StringsInterner`]. #[inline(always)] #[must_use] - pub fn new() -> Self { + pub fn new(max_strings_interned: usize) -> Self { Self { + max_strings_interned, cache: <_>::default(), bloom_filter: BloomFilterU64::new(), } @@ -65,6 +57,19 @@ impl StringsInterner { self.get_with_mapper(0, Into::into, text) } + /// Set the maximum number of strings to be interned. + #[inline(always)] + pub fn set_max(&mut self, max: usize) { + self.max_strings_interned = max; + self.throttle_cache(None); + } + /// The maximum number of strings to be interned. + #[inline(always)] + #[must_use] + pub const fn max(&self) -> usize { + self.max_strings_interned + } + /// Get an identifier from a text string, adding it to the interner if necessary. #[inline] #[must_use] @@ -76,6 +81,10 @@ impl StringsInterner { ) -> ImmutableString { let key = text.as_ref(); + if self.max() == 0 { + return mapper(text); + } + let hasher = &mut get_hasher(); hasher.write_u8(category); key.hash(hasher); @@ -87,7 +96,8 @@ impl StringsInterner { } if self.cache.is_empty() { - self.cache.reserve(MAX_INTERNED_STRINGS); + // Reserve a good size to kick start the strings interner + self.cache.reserve(128); } let result = match self.cache.entry(hash) { @@ -96,28 +106,32 @@ impl StringsInterner { }; // Throttle the cache upon exit - self.throttle_cache(hash); + self.throttle_cache(Some(hash)); result } /// If the interner is over capacity, remove the longest entry that has the lowest count #[inline] - fn throttle_cache(&mut self, skip_hash: u64) { - if self.cache.len() <= MAX_INTERNED_STRINGS { + fn throttle_cache(&mut self, skip_hash: Option) { + if self.max() == 0 { + self.clear(); + return; + } + if self.cache.len() <= self.max() { return; } // Leave some buffer to grow when shrinking the cache. // We leave at least two entries, one for the empty string, and one for the string // that has just been inserted. - while self.cache.len() > MAX_INTERNED_STRINGS - 3 { + while self.cache.len() > self.max() - 3 { let mut max_len = 0; let mut min_count = usize::MAX; let mut index = 0; for (&k, v) in &self.cache { - if k != skip_hash + if skip_hash.map_or(true, |hash| k != hash) && (v.strong_count() < min_count || (v.strong_count() == min_count && v.len() > max_len)) { @@ -152,6 +166,7 @@ impl StringsInterner { #[allow(dead_code)] pub fn clear(&mut self) { self.cache.clear(); + self.bloom_filter.clear(); } } @@ -159,6 +174,7 @@ impl AddAssign for StringsInterner { #[inline(always)] fn add_assign(&mut self, rhs: Self) { self.cache.extend(rhs.cache); + self.bloom_filter += rhs.bloom_filter; } } @@ -167,5 +183,6 @@ impl AddAssign<&Self> for StringsInterner { fn add_assign(&mut self, rhs: &Self) { self.cache .extend(rhs.cache.iter().map(|(&k, v)| (k, v.clone()))); + self.bloom_filter += &rhs.bloom_filter; } } diff --git a/tests/closures.rs b/tests/closures.rs index 89585d8ff..39bf88565 100644 --- a/tests/closures.rs +++ b/tests/closures.rs @@ -191,6 +191,12 @@ fn test_closures() { .unwrap(), 42 ); + + #[cfg(not(feature = "unchecked"))] + { + let x = engine.eval::("let x = #{}; x.a = || x; x").unwrap(); + println!("{x:?}"); + } } #[test]