Merge branch 'master' into plugins

This commit is contained in:
Stephen Chung
2020-06-01 10:58:26 +08:00
18 changed files with 586 additions and 335 deletions

View File

@@ -1,11 +1,9 @@
//! Helper module which defines the `Any` trait to to allow dynamic value handling.
use crate::module::Module;
use crate::parser::{ImmutableString, INT};
use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast};
#[cfg(not(feature = "no_module"))]
use crate::module::Module;
#[cfg(not(feature = "no_float"))]
use crate::parser::FLOAT;
@@ -160,7 +158,6 @@ pub enum Union {
Array(Box<Array>),
#[cfg(not(feature = "no_object"))]
Map(Box<Map>),
#[cfg(not(feature = "no_module"))]
Module(Box<Module>),
Variant(Box<Box<dyn Variant>>),
}
@@ -198,7 +195,6 @@ impl Dynamic {
Union::Array(_) => TypeId::of::<Array>(),
#[cfg(not(feature = "no_object"))]
Union::Map(_) => TypeId::of::<Map>(),
#[cfg(not(feature = "no_module"))]
Union::Module(_) => TypeId::of::<Module>(),
Union::Variant(value) => (***value).type_id(),
}
@@ -218,7 +214,6 @@ impl Dynamic {
Union::Array(_) => "array",
#[cfg(not(feature = "no_object"))]
Union::Map(_) => "map",
#[cfg(not(feature = "no_module"))]
Union::Module(_) => "sub-scope",
#[cfg(not(feature = "no_std"))]
@@ -242,7 +237,6 @@ impl fmt::Display for Dynamic {
Union::Array(value) => write!(f, "{:?}", value),
#[cfg(not(feature = "no_object"))]
Union::Map(value) => write!(f, "#{:?}", value),
#[cfg(not(feature = "no_module"))]
Union::Module(value) => write!(f, "{:?}", value),
#[cfg(not(feature = "no_std"))]
@@ -266,7 +260,6 @@ impl fmt::Debug for Dynamic {
Union::Array(value) => write!(f, "{:?}", value),
#[cfg(not(feature = "no_object"))]
Union::Map(value) => write!(f, "#{:?}", value),
#[cfg(not(feature = "no_module"))]
Union::Module(value) => write!(f, "{:?}", value),
#[cfg(not(feature = "no_std"))]
@@ -290,7 +283,6 @@ impl Clone for Dynamic {
Union::Array(ref value) => Self(Union::Array(value.clone())),
#[cfg(not(feature = "no_object"))]
Union::Map(ref value) => Self(Union::Map(value.clone())),
#[cfg(not(feature = "no_module"))]
Union::Module(ref value) => Self(Union::Module(value.clone())),
Union::Variant(ref value) => (***value).clone_into_dynamic(),
}
@@ -426,7 +418,6 @@ impl Dynamic {
Union::Array(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
#[cfg(not(feature = "no_object"))]
Union::Map(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
#[cfg(not(feature = "no_module"))]
Union::Module(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v),
Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).ok(),
}
@@ -470,7 +461,6 @@ impl Dynamic {
Union::Array(value) => *unsafe_cast_box::<_, T>(value).unwrap(),
#[cfg(not(feature = "no_object"))]
Union::Map(value) => *unsafe_cast_box::<_, T>(value).unwrap(),
#[cfg(not(feature = "no_module"))]
Union::Module(value) => *unsafe_cast_box::<_, T>(value).unwrap(),
Union::Variant(value) => (*value).as_box_any().downcast().map(|x| *x).unwrap(),
}
@@ -498,7 +488,6 @@ impl Dynamic {
Union::Array(value) => (value.as_ref() as &dyn Any).downcast_ref::<T>(),
#[cfg(not(feature = "no_object"))]
Union::Map(value) => (value.as_ref() as &dyn Any).downcast_ref::<T>(),
#[cfg(not(feature = "no_module"))]
Union::Module(value) => (value.as_ref() as &dyn Any).downcast_ref::<T>(),
Union::Variant(value) => value.as_ref().as_ref().as_any().downcast_ref::<T>(),
}
@@ -524,7 +513,6 @@ impl Dynamic {
Union::Array(value) => (value.as_mut() as &mut dyn Any).downcast_mut::<T>(),
#[cfg(not(feature = "no_object"))]
Union::Map(value) => (value.as_mut() as &mut dyn Any).downcast_mut::<T>(),
#[cfg(not(feature = "no_module"))]
Union::Module(value) => (value.as_mut() as &mut dyn Any).downcast_mut::<T>(),
Union::Variant(value) => value.as_mut().as_mut_any().downcast_mut::<T>(),
}

View File

@@ -997,6 +997,7 @@ impl Engine {
}
/// Call a script function defined in an `AST` with multiple arguments.
/// Arguments are passed as a tuple.
///
/// # Example
///
@@ -1040,6 +1041,67 @@ impl Engine {
args: A,
) -> Result<T, Box<EvalAltResult>> {
let mut arg_values = args.into_vec();
let result = self.call_fn_dynamic(scope, ast, name, arg_values.as_mut())?;
let return_type = self.map_type_name(result.type_name());
return result.try_cast().ok_or_else(|| {
Box::new(EvalAltResult::ErrorMismatchOutputType(
return_type.into(),
Position::none(),
))
});
}
/// Call a script function defined in an `AST` with multiple `Dynamic` arguments.
///
/// ## WARNING
///
/// All the arguments are _consumed_, meaning that they're replaced by `()`.
/// This is to avoid unnecessarily cloning the arguments.
/// Do you use the arguments after this call. If you need them afterwards,
/// clone them _before_ calling this function.
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// # #[cfg(not(feature = "no_function"))]
/// # {
/// use rhai::{Engine, Scope};
///
/// let engine = Engine::new();
///
/// let ast = engine.compile(r"
/// fn add(x, y) { len(x) + y + foo }
/// fn add1(x) { len(x) + 1 + foo }
/// fn bar() { foo/2 }
/// ")?;
///
/// let mut scope = Scope::new();
/// scope.push("foo", 42_i64);
///
/// // Call the script-defined function
/// let result = engine.call_fn_dynamic(&mut scope, &ast, "add", &mut [ String::from("abc").into(), 123_i64.into() ])?;
/// assert_eq!(result.cast::<i64>(), 168);
///
/// let result = engine.call_fn_dynamic(&mut scope, &ast, "add1", &mut [ String::from("abc").into() ])?;
/// assert_eq!(result.cast::<i64>(), 46);
///
/// let result= engine.call_fn_dynamic(&mut scope, &ast, "bar", &mut [])?;
/// assert_eq!(result.cast::<i64>(), 21);
/// # }
/// # Ok(())
/// # }
/// ```
#[cfg(not(feature = "no_function"))]
pub fn call_fn_dynamic(
&self,
scope: &mut Scope,
ast: &AST,
name: &str,
arg_values: &mut [Dynamic],
) -> Result<Dynamic, Box<EvalAltResult>> {
let mut args: StaticVec<_> = arg_values.iter_mut().collect();
let lib = ast.lib();
let pos = Position::none();
@@ -1051,16 +1113,7 @@ impl Engine {
let mut state = State::new();
let args = args.as_mut();
let result = self.call_script_fn(scope, &mut state, &lib, name, fn_def, args, pos, 0)?;
let return_type = self.map_type_name(result.type_name());
return result.try_cast().ok_or_else(|| {
Box::new(EvalAltResult::ErrorMismatchOutputType(
return_type.into(),
pos,
))
});
self.call_script_fn(scope, &mut state, &lib, name, fn_def, args, pos, 0)
}
/// Optimize the `AST` with constants defined in an external Scope.

View File

@@ -4,7 +4,7 @@ use crate::any::{Dynamic, Union};
use crate::calc_fn_hash;
use crate::error::ParseErrorType;
use crate::fn_native::{CallableFunction, FnCallArgs, Shared};
use crate::module::Module;
use crate::module::{resolvers, Module, ModuleResolver};
use crate::optimize::OptimizationLevel;
use crate::packages::{CorePackage, Package, PackageLibrary, PackagesCollection, StandardPackage};
use crate::parser::{Expr, FnAccess, FnDef, ImmutableString, ReturnType, Stmt, AST, INT};
@@ -12,14 +12,11 @@ use crate::r#unsafe::{unsafe_cast_var_name_to_lifetime, unsafe_mut_cast_to_lifet
use crate::result::EvalAltResult;
use crate::scope::{EntryType as ScopeEntryType, Scope};
use crate::token::Position;
use crate::utils::StaticVec;
use crate::utils::{StaticVec, StraightHasherBuilder};
#[cfg(not(feature = "no_float"))]
use crate::parser::FLOAT;
#[cfg(not(feature = "no_module"))]
use crate::module::{resolvers, ModuleResolver};
use crate::stdlib::{
any::TypeId,
boxed::Box,
@@ -196,7 +193,7 @@ impl State {
///
/// The key of the `HashMap` is a `u64` hash calculated by the function `calc_fn_hash`.
#[derive(Debug, Clone, Default)]
pub struct FunctionsLib(HashMap<u64, Shared<FnDef>>);
pub struct FunctionsLib(HashMap<u64, Shared<FnDef>, StraightHasherBuilder>);
impl FunctionsLib {
/// Create a new `FunctionsLib` from a collection of `FnDef`.
@@ -261,7 +258,7 @@ impl From<Vec<(u64, Shared<FnDef>)>> for FunctionsLib {
}
impl Deref for FunctionsLib {
type Target = HashMap<u64, Shared<FnDef>>;
type Target = HashMap<u64, Shared<FnDef>, StraightHasherBuilder>;
fn deref(&self) -> &Self::Target {
&self.0
@@ -269,7 +266,7 @@ impl Deref for FunctionsLib {
}
impl DerefMut for FunctionsLib {
fn deref_mut(&mut self) -> &mut HashMap<u64, Shared<FnDef>> {
fn deref_mut(&mut self) -> &mut HashMap<u64, Shared<FnDef>, StraightHasherBuilder> {
&mut self.0
}
}
@@ -297,7 +294,6 @@ pub struct Engine {
pub(crate) packages: PackagesCollection,
/// A module resolution service.
#[cfg(not(feature = "no_module"))]
pub(crate) module_resolver: Option<Box<dyn ModuleResolver>>,
/// A hashmap mapping type names to pretty-print names.
@@ -350,8 +346,7 @@ impl Default for Engine {
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_std"))]
module_resolver: Some(Box::new(resolvers::FileModuleResolver::new())),
#[cfg(not(feature = "no_module"))]
#[cfg(feature = "no_std")]
#[cfg(any(feature = "no_module", feature = "no_std"))]
module_resolver: None,
type_names: HashMap::new(),
@@ -441,35 +436,33 @@ fn search_scope<'s, 'a>(
Expr::Variable(x) => x.as_ref(),
_ => unreachable!(),
};
let index = if state.always_search { None } else { *index };
#[cfg(not(feature = "no_module"))]
{
if let Some(modules) = modules.as_ref() {
let module = if let Some(index) = modules.index() {
scope
.get_mut(scope.len() - index.get())
.0
.downcast_mut::<Module>()
.unwrap()
} else {
let (id, root_pos) = modules.get(0);
if let Some(modules) = modules.as_ref() {
let module = if let Some(index) = modules.index() {
scope
.get_mut(scope.len() - index.get())
.0
.downcast_mut::<Module>()
.unwrap()
} else {
let (id, root_pos) = modules.get(0);
scope.find_module(id).ok_or_else(|| {
Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos))
})?
};
scope
.find_module_internal(id)
.ok_or_else(|| Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos)))?
};
return Ok((
module.get_qualified_var_mut(name, *hash_var, *pos)?,
name,
// Module variables are constant
ScopeEntryType::Constant,
*pos,
));
}
return Ok((
module.get_qualified_var_mut(name, *hash_var, *pos)?,
name,
// Module variables are constant
ScopeEntryType::Constant,
*pos,
));
}
let index = if state.always_search { None } else { *index };
let index = if let Some(index) = index {
scope.len() - index.get()
} else {
@@ -495,8 +488,6 @@ impl Engine {
Self {
packages: Default::default(),
global_module: Default::default(),
#[cfg(not(feature = "no_module"))]
module_resolver: None,
type_names: HashMap::new(),
@@ -952,21 +943,24 @@ impl Engine {
let mut idx_val = idx_values.pop();
if is_index {
let pos = rhs.position();
match rhs {
// xxx[idx].dot_rhs... | xxx[idx][dot_rhs]...
// xxx[idx].expr... | xxx[idx][expr]...
Expr::Dot(x) | Expr::Index(x) => {
let (idx, expr, pos) = x.as_ref();
let is_idx = matches!(rhs, Expr::Index(_));
let pos = x.0.position();
let this_ptr = &mut self
.get_indexed_mut(state, lib, obj, is_ref, idx_val, pos, op_pos, false)?;
let idx_pos = idx.position();
let this_ptr = &mut self.get_indexed_mut(
state, lib, obj, is_ref, idx_val, idx_pos, op_pos, false,
)?;
self.eval_dot_index_chain_helper(
state, lib, this_ptr, &x.1, idx_values, is_idx, x.2, level, new_val,
state, lib, this_ptr, expr, idx_values, is_idx, *pos, level, new_val,
)
}
// xxx[rhs] = new_val
_ if new_val.is_some() => {
let pos = rhs.position();
let this_ptr = &mut self
.get_indexed_mut(state, lib, obj, is_ref, idx_val, pos, op_pos, true)?;
@@ -975,16 +969,7 @@ impl Engine {
}
// xxx[rhs]
_ => self
.get_indexed_mut(
state,
lib,
obj,
is_ref,
idx_val,
rhs.position(),
op_pos,
false,
)
.get_indexed_mut(state, lib, obj, is_ref, idx_val, pos, op_pos, false)
.map(|v| (v.clone_into_dynamic(), false)),
}
} else {
@@ -1050,57 +1035,51 @@ impl Engine {
.map(|(v, _)| (v, false))
}
#[cfg(not(feature = "no_object"))]
// {xxx:map}.idx_lhs[idx_expr] | {xxx:map}.dot_lhs.rhs
// {xxx:map}.prop[expr] | {xxx:map}.prop.expr
Expr::Index(x) | Expr::Dot(x) if obj.is::<Map>() => {
let (prop, expr, pos) = x.as_ref();
let is_idx = matches!(rhs, Expr::Index(_));
let mut val = if let Expr::Property(p) = &x.0 {
let mut val = if let Expr::Property(p) = prop {
let ((prop, _, _), _) = p.as_ref();
let index = prop.clone().into();
self.get_indexed_mut(state, lib, obj, is_ref, index, x.2, op_pos, false)?
self.get_indexed_mut(state, lib, obj, is_ref, index, *pos, op_pos, false)?
} else {
// Syntax error
return Err(Box::new(EvalAltResult::ErrorDotExpr(
"".into(),
rhs.position(),
)));
unreachable!();
};
self.eval_dot_index_chain_helper(
state, lib, &mut val, &x.1, idx_values, is_idx, x.2, level, new_val,
state, lib, &mut val, expr, idx_values, is_idx, *pos, level, new_val,
)
}
// xxx.idx_lhs[idx_expr] | xxx.dot_lhs.rhs
// xxx.prop[expr] | xxx.prop.expr
Expr::Index(x) | Expr::Dot(x) => {
let (prop, expr, pos) = x.as_ref();
let is_idx = matches!(rhs, Expr::Index(_));
let args = &mut [obj, &mut Default::default()];
let (mut val, updated) = if let Expr::Property(p) = &x.0 {
let (mut val, updated) = if let Expr::Property(p) = prop {
let ((_, getter, _), _) = p.as_ref();
let args = &mut args[..1];
self.exec_fn_call(state, lib, getter, true, 0, args, is_ref, None, x.2, 0)?
self.exec_fn_call(state, lib, getter, true, 0, args, is_ref, None, *pos, 0)?
} else {
// Syntax error
return Err(Box::new(EvalAltResult::ErrorDotExpr(
"".into(),
rhs.position(),
)));
unreachable!();
};
let val = &mut val;
let target = &mut val.into();
let (result, may_be_changed) = self.eval_dot_index_chain_helper(
state, lib, target, &x.1, idx_values, is_idx, x.2, level, new_val,
state, lib, target, expr, idx_values, is_idx, *pos, level, new_val,
)?;
// Feed the value back via a setter just in case it has been updated
if updated || may_be_changed {
if let Expr::Property(p) = &x.0 {
if let Expr::Property(p) = prop {
let ((_, _, setter), _) = p.as_ref();
// Re-use args because the first &mut parameter will not be consumed
args[1] = val;
self.exec_fn_call(
state, lib, setter, true, 0, args, is_ref, None, x.2, 0,
state, lib, setter, true, 0, args, is_ref, None, *pos, 0,
)
.or_else(|err| match *err {
// If there is no setter, no need to feed it back because the property is read-only
@@ -1127,13 +1106,16 @@ impl Engine {
scope: &mut Scope,
state: &mut State,
lib: &FunctionsLib,
dot_lhs: &Expr,
dot_rhs: &Expr,
is_index: bool,
op_pos: Position,
expr: &Expr,
level: usize,
new_val: Option<Dynamic>,
) -> Result<Dynamic, Box<EvalAltResult>> {
let ((dot_lhs, dot_rhs, op_pos), is_index) = match expr {
Expr::Index(x) => (x.as_ref(), true),
Expr::Dot(x) => (x.as_ref(), false),
_ => unreachable!(),
};
let idx_values = &mut StaticVec::new();
self.eval_indexed_chain(scope, state, lib, dot_rhs, idx_values, 0, level)?;
@@ -1158,7 +1140,7 @@ impl Engine {
let this_ptr = &mut target.into();
self.eval_dot_index_chain_helper(
state, lib, this_ptr, dot_rhs, idx_values, is_index, op_pos, level, new_val,
state, lib, this_ptr, dot_rhs, idx_values, is_index, *op_pos, level, new_val,
)
.map(|(v, _)| v)
}
@@ -1173,7 +1155,7 @@ impl Engine {
let val = self.eval_expr(scope, state, lib, expr, level)?;
let this_ptr = &mut val.into();
self.eval_dot_index_chain_helper(
state, lib, this_ptr, dot_rhs, idx_values, is_index, op_pos, level, new_val,
state, lib, this_ptr, dot_rhs, idx_values, is_index, *op_pos, level, new_val,
)
.map(|(v, _)| v)
}
@@ -1489,14 +1471,14 @@ impl Engine {
Expr::Variable(_) => unreachable!(),
// idx_lhs[idx_expr] op= rhs
#[cfg(not(feature = "no_index"))]
Expr::Index(x) => self.eval_dot_index_chain(
scope, state, lib, &x.0, &x.1, true, x.2, level, new_val,
),
Expr::Index(_) => {
self.eval_dot_index_chain(scope, state, lib, lhs_expr, level, new_val)
}
// dot_lhs.dot_rhs op= rhs
#[cfg(not(feature = "no_object"))]
Expr::Dot(x) => self.eval_dot_index_chain(
scope, state, lib, &x.0, &x.1, false, *op_pos, level, new_val,
),
Expr::Dot(_) => {
self.eval_dot_index_chain(scope, state, lib, lhs_expr, level, new_val)
}
// Error assignment to constant
expr if expr.is_constant() => {
Err(Box::new(EvalAltResult::ErrorAssignmentToConstant(
@@ -1513,15 +1495,11 @@ impl Engine {
// lhs[idx_expr]
#[cfg(not(feature = "no_index"))]
Expr::Index(x) => {
self.eval_dot_index_chain(scope, state, lib, &x.0, &x.1, true, x.2, level, None)
}
Expr::Index(_) => self.eval_dot_index_chain(scope, state, lib, expr, level, None),
// lhs.dot_rhs
#[cfg(not(feature = "no_object"))]
Expr::Dot(x) => {
self.eval_dot_index_chain(scope, state, lib, &x.0, &x.1, false, x.2, level, None)
}
Expr::Dot(_) => self.eval_dot_index_chain(scope, state, lib, expr, level, None),
#[cfg(not(feature = "no_index"))]
Expr::Array(x) => Ok(Dynamic(Union::Array(Box::new(
@@ -1621,7 +1599,6 @@ impl Engine {
}
// Module-qualified function call
#[cfg(not(feature = "no_module"))]
Expr::FnCall(x) if x.1.is_some() => {
let ((name, _, pos), modules, hash_fn_def, args_expr, def_val) = x.as_ref();
let modules = modules.as_ref().unwrap();
@@ -1642,7 +1619,7 @@ impl Engine {
.downcast_mut::<Module>()
.unwrap()
} else {
scope.find_module(id).ok_or_else(|| {
scope.find_module_internal(id).ok_or_else(|| {
Box::new(EvalAltResult::ErrorModuleNotFound(id.into(), *root_pos))
})?
};
@@ -1931,21 +1908,18 @@ impl Engine {
// Import statement
Stmt::Import(x) => {
#[cfg(feature = "no_module")]
unreachable!();
let (expr, (name, pos)) = x.as_ref();
#[cfg(not(feature = "no_module"))]
// Guard against too many modules
if state.modules >= self.max_modules {
return Err(Box::new(EvalAltResult::ErrorTooManyModules(*pos)));
}
if let Some(path) = self
.eval_expr(scope, state, lib, &expr, level)?
.try_cast::<ImmutableString>()
{
let (expr, (name, pos)) = x.as_ref();
// Guard against too many modules
if state.modules >= self.max_modules {
return Err(Box::new(EvalAltResult::ErrorTooManyModules(*pos)));
}
if let Some(path) = self
.eval_expr(scope, state, lib, &expr, level)?
.try_cast::<ImmutableString>()
#[cfg(not(feature = "no_module"))]
{
if let Some(resolver) = &self.module_resolver {
// Use an empty scope to create a module
@@ -1953,7 +1927,7 @@ impl Engine {
resolver.resolve(self, Scope::new(), &path, expr.position())?;
let mod_name = unsafe_cast_var_name_to_lifetime(name, &state);
scope.push_module(mod_name, module);
scope.push_module_internal(mod_name, module);
state.modules += 1;
@@ -1964,9 +1938,12 @@ impl Engine {
expr.position(),
)))
}
} else {
Err(Box::new(EvalAltResult::ErrorImportExpr(expr.position())))
}
#[cfg(feature = "no_module")]
Ok(Default::default())
} else {
Err(Box::new(EvalAltResult::ErrorImportExpr(expr.position())))
}
}

View File

@@ -3,7 +3,7 @@
use crate::any::{Dynamic, Variant};
use crate::calc_fn_hash;
use crate::engine::{make_getter, make_setter, Engine, FunctionsLib, FUNC_INDEXER};
use crate::fn_native::{CallableFunction, FnCallArgs, IteratorFn};
use crate::fn_native::{CallableFunction, FnCallArgs, IteratorFn, SendSync};
use crate::parser::{
FnAccess,
FnAccess::{Private, Public},
@@ -12,7 +12,7 @@ use crate::parser::{
use crate::result::EvalAltResult;
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
use crate::token::{Position, Token};
use crate::utils::StaticVec;
use crate::utils::{StaticVec, StraightHasherBuilder};
use crate::stdlib::{
any::TypeId,
@@ -44,10 +44,14 @@ pub struct Module {
variables: HashMap<String, Dynamic>,
/// Flattened collection of all module variables, including those in sub-modules.
all_variables: HashMap<u64, Dynamic>,
all_variables: HashMap<u64, Dynamic, StraightHasherBuilder>,
/// External Rust functions.
functions: HashMap<u64, (String, FnAccess, StaticVec<TypeId>, CallableFunction)>,
functions: HashMap<
u64,
(String, FnAccess, StaticVec<TypeId>, CallableFunction),
StraightHasherBuilder,
>,
/// Script-defined functions.
lib: FunctionsLib,
@@ -57,7 +61,7 @@ pub struct Module {
/// Flattened collection of all external Rust functions, native or scripted,
/// including those in sub-modules.
all_functions: HashMap<u64, CallableFunction>,
all_functions: HashMap<u64, CallableFunction, StraightHasherBuilder>,
}
impl fmt::Debug for Module {
@@ -101,7 +105,7 @@ impl Module {
/// ```
pub fn new_with_capacity(capacity: usize) -> Self {
Self {
functions: HashMap::with_capacity(capacity),
functions: HashMap::with_capacity_and_hasher(capacity, StraightHasherBuilder),
..Default::default()
}
}
@@ -941,23 +945,7 @@ impl ModuleRef {
}
/// Trait that encapsulates a module resolution service.
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "sync"))]
pub trait ModuleResolver {
/// Resolve a module based on a path string.
fn resolve(
&self,
engine: &Engine,
scope: Scope,
path: &str,
pos: Position,
) -> Result<Module, Box<EvalAltResult>>;
}
/// Trait that encapsulates a module resolution service.
#[cfg(not(feature = "no_module"))]
#[cfg(feature = "sync")]
pub trait ModuleResolver: Send + Sync {
pub trait ModuleResolver: SendSync {
/// Resolve a module based on a path string.
fn resolve(
&self,
@@ -975,6 +963,8 @@ pub mod resolvers {
pub use super::file::FileModuleResolver;
pub use super::stat::StaticModuleResolver;
}
#[cfg(feature = "no_module")]
pub mod resolvers {}
/// Script file-based module resolver.
#[cfg(not(feature = "no_module"))]

View File

@@ -37,6 +37,10 @@ impl OptimizationLevel {
pub fn is_none(self) -> bool {
self == Self::None
}
/// Is the `OptimizationLevel` Simple.
pub fn is_simple(self) -> bool {
self == Self::Simple
}
/// Is the `OptimizationLevel` Full.
pub fn is_full(self) -> bool {
self == Self::Full
@@ -381,10 +385,10 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
// ( stmt )
stmt => Expr::Stmt(Box::new((stmt, x.1))),
},
// id = expr
// id op= expr
Expr::Assignment(x) => match x.2 {
//id = id2 op= expr2
Expr::Assignment(x2) if x.1 == "=" => match (x.0, x2.0) {
//id = id2 op= rhs
Expr::Assignment(x2) if x.1.is_empty() => match (x.0, &x2.0) {
// var = var op= expr2 -> var op= expr2
(Expr::Variable(a), Expr::Variable(b))
if a.1.is_none() && b.1.is_none() && a.0 == b.0 && a.3 == b.3 =>
@@ -393,14 +397,10 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr {
state.set_dirty();
Expr::Assignment(Box::new((Expr::Variable(a), x2.1, optimize_expr(x2.2, state), x.3)))
}
// id1 = id2 op= expr2
(id1, id2) => {
Expr::Assignment(Box::new((
id1, x.1, Expr::Assignment(Box::new((id2, x2.1, optimize_expr(x2.2, state), x2.3))), x.3,
)))
}
// expr1 = expr2 op= rhs
(expr1, _) => Expr::Assignment(Box::new((expr1, x.1, optimize_expr(Expr::Assignment(x2), state), x.3))),
},
// id op= expr
// expr = rhs
expr => Expr::Assignment(Box::new((x.0, x.1, optimize_expr(expr, state), x.3))),
},

View File

@@ -4,17 +4,11 @@ use crate::any::{Dynamic, Union};
use crate::calc_fn_hash;
use crate::engine::{make_getter, make_setter, Engine, FunctionsLib};
use crate::error::{LexError, ParseError, ParseErrorType};
use crate::module::ModuleRef;
use crate::optimize::{optimize_into_ast, OptimizationLevel};
use crate::scope::{EntryType as ScopeEntryType, Scope};
use crate::token::{Position, Token, TokenIterator};
use crate::utils::StaticVec;
#[cfg(not(feature = "no_module"))]
use crate::module::ModuleRef;
#[cfg(feature = "no_module")]
#[derive(Debug, Eq, PartialEq, Clone, Hash, Copy, Default)]
pub struct ModuleRef;
use crate::utils::{StaticVec, StraightHasherBuilder};
use crate::stdlib::{
borrow::Cow,
@@ -644,7 +638,6 @@ impl Expr {
Self::Variable(_) => match token {
Token::LeftBracket | Token::LeftParen => true,
#[cfg(not(feature = "no_module"))]
Token::DoubleColon => true,
_ => false,
},
@@ -761,27 +754,21 @@ fn parse_call_expr<'a>(
Token::RightParen => {
eat_token(input, Token::RightParen);
#[cfg(not(feature = "no_module"))]
let hash_fn_def = {
if let Some(modules) = modules.as_mut() {
modules.set_index(state.find_module(&modules.get(0).0));
let hash_fn_def = if let Some(modules) = modules.as_mut() {
modules.set_index(state.find_module(&modules.get(0).0));
// Rust functions are indexed in two steps:
// 1) Calculate a hash in a similar manner to script-defined functions,
// i.e. qualifiers + function name + number of arguments.
// 2) Calculate a second hash with no qualifiers, empty function name,
// zero number of arguments, and the actual list of argument `TypeId`'s.
// 3) The final hash is the XOR of the two hashes.
let qualifiers = modules.iter().map(|(m, _)| m.as_str());
calc_fn_hash(qualifiers, &id, 0, empty())
} else {
// Qualifiers (none) + function name + no parameters.
calc_fn_hash(empty(), &id, 0, empty())
}
// Rust functions are indexed in two steps:
// 1) Calculate a hash in a similar manner to script-defined functions,
// i.e. qualifiers + function name + number of arguments.
// 2) Calculate a second hash with no qualifiers, empty function name,
// zero number of arguments, and the actual list of argument `TypeId`'s.
// 3) The final hash is the XOR of the two hashes.
let qualifiers = modules.iter().map(|(m, _)| m.as_str());
calc_fn_hash(qualifiers, &id, 0, empty())
} else {
// Qualifiers (none) + function name + no parameters.
calc_fn_hash(empty(), &id, 0, empty())
};
// Qualifiers (none) + function name + no parameters.
#[cfg(feature = "no_module")]
let hash_fn_def = calc_fn_hash(empty(), &id, 0, empty());
return Ok(Expr::FnCall(Box::new((
(id.into(), false, begin),
@@ -803,27 +790,21 @@ fn parse_call_expr<'a>(
(Token::RightParen, _) => {
eat_token(input, Token::RightParen);
#[cfg(not(feature = "no_module"))]
let hash_fn_def = {
if let Some(modules) = modules.as_mut() {
modules.set_index(state.find_module(&modules.get(0).0));
let hash_fn_def = if let Some(modules) = modules.as_mut() {
modules.set_index(state.find_module(&modules.get(0).0));
// Rust functions are indexed in two steps:
// 1) Calculate a hash in a similar manner to script-defined functions,
// i.e. qualifiers + function name + number of arguments.
// 2) Calculate a second hash with no qualifiers, empty function name,
// zero number of arguments, and the actual list of argument `TypeId`'s.
// 3) The final hash is the XOR of the two hashes.
let qualifiers = modules.iter().map(|(m, _)| m.as_str());
calc_fn_hash(qualifiers, &id, args.len(), empty())
} else {
// Qualifiers (none) + function name + number of arguments.
calc_fn_hash(empty(), &id, args.len(), empty())
}
// Rust functions are indexed in two steps:
// 1) Calculate a hash in a similar manner to script-defined functions,
// i.e. qualifiers + function name + number of arguments.
// 2) Calculate a second hash with no qualifiers, empty function name,
// zero number of arguments, and the actual list of argument `TypeId`'s.
// 3) The final hash is the XOR of the two hashes.
let qualifiers = modules.iter().map(|(m, _)| m.as_str());
calc_fn_hash(qualifiers, &id, args.len(), empty())
} else {
// Qualifiers (none) + function name + number of arguments.
calc_fn_hash(empty(), &id, args.len(), empty())
};
// Qualifiers (none) + function name + number of arguments.
#[cfg(feature = "no_module")]
let hash_fn_def = calc_fn_hash(empty(), &id, args.len(), empty());
return Ok(Expr::FnCall(Box::new((
(id.into(), false, begin),
@@ -1265,7 +1246,6 @@ fn parse_primary<'a>(
}
(Expr::Property(_), _) => unreachable!(),
// module access
#[cfg(not(feature = "no_module"))]
(Expr::Variable(x), Token::DoubleColon) => match input.next().unwrap() {
(Token::Identifier(id2), pos2) => {
let ((name, pos), mut modules, _, index) = *x;
@@ -1293,7 +1273,6 @@ fn parse_primary<'a>(
match &mut root_expr {
// Cache the hash key for module-qualified variables
#[cfg(not(feature = "no_module"))]
Expr::Variable(x) if x.1.is_some() => {
let ((name, _), modules, hash, _) = x.as_mut();
let modules = modules.as_mut().unwrap();
@@ -1496,22 +1475,21 @@ fn parse_op_assignment_stmt<'a>(
}
/// Make a dot expression.
fn make_dot_expr(
lhs: Expr,
rhs: Expr,
op_pos: Position,
is_index: bool,
) -> Result<Expr, ParseError> {
fn make_dot_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result<Expr, ParseError> {
Ok(match (lhs, rhs) {
// idx_lhs[idx_rhs].rhs
// idx_lhs[idx_expr].rhs
// Attach dot chain to the bottom level of indexing chain
(Expr::Index(x), rhs) => {
Expr::Index(Box::new((x.0, make_dot_expr(x.1, rhs, op_pos, true)?, x.2)))
let (idx_lhs, idx_expr, pos) = *x;
Expr::Index(Box::new((
idx_lhs,
make_dot_expr(idx_expr, rhs, op_pos)?,
pos,
)))
}
// lhs.id
(lhs, Expr::Variable(x)) if x.1.is_none() => {
let (name, pos) = x.0;
let lhs = if is_index { lhs.into_property() } else { lhs };
let getter = make_getter(&name);
let setter = make_setter(&name);
@@ -1519,46 +1497,34 @@ fn make_dot_expr(
Expr::Dot(Box::new((lhs, rhs, op_pos)))
}
(lhs, Expr::Property(x)) => {
let lhs = if is_index { lhs.into_property() } else { lhs };
let rhs = Expr::Property(x);
Expr::Dot(Box::new((lhs, rhs, op_pos)))
}
// lhs.module::id - syntax error
(_, Expr::Variable(x)) if x.1.is_some() => {
#[cfg(feature = "no_module")]
unreachable!();
#[cfg(not(feature = "no_module"))]
return Err(PERR::PropertyExpected.into_err(x.1.unwrap().get(0).1));
}
// lhs.prop
(lhs, prop @ Expr::Property(_)) => Expr::Dot(Box::new((lhs, prop, op_pos))),
// lhs.dot_lhs.dot_rhs
(lhs, Expr::Dot(x)) => {
let (dot_lhs, dot_rhs, pos) = *x;
Expr::Dot(Box::new((
lhs,
Expr::Dot(Box::new((
dot_lhs.into_property(),
dot_rhs.into_property(),
pos,
))),
Expr::Dot(Box::new((dot_lhs.into_property(), dot_rhs, pos))),
op_pos,
)))
}
// lhs.idx_lhs[idx_rhs]
(lhs, Expr::Index(x)) => {
let (idx_lhs, idx_rhs, pos) = *x;
let (dot_lhs, dot_rhs, pos) = *x;
Expr::Dot(Box::new((
lhs,
Expr::Index(Box::new((
idx_lhs.into_property(),
idx_rhs.into_property(),
pos,
))),
Expr::Index(Box::new((dot_lhs.into_property(), dot_rhs, pos))),
op_pos,
)))
}
// lhs.func()
(lhs, func @ Expr::FnCall(_)) => Expr::Dot(Box::new((lhs, func, op_pos))),
// lhs.rhs
(lhs, rhs) => Expr::Dot(Box::new((lhs, rhs.into_property(), op_pos))),
_ => unreachable!(),
})
}
@@ -1822,7 +1788,7 @@ fn parse_binary_op<'a>(
_ => (),
}
make_dot_expr(current_lhs, rhs, pos, false)?
make_dot_expr(current_lhs, rhs, pos)?
}
token => return Err(PERR::UnknownOperator(token.into()).into_err(pos)),
@@ -2123,6 +2089,7 @@ fn parse_import<'a>(
}
/// Parse an export statement.
#[cfg(not(feature = "no_module"))]
fn parse_export<'a>(
input: &mut Peekable<TokenIterator<'a>>,
state: &mut ParseState,
@@ -2304,7 +2271,9 @@ fn parse_stmt<'a>(
Token::LeftBrace => parse_block(input, state, breakable, level + 1, allow_stmt_expr),
// fn ...
#[cfg(not(feature = "no_function"))]
Token::Fn if !is_global => Err(PERR::WrongFnDefinition.into_err(*pos)),
#[cfg(not(feature = "no_function"))]
Token::Fn => unreachable!(),
Token::If => parse_if(input, state, breakable, level + 1, allow_stmt_expr),
@@ -2353,8 +2322,6 @@ fn parse_stmt<'a>(
Token::Let => parse_let(input, state, Normal, level + 1, allow_stmt_expr),
Token::Const => parse_let(input, state, Constant, level + 1, allow_stmt_expr),
#[cfg(not(feature = "no_module"))]
Token::Import => parse_import(input, state, level + 1, allow_stmt_expr),
#[cfg(not(feature = "no_module"))]
@@ -2368,6 +2335,7 @@ fn parse_stmt<'a>(
}
/// Parse a function definition.
#[cfg(not(feature = "no_function"))]
fn parse_fn<'a>(
input: &mut Peekable<TokenIterator<'a>>,
state: &mut ParseState,
@@ -2493,24 +2461,23 @@ pub fn parse_global_expr<'a>(
fn parse_global_level<'a>(
input: &mut Peekable<TokenIterator<'a>>,
max_expr_depth: (usize, usize),
) -> Result<(Vec<Stmt>, HashMap<u64, FnDef>), ParseError> {
) -> Result<(Vec<Stmt>, Vec<FnDef>), ParseError> {
let mut statements = Vec::<Stmt>::new();
let mut functions = HashMap::<u64, FnDef>::new();
let mut functions = HashMap::<u64, FnDef, _>::with_hasher(StraightHasherBuilder);
let mut state = ParseState::new(max_expr_depth.0);
while !input.peek().unwrap().0.is_eof() {
// Collect all the function definitions
#[cfg(not(feature = "no_function"))]
{
let mut access = FnAccess::Public;
let mut must_be_fn = false;
if match_token(input, Token::Private)? {
access = FnAccess::Private;
must_be_fn = true;
}
let (access, must_be_fn) = if match_token(input, Token::Private)? {
(FnAccess::Private, true)
} else {
(FnAccess::Public, false)
};
match input.peek().unwrap() {
#[cfg(not(feature = "no_function"))]
(Token::Fn, _) => {
let mut state = ParseState::new(max_expr_depth.1);
let func = parse_fn(input, &mut state, access, 0, true)?;
@@ -2565,7 +2532,7 @@ fn parse_global_level<'a>(
}
}
Ok((statements, functions))
Ok((statements, functions.into_iter().map(|(_, v)| v).collect()))
}
/// Run the parser on an input stream, returning an AST.
@@ -2576,9 +2543,8 @@ pub fn parse<'a>(
optimization_level: OptimizationLevel,
max_expr_depth: (usize, usize),
) -> Result<AST, ParseError> {
let (statements, functions) = parse_global_level(input, max_expr_depth)?;
let (statements, lib) = parse_global_level(input, max_expr_depth)?;
let lib = functions.into_iter().map(|(_, v)| v).collect();
Ok(
// Optimize AST
optimize_into_ast(engine, scope, statements, lib, optimization_level),

View File

@@ -1,12 +1,10 @@
//! Module that defines the `Scope` type representing a function call-stack scope.
use crate::any::{Dynamic, Union, Variant};
use crate::module::Module;
use crate::parser::{map_dynamic_to_expr, Expr};
use crate::token::Position;
#[cfg(not(feature = "no_module"))]
use crate::module::Module;
use crate::stdlib::{borrow::Cow, boxed::Box, iter, string::String, vec::Vec};
/// Type of an entry in the Scope.
@@ -178,6 +176,17 @@ impl<'a> Scope<'a> {
/// Modules are used for accessing member variables, functions and plugins under a namespace.
#[cfg(not(feature = "no_module"))]
pub fn push_module<K: Into<Cow<'a, str>>>(&mut self, name: K, mut value: Module) {
self.push_module_internal(name, value);
}
/// Add (push) a new module to the Scope.
///
/// Modules are used for accessing member variables, functions and plugins under a namespace.
pub(crate) fn push_module_internal<K: Into<Cow<'a, str>>>(
&mut self,
name: K,
mut value: Module,
) {
value.index_all_sub_modules();
self.push_dynamic_value(
@@ -350,6 +359,11 @@ impl<'a> Scope<'a> {
/// Find a module in the Scope, starting from the last entry.
#[cfg(not(feature = "no_module"))]
pub fn find_module(&mut self, name: &str) -> Option<&mut Module> {
self.find_module_internal(name)
}
/// Find a module in the Scope, starting from the last entry.
pub(crate) fn find_module_internal(&mut self, name: &str) -> Option<&mut Module> {
let index = self.get_module_index(name)?;
self.get_mut(index).0.downcast_mut::<Module>()
}

View File

@@ -181,6 +181,7 @@ pub enum Token {
XOr,
Ampersand,
And,
#[cfg(not(feature = "no_function"))]
Fn,
Continue,
Break,
@@ -199,6 +200,7 @@ pub enum Token {
PowerOfAssign,
Private,
Import,
#[cfg(not(feature = "no_module"))]
Export,
As,
LexError(Box<LexError>),
@@ -260,6 +262,7 @@ impl Token {
Or => "||",
Ampersand => "&",
And => "&&",
#[cfg(not(feature = "no_function"))]
Fn => "fn",
Continue => "continue",
Break => "break",
@@ -283,6 +286,7 @@ impl Token {
PowerOfAssign => "~=",
Private => "private",
Import => "import",
#[cfg(not(feature = "no_module"))]
Export => "export",
As => "as",
EOF => "{EOF}",
@@ -754,12 +758,9 @@ impl<'a> TokenIterator<'a> {
"for" => Token::For,
"in" => Token::In,
"private" => Token::Private,
#[cfg(not(feature = "no_module"))]
"import" => Token::Import,
#[cfg(not(feature = "no_module"))]
"export" => Token::Export,
#[cfg(not(feature = "no_module"))]
"as" => Token::As,
#[cfg(not(feature = "no_function"))]
@@ -916,7 +917,6 @@ impl<'a> TokenIterator<'a> {
}
('=', _) => return Some((Token::Equals, pos)),
#[cfg(not(feature = "no_module"))]
(':', ':') => {
self.eat_next();
return Some((Token::DoubleColon, pos));

View File

@@ -11,7 +11,7 @@ use crate::stdlib::{
borrow::Borrow,
boxed::Box,
fmt,
hash::{Hash, Hasher},
hash::{BuildHasher, Hash, Hasher},
iter::FromIterator,
mem,
mem::MaybeUninit,
@@ -27,6 +27,48 @@ use crate::stdlib::collections::hash_map::DefaultHasher;
#[cfg(feature = "no_std")]
use ahash::AHasher;
/// A hasher that only takes one single `u64` and returns it as a hash key.
///
/// # Panics
///
/// Panics when hashing any data type other than a `u64`.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
pub struct StraightHasher(u64);
impl Hasher for StraightHasher {
#[inline(always)]
fn finish(&self) -> u64 {
self.0
}
#[inline]
fn write(&mut self, bytes: &[u8]) {
let mut key = [0_u8; 8];
key.copy_from_slice(&bytes[..8]); // Panics if fewer than 8 bytes
self.0 = u64::from_le_bytes(key);
}
}
impl StraightHasher {
/// Create a `StraightHasher`.
#[inline(always)]
pub fn new() -> Self {
Self(0)
}
}
/// A hash builder for `StraightHasher`.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
pub struct StraightHasherBuilder;
impl BuildHasher for StraightHasherBuilder {
type Hasher = StraightHasher;
#[inline(always)]
fn build_hasher(&self) -> Self::Hasher {
StraightHasher::new()
}
}
/// Calculate a `u64` hash key from a module-qualified function name and parameter types.
///
/// Module names are passed in via `&str` references from an iterator.
@@ -108,6 +150,7 @@ pub struct StaticVec<T> {
const MAX_STATIC_VEC: usize = 4;
impl<T> Drop for StaticVec<T> {
#[inline(always)]
fn drop(&mut self) {
self.clear();
}
@@ -174,6 +217,7 @@ impl<T> FromIterator<T> for StaticVec<T> {
impl<T> StaticVec<T> {
/// Create a new `StaticVec`.
#[inline(always)]
pub fn new() -> Self {
Default::default()
}
@@ -189,6 +233,7 @@ impl<T> StaticVec<T> {
self.len = 0;
}
/// Extract a `MaybeUninit` into a concrete initialized type.
#[inline(always)]
fn extract(value: MaybeUninit<T>) -> T {
unsafe { value.assume_init() }
}
@@ -250,6 +295,7 @@ impl<T> StaticVec<T> {
);
}
/// Is data stored in fixed-size storage?
#[inline(always)]
fn is_fixed_storage(&self) -> bool {
self.len <= MAX_STATIC_VEC
}
@@ -359,10 +405,12 @@ impl<T> StaticVec<T> {
result
}
/// Get the number of items in this `StaticVec`.
#[inline(always)]
pub fn len(&self) -> usize {
self.len
}
/// Is this `StaticVec` empty?
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.len == 0
}
@@ -605,41 +653,48 @@ pub struct ImmutableString(Shared<String>);
impl Deref for ImmutableString {
type Target = String;
#[inline(always)]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl AsRef<String> for ImmutableString {
#[inline(always)]
fn as_ref(&self) -> &String {
&self.0
}
}
impl Borrow<str> for ImmutableString {
#[inline(always)]
fn borrow(&self) -> &str {
self.0.as_str()
}
}
impl From<&str> for ImmutableString {
#[inline(always)]
fn from(value: &str) -> Self {
Self(value.to_string().into())
}
}
impl From<String> for ImmutableString {
#[inline(always)]
fn from(value: String) -> Self {
Self(value.into())
}
}
impl From<Box<String>> for ImmutableString {
#[inline(always)]
fn from(value: Box<String>) -> Self {
Self(value.into())
}
}
impl From<ImmutableString> for String {
#[inline(always)]
fn from(value: ImmutableString) -> Self {
value.into_owned()
}
@@ -648,42 +703,49 @@ impl From<ImmutableString> for String {
impl FromStr for ImmutableString {
type Err = ();
#[inline(always)]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(s.to_string().into()))
}
}
impl FromIterator<char> for ImmutableString {
#[inline(always)]
fn from_iter<T: IntoIterator<Item = char>>(iter: T) -> Self {
Self(iter.into_iter().collect::<String>().into())
}
}
impl<'a> FromIterator<&'a char> for ImmutableString {
#[inline(always)]
fn from_iter<T: IntoIterator<Item = &'a char>>(iter: T) -> Self {
Self(iter.into_iter().cloned().collect::<String>().into())
}
}
impl<'a> FromIterator<&'a str> for ImmutableString {
#[inline(always)]
fn from_iter<T: IntoIterator<Item = &'a str>>(iter: T) -> Self {
Self(iter.into_iter().collect::<String>().into())
}
}
impl<'a> FromIterator<String> for ImmutableString {
#[inline(always)]
fn from_iter<T: IntoIterator<Item = String>>(iter: T) -> Self {
Self(iter.into_iter().collect::<String>().into())
}
}
impl fmt::Display for ImmutableString {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self.0.as_str(), f)
}
}
impl fmt::Debug for ImmutableString {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(self.0.as_str(), f)
}
@@ -818,6 +880,7 @@ impl Add<char> for &ImmutableString {
}
impl AddAssign<char> for ImmutableString {
#[inline(always)]
fn add_assign(&mut self, rhs: char) {
self.make_mut().push(rhs);
}
@@ -832,6 +895,7 @@ impl ImmutableString {
}
/// Make sure that the `ImmutableString` is unique (i.e. no other outstanding references).
/// Then return a mutable reference to the `String`.
#[inline(always)]
pub fn make_mut(&mut self) -> &mut String {
shared_make_mut(&mut self.0)
}