From 62928f86139b692efbfe0cd4b45f2c3d9529d20d Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 8 Mar 2021 15:30:32 +0800 Subject: [PATCH] Revise function hashing. --- src/ast.rs | 100 +++++++++++++++++++---- src/engine.rs | 147 +++++++++++++++------------------ src/fn_call.rs | 198 +++++++++++++++++++++++---------------------- src/fn_native.rs | 17 +++- src/fn_register.rs | 22 +---- src/lib.rs | 8 +- src/module/mod.rs | 178 +++++++++++++++++++--------------------- src/optimize.rs | 16 ++-- src/parser.rs | 180 +++++++++++++++++++++++------------------ src/utils.rs | 94 ++++++++------------- tests/bit_shift.rs | 2 +- tests/string.rs | 8 +- 12 files changed, 501 insertions(+), 469 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index 46789cbd..e586caeb 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -8,7 +8,7 @@ use crate::stdlib::{ boxed::Box, fmt, hash::Hash, - num::{NonZeroU64, NonZeroUsize}, + num::NonZeroUsize, ops::{Add, AddAssign}, string::String, vec, @@ -836,7 +836,7 @@ pub enum Stmt { /// \[`export`\] `const` id `=` expr Const(Box, Option, bool, Position), /// expr op`=` expr - Assignment(Box<(Expr, Cow<'static, str>, Expr)>, Position), + Assignment(Box<(Expr, Expr, Option)>, Position), /// `{` stmt`;` ... `}` Block(Vec, Position), /// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}` @@ -1036,7 +1036,7 @@ impl Stmt { } Self::Assignment(x, _) => { x.0.walk(path, on_node); - x.2.walk(path, on_node); + x.1.walk(path, on_node); } Self::Block(x, _) => x.iter().for_each(|s| s.walk(path, on_node)), Self::TryCatch(x, _, _) => { @@ -1083,6 +1083,79 @@ pub struct BinaryExpr { pub rhs: Expr, } +/// _(INTERNALS)_ An op-assignment operator. +/// Exported under the `internals` feature only. +/// +/// # Volatile Data Structure +/// +/// This type is volatile and may change. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct OpAssignment { + pub hash_op_assign: u64, + pub hash_op: u64, + pub op: Cow<'static, str>, +} + +/// _(INTERNALS)_ An set of function call hashes. +/// Exported under the `internals` feature only. +/// +/// # Volatile Data Structure +/// +/// This type is volatile and may change. +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Default)] +pub struct FnHash { + /// Pre-calculated hash for a script-defined function ([`None`] if native functions only). + script: Option, + /// Pre-calculated hash for a native Rust function with no parameter types. + native: u64, +} + +impl FnHash { + /// Create a [`FnHash`] with only the native Rust hash. + #[inline(always)] + pub fn from_native(hash: u64) -> Self { + Self { + script: None, + native: hash, + } + } + /// Create a [`FnHash`] with both native Rust and script function hashes set to the same value. + #[inline(always)] + pub fn from_script(hash: u64) -> Self { + Self { + script: Some(hash), + native: hash, + } + } + /// Create a [`FnHash`] with both native Rust and script function hashes. + #[inline(always)] + pub fn from_script_and_native(script: u64, native: u64) -> Self { + Self { + script: Some(script), + native, + } + } + /// Is this [`FnHash`] native Rust only? + #[inline(always)] + pub fn is_native_only(&self) -> bool { + self.script.is_none() + } + /// Get the script function hash from this [`FnHash`]. + /// + /// # Panics + /// + /// Panics if the [`FnHash`] is native Rust only. + #[inline(always)] + pub fn script_hash(&self) -> u64 { + self.script.unwrap() + } + /// Get the naive Rust function hash from this [`FnHash`]. + #[inline(always)] + pub fn native_hash(&self) -> u64 { + self.native + } +} + /// _(INTERNALS)_ A function call. /// Exported under the `internals` feature only. /// @@ -1091,9 +1164,8 @@ pub struct BinaryExpr { /// This type is volatile and may change. #[derive(Debug, Clone, Default, Hash)] pub struct FnCallExpr { - /// Pre-calculated hash for a script-defined function of the same name and number of parameters. - /// None if native Rust only. - pub hash_script: Option, + /// Pre-calculated hash. + pub hash: FnHash, /// Does this function call capture the parent scope? pub capture: bool, /// Namespace of the function, if any. Boxed because it occurs rarely. @@ -1225,15 +1297,9 @@ pub enum Expr { /// () Unit(Position), /// Variable access - (optional index, optional (hash, modules), variable name) - Variable( - Box<( - Option, - Option<(NonZeroU64, NamespaceRef)>, - Ident, - )>, - ), - /// Property access - (getter, setter), prop - Property(Box<(ImmutableString, ImmutableString, Ident)>), + Variable(Box<(Option, Option<(u64, NamespaceRef)>, Ident)>), + /// Property access - (getter, hash, setter, hash, prop) + Property(Box<(ImmutableString, u64, ImmutableString, u64, Ident)>), /// { [statement][Stmt] } Stmt(Box>, Position), /// func `(` expr `,` ... `)` @@ -1326,7 +1392,7 @@ impl Expr { Self::FnPointer(_, pos) => *pos, Self::Array(_, pos) => *pos, Self::Map(_, pos) => *pos, - Self::Property(x) => (x.2).pos, + Self::Property(x) => (x.4).pos, Self::Stmt(_, pos) => *pos, Self::Variable(x) => (x.2).pos, Self::FnCall(_, pos) => *pos, @@ -1355,7 +1421,7 @@ impl Expr { Self::Array(_, pos) => *pos = new_pos, Self::Map(_, pos) => *pos = new_pos, Self::Variable(x) => (x.2).pos = new_pos, - Self::Property(x) => (x.2).pos = new_pos, + Self::Property(x) => (x.4).pos = new_pos, Self::Stmt(_, pos) => *pos = new_pos, Self::FnCall(_, pos) => *pos = new_pos, Self::And(_, pos) | Self::Or(_, pos) | Self::In(_, pos) => *pos = new_pos, diff --git a/src/engine.rs b/src/engine.rs index d8ffd7bb..30f6d1a4 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,6 +1,6 @@ //! Main module defining the script evaluation [`Engine`]. -use crate::ast::{Expr, FnCallExpr, Ident, ReturnType, Stmt}; +use crate::ast::{Expr, FnCallExpr, FnHash, Ident, OpAssignment, ReturnType, Stmt}; use crate::dynamic::{map_std_type_name, AccessMode, Union, Variant}; use crate::fn_native::{ CallableFunction, IteratorFn, OnDebugCallback, OnPrintCallback, OnProgressCallback, @@ -25,8 +25,8 @@ use crate::stdlib::{ use crate::syntax::CustomSyntax; use crate::utils::{get_hasher, StraightHasherBuilder}; use crate::{ - calc_native_fn_hash, Dynamic, EvalAltResult, FnPtr, ImmutableString, Module, Position, - RhaiResult, Scope, Shared, StaticVec, + calc_fn_hash, Dynamic, EvalAltResult, FnPtr, ImmutableString, Module, Position, RhaiResult, + Scope, Shared, StaticVec, }; #[cfg(not(feature = "no_index"))] @@ -126,15 +126,12 @@ impl Imports { /// Does the specified function hash key exist in this stack of imported [modules][Module]? #[allow(dead_code)] #[inline(always)] - pub fn contains_fn(&self, hash: NonZeroU64) -> bool { + pub fn contains_fn(&self, hash: u64) -> bool { self.0.iter().any(|(_, m)| m.contains_qualified_fn(hash)) } /// Get specified function via its hash key. #[inline(always)] - pub fn get_fn( - &self, - hash: NonZeroU64, - ) -> Option<(&CallableFunction, Option<&ImmutableString>)> { + pub fn get_fn(&self, hash: u64) -> Option<(&CallableFunction, Option<&ImmutableString>)> { self.0 .iter() .rev() @@ -510,11 +507,7 @@ pub struct State { pub resolver: Option>, /// Functions resolution cache. fn_resolution_caches: StaticVec< - HashMap< - NonZeroU64, - Option<(CallableFunction, Option)>, - StraightHasherBuilder, - >, + HashMap)>, StraightHasherBuilder>, >, } @@ -527,11 +520,8 @@ impl State { /// Get a mutable reference to the current functions resolution cache. pub fn fn_resolution_cache_mut( &mut self, - ) -> &mut HashMap< - NonZeroU64, - Option<(CallableFunction, Option)>, - StraightHasherBuilder, - > { + ) -> &mut HashMap)>, StraightHasherBuilder> + { if self.fn_resolution_caches.is_empty() { self.fn_resolution_caches .push(HashMap::with_capacity_and_hasher(16, StraightHasherBuilder)); @@ -1053,7 +1043,7 @@ impl Engine { idx_values: &mut StaticVec, chain_type: ChainType, level: usize, - new_val: Option<((Dynamic, Position), (&str, Position))>, + new_val: Option<((Dynamic, Position), (&Option, Position))>, ) -> Result<(Dynamic, bool), Box> { assert!(chain_type != ChainType::NonChaining); @@ -1102,9 +1092,9 @@ impl Engine { ) { // Indexed value is a reference - update directly Ok(obj_ptr) => { - let ((new_val, new_pos), (op, op_pos)) = new_val.unwrap(); + let ((new_val, new_pos), (op_info, op_pos)) = new_val.unwrap(); self.eval_op_assignment( - mods, state, lib, op, op_pos, obj_ptr, new_val, new_pos, + mods, state, lib, op_info, op_pos, obj_ptr, new_val, new_pos, )?; None } @@ -1122,11 +1112,13 @@ impl Engine { let val_type_name = target_val.type_name(); let ((_, val_pos), _) = new_val; + let hash_set = + FnHash::from_native(calc_fn_hash(empty(), FN_IDX_SET, 3)); let args = &mut [target_val, &mut idx_val2, &mut (new_val.0).0]; self.exec_fn_call( - mods, state, lib, FN_IDX_SET, None, args, is_ref, true, val_pos, - None, level, + mods, state, lib, FN_IDX_SET, hash_set, args, is_ref, true, + val_pos, None, level, ) .map_err(|err| match *err { EvalAltResult::ErrorFunctionNotFound(fn_sig, _) @@ -1159,11 +1151,7 @@ impl Engine { match rhs { // xxx.fn_name(arg_expr_list) Expr::FnCall(x, pos) if x.namespace.is_none() && new_val.is_none() => { - let FnCallExpr { - name, - hash_script: hash, - .. - } = x.as_ref(); + let FnCallExpr { name, hash, .. } = x.as_ref(); let args = idx_val.as_fn_call_args(); self.make_method_call( mods, state, lib, name, *hash, target, args, *pos, level, @@ -1179,20 +1167,20 @@ impl Engine { } // {xxx:map}.id op= ??? Expr::Property(x) if target_val.is::() && new_val.is_some() => { - let Ident { name, pos } = &x.2; + let Ident { name, pos } = &x.4; let index = name.clone().into(); let val = self.get_indexed_mut( mods, state, lib, target_val, index, *pos, true, is_ref, false, level, )?; - let ((new_val, new_pos), (op, op_pos)) = new_val.unwrap(); + let ((new_val, new_pos), (op_info, op_pos)) = new_val.unwrap(); self.eval_op_assignment( - mods, state, lib, op, op_pos, val, new_val, new_pos, + mods, state, lib, op_info, op_pos, val, new_val, new_pos, )?; Ok((Dynamic::UNIT, true)) } // {xxx:map}.id Expr::Property(x) if target_val.is::() => { - let Ident { name, pos } = &x.2; + let Ident { name, pos } = &x.4; let index = name.clone().into(); let val = self.get_indexed_mut( mods, state, lib, target_val, index, *pos, false, is_ref, false, level, @@ -1202,21 +1190,23 @@ impl Engine { } // xxx.id = ??? Expr::Property(x) if new_val.is_some() => { - let (_, setter, Ident { pos, .. }) = x.as_ref(); + let (_, _, setter, hash_set, Ident { pos, .. }) = x.as_ref(); + let hash = FnHash::from_native(*hash_set); let mut new_val = new_val; let mut args = [target_val, &mut (new_val.as_mut().unwrap().0).0]; self.exec_fn_call( - mods, state, lib, setter, None, &mut args, is_ref, true, *pos, None, + mods, state, lib, setter, hash, &mut args, is_ref, true, *pos, None, level, ) .map(|(v, _)| (v, true)) } // xxx.id Expr::Property(x) => { - let (getter, _, Ident { pos, .. }) = x.as_ref(); + let (getter, hash_get, _, _, Ident { pos, .. }) = x.as_ref(); + let hash = FnHash::from_native(*hash_get); let mut args = [target_val]; self.exec_fn_call( - mods, state, lib, getter, None, &mut args, is_ref, true, *pos, None, + mods, state, lib, getter, hash, &mut args, is_ref, true, *pos, None, level, ) .map(|(v, _)| (v, false)) @@ -1225,7 +1215,7 @@ impl Engine { Expr::Index(x, x_pos) | Expr::Dot(x, x_pos) if target_val.is::() => { let mut val = match &x.lhs { Expr::Property(p) => { - let Ident { name, pos } = &p.2; + let Ident { name, pos } = &p.4; let index = name.clone().into(); self.get_indexed_mut( mods, state, lib, target_val, index, *pos, false, is_ref, true, @@ -1234,11 +1224,7 @@ impl Engine { } // {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr Expr::FnCall(x, pos) if x.namespace.is_none() => { - let FnCallExpr { - name, - hash_script: hash, - .. - } = x.as_ref(); + let FnCallExpr { name, hash, .. } = x.as_ref(); let args = idx_val.as_fn_call_args(); let (val, _) = self.make_method_call( mods, state, lib, name, *hash, target, args, *pos, level, @@ -1264,12 +1250,15 @@ impl Engine { match &x.lhs { // xxx.prop[expr] | xxx.prop.expr Expr::Property(p) => { - let (getter, setter, Ident { pos, .. }) = p.as_ref(); + let (getter, hash_get, setter, hash_set, Ident { pos, .. }) = + p.as_ref(); + let hash_get = FnHash::from_native(*hash_get); + let hash_set = FnHash::from_native(*hash_set); let arg_values = &mut [target_val, &mut Default::default()]; let args = &mut arg_values[..1]; let (mut val, updated) = self.exec_fn_call( - mods, state, lib, getter, None, args, is_ref, true, *pos, None, - level, + mods, state, lib, getter, hash_get, args, is_ref, true, *pos, + None, level, )?; let val = &mut val; @@ -1294,8 +1283,8 @@ impl Engine { // Re-use args because the first &mut parameter will not be consumed arg_values[1] = val; self.exec_fn_call( - mods, state, lib, setter, None, arg_values, is_ref, true, - *pos, None, level, + mods, state, lib, setter, hash_set, arg_values, is_ref, + true, *pos, None, level, ) .or_else( |err| match *err { @@ -1313,11 +1302,7 @@ impl Engine { } // xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr Expr::FnCall(f, pos) if f.namespace.is_none() => { - let FnCallExpr { - name, - hash_script: hash, - .. - } = f.as_ref(); + let FnCallExpr { name, hash, .. } = f.as_ref(); let args = idx_val.as_fn_call_args(); let (mut val, _) = self.make_method_call( mods, state, lib, name, *hash, target, args, *pos, level, @@ -1359,7 +1344,7 @@ impl Engine { this_ptr: &mut Option<&mut Dynamic>, expr: &Expr, level: usize, - new_val: Option<((Dynamic, Position), (&str, Position))>, + new_val: Option<((Dynamic, Position), (&Option, Position))>, ) -> RhaiResult { let (crate::ast::BinaryExpr { lhs, rhs }, chain_type, op_pos) = match expr { Expr::Index(x, pos) => (x.as_ref(), ChainType::Index, *pos), @@ -1593,8 +1578,9 @@ impl Engine { let type_name = target.type_name(); let mut idx = idx; let args = &mut [target, &mut idx]; + let hash_get = FnHash::from_native(calc_fn_hash(empty(), FN_IDX_GET, 2)); self.exec_fn_call( - _mods, state, _lib, FN_IDX_GET, None, args, _is_ref, true, idx_pos, None, + _mods, state, _lib, FN_IDX_GET, hash_get, args, _is_ref, true, idx_pos, None, _level, ) .map(|(v, _)| v.into()) @@ -1640,17 +1626,13 @@ impl Engine { #[cfg(not(feature = "no_index"))] Dynamic(Union::Array(mut rhs_value, _)) => { // Call the `==` operator to compare each value + let hash = calc_fn_hash(empty(), OP_EQUALS, 2); for value in rhs_value.iter_mut() { let args = &mut [&mut lhs_value.clone(), value]; - let hash_fn = - calc_native_fn_hash(empty(), OP_EQUALS, args.iter().map(|a| a.type_id())) - .unwrap(); let pos = rhs.position(); if self - .call_native_fn( - mods, state, lib, OP_EQUALS, hash_fn, args, false, false, pos, - )? + .call_native_fn(mods, state, lib, OP_EQUALS, hash, args, false, false, pos)? .0 .as_bool() .unwrap_or(false) @@ -1764,7 +1746,7 @@ impl Engine { let FnCallExpr { name, capture: cap_scope, - hash_script: hash, + hash, args, .. } = x.as_ref(); @@ -1778,12 +1760,12 @@ impl Engine { let FnCallExpr { name, namespace, - hash_script, + hash, args, .. } = x.as_ref(); let namespace = namespace.as_ref(); - let hash = hash_script.unwrap(); + let hash = hash.native_hash(); self.make_qualified_function_call( scope, mods, state, lib, this_ptr, namespace, name, args, hash, *pos, level, ) @@ -1927,7 +1909,7 @@ impl Engine { mods: &mut Imports, state: &mut State, lib: &[&Module], - op: &str, + op_info: &Option, op_pos: Position, mut target: Target, mut new_value: Dynamic, @@ -1937,11 +1919,12 @@ impl Engine { unreachable!("LHS should not be read-only"); } - if op.is_empty() { - // Normal assignment - target.set_value(new_value, new_value_pos)?; - Ok(()) - } else { + if let Some(OpAssignment { + hash_op_assign, + hash_op, + op, + }) = op_info + { let mut lock_guard; let lhs_ptr_inner; @@ -1952,28 +1935,30 @@ impl Engine { lhs_ptr_inner = target.as_mut(); } + let hash = *hash_op_assign; let args = &mut [lhs_ptr_inner, &mut new_value]; - let hash_fn = - calc_native_fn_hash(empty(), op, args.iter().map(|a| a.type_id())).unwrap(); - match self.call_native_fn(mods, state, lib, op, hash_fn, args, true, true, op_pos) { + match self.call_native_fn(mods, state, lib, op, hash, args, true, true, op_pos) { Ok(_) => (), - Err(err) if matches!(err.as_ref(), EvalAltResult::ErrorFunctionNotFound(f, _) if f.starts_with(op)) => + Err(err) if matches!(err.as_ref(), EvalAltResult::ErrorFunctionNotFound(f, _) if f.starts_with(op.as_ref())) => { // Expand to `var = var op rhs` let op = &op[..op.len() - 1]; // extract operator without = - let hash_fn = - calc_native_fn_hash(empty(), op, args.iter().map(|a| a.type_id())).unwrap(); // Run function - let (value, _) = self - .call_native_fn(mods, state, lib, op, hash_fn, args, true, false, op_pos)?; + let (value, _) = self.call_native_fn( + mods, state, lib, op, *hash_op, args, true, false, op_pos, + )?; *args[0] = value.flatten(); } err => return err.map(|_| ()), } + Ok(()) + } else { + // Normal assignment + target.set_value(new_value, new_value_pos)?; Ok(()) } } @@ -2007,7 +1992,7 @@ impl Engine { // var op= rhs Stmt::Assignment(x, op_pos) if x.0.get_variable_access(false).is_some() => { - let (lhs_expr, op, rhs_expr) = x.as_ref(); + let (lhs_expr, rhs_expr, op_info) = x.as_ref(); let rhs_val = self .eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)? .flatten(); @@ -2035,7 +2020,7 @@ impl Engine { mods, state, lib, - op, + op_info, *op_pos, lhs_ptr, rhs_val, @@ -2047,11 +2032,11 @@ impl Engine { // lhs op= rhs Stmt::Assignment(x, op_pos) => { - let (lhs_expr, op, rhs_expr) = x.as_ref(); + let (lhs_expr, rhs_expr, op_info) = x.as_ref(); let rhs_val = self .eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)? .flatten(); - let _new_val = Some(((rhs_val, rhs_expr.position()), (op.as_ref(), *op_pos))); + let _new_val = Some(((rhs_val, rhs_expr.position()), (op_info, *op_pos))); // Must be either `var[index] op= val` or `var.prop op= val` match lhs_expr { diff --git a/src/fn_call.rs b/src/fn_call.rs index 35c7a768..b0d43420 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -1,5 +1,6 @@ //! Implement function-calling mechanism for [`Engine`]. +use crate::ast::FnHash; use crate::engine::{ Imports, State, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_TYPE_OF, @@ -16,18 +17,16 @@ use crate::stdlib::{ format, iter::{empty, once}, mem, - num::NonZeroU64, string::{String, ToString}, vec::Vec, }; -use crate::utils::combine_hashes; use crate::{ ast::{Expr, Stmt}, fn_native::CallableFunction, RhaiResult, }; use crate::{ - calc_native_fn_hash, calc_script_fn_hash, Dynamic, Engine, EvalAltResult, FnPtr, + calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, EvalAltResult, FnPtr, ImmutableString, Module, ParseErrorType, Position, Scope, StaticVec, }; @@ -183,20 +182,27 @@ impl Engine { state: &'s mut State, lib: &[&Module], fn_name: &str, - mut hash: NonZeroU64, - args: &mut FnCallArgs, + hash_script: u64, + args: Option<&mut FnCallArgs>, allow_dynamic: bool, is_op_assignment: bool, ) -> &'s Option<(CallableFunction, Option)> { + let mut hash = if let Some(ref args) = args { + let hash_params = calc_fn_params_hash(args.iter().map(|a| a.type_id())); + combine_hashes(hash_script, hash_params) + } else { + hash_script + }; + &*state .fn_resolution_cache_mut() .entry(hash) .or_insert_with(|| { - let num_args = args.len(); + let num_args = args.as_ref().map(|a| a.len()).unwrap_or(0); let max_bitmask = if !allow_dynamic { 0 } else { - 1usize << args.len().min(MAX_DYNAMIC_PARAMETERS) + 1usize << num_args.min(MAX_DYNAMIC_PARAMETERS) }; let mut bitmask = 1usize; // Bitmask of which parameter to replace with `Dynamic` @@ -238,39 +244,41 @@ impl Engine { None if bitmask >= max_bitmask => { return if num_args != 2 { None - } else if !is_op_assignment { - if let Some(f) = - get_builtin_binary_op_fn(fn_name, &args[0], &args[1]) - { - Some(( - CallableFunction::from_method(Box::new(f) as Box), - None, - )) + } else if let Some(ref args) = args { + if !is_op_assignment { + if let Some(f) = + get_builtin_binary_op_fn(fn_name, &args[0], &args[1]) + { + Some(( + CallableFunction::from_method(Box::new(f) as Box), + None, + )) + } else { + None + } } else { - None + let (first, second) = args.split_first().unwrap(); + + if let Some(f) = + get_builtin_op_assignment_fn(fn_name, *first, second[0]) + { + Some(( + CallableFunction::from_method(Box::new(f) as Box), + None, + )) + } else { + None + } } } else { - let (first, second) = args.split_first().unwrap(); - - if let Some(f) = - get_builtin_op_assignment_fn(fn_name, *first, second[0]) - { - Some(( - CallableFunction::from_method(Box::new(f) as Box), - None, - )) - } else { - None - } + None } } // Try all permutations with `Dynamic` wildcards None => { - hash = calc_native_fn_hash( - empty(), - fn_name, - args.iter().enumerate().map(|(i, a)| { + let hash_params = calc_fn_params_hash( + args.as_ref().unwrap().iter().enumerate().map(|(i, a)| { let mask = 1usize << (num_args - i - 1); if bitmask & mask != 0 { // Replace with `Dynamic` @@ -279,8 +287,8 @@ impl Engine { a.type_id() } }), - ) - .unwrap(); + ); + hash = combine_hashes(hash_script, hash_params); bitmask += 1; } @@ -302,7 +310,7 @@ impl Engine { state: &mut State, lib: &[&Module], fn_name: &str, - hash_fn: NonZeroU64, + hash_native: u64, args: &mut FnCallArgs, is_ref: bool, is_op_assignment: bool, @@ -318,8 +326,8 @@ impl Engine { state, lib, fn_name, - hash_fn, - args, + hash_native, + Some(args), true, is_op_assignment, ); @@ -569,9 +577,9 @@ impl Engine { result } - // Has a system function an override? + // Has a system function a Rust-native override? #[inline(always)] - pub(crate) fn has_override_by_name_and_arguments( + pub(crate) fn has_native_override( &self, mods: Option<&Imports>, state: &mut State, @@ -579,15 +587,11 @@ impl Engine { fn_name: &str, arg_types: &[TypeId], ) -> bool { - let arg_types = arg_types.as_ref(); + let hash_script = calc_fn_hash(empty(), fn_name, arg_types.len()); + let hash_params = calc_fn_params_hash(arg_types.iter().cloned()); + let hash_fn = combine_hashes(hash_script, hash_params); - self.has_override( - mods, - state, - lib, - calc_native_fn_hash(empty(), fn_name, arg_types.iter().cloned()), - calc_script_fn_hash(empty(), fn_name, arg_types.len()), - ) + self.has_override(mods, state, lib, Some(hash_fn), None) } // Has a system function an override? @@ -597,8 +601,8 @@ impl Engine { mods: Option<&Imports>, state: &mut State, lib: &[&Module], - hash_fn: Option, - hash_script: Option, + hash_fn: Option, + hash_script: Option, ) -> bool { let cache = state.fn_resolution_cache_mut(); @@ -649,7 +653,7 @@ impl Engine { state: &mut State, lib: &[&Module], fn_name: &str, - _hash_script: Option, + hash: FnHash, args: &mut FnCallArgs, is_ref: bool, _is_method: bool, @@ -684,9 +688,8 @@ impl Engine { if num_params < 0 { Dynamic::FALSE } else { - let hash_script = - calc_script_fn_hash(empty(), &fn_name, num_params as usize); - self.has_override(Some(mods), state, lib, None, hash_script) + let hash_script = calc_fn_hash(empty(), &fn_name, num_params as usize); + self.has_override(Some(mods), state, lib, None, Some(hash_script)) .into() }, false, @@ -731,9 +734,16 @@ impl Engine { _ => (), } + // Scripted function call? + let hash_script = if hash.is_native_only() { + None + } else { + Some(hash.script_hash()) + }; + #[cfg(not(feature = "no_function"))] - if let Some((func, source)) = _hash_script.and_then(|hash| { - self.resolve_function(mods, state, lib, fn_name, hash, args, false, false) + if let Some((func, source)) = hash_script.and_then(|hash| { + self.resolve_function(mods, state, lib, fn_name, hash, None, false, false) .as_ref() .map(|(f, s)| (f.clone(), s.clone())) }) { @@ -815,9 +825,17 @@ impl Engine { } // Native function call - let hash_fn = - calc_native_fn_hash(empty(), fn_name, args.iter().map(|a| a.type_id())).unwrap(); - self.call_native_fn(mods, state, lib, fn_name, hash_fn, args, is_ref, false, pos) + self.call_native_fn( + mods, + state, + lib, + fn_name, + hash.native_hash(), + args, + is_ref, + false, + pos, + ) } /// Evaluate a list of statements with no `this` pointer. @@ -894,7 +912,7 @@ impl Engine { state: &mut State, lib: &[&Module], fn_name: &str, - hash_script: Option, + mut hash: FnHash, target: &mut crate::engine::Target, mut call_args: StaticVec, pos: Position, @@ -913,9 +931,8 @@ impl Engine { // Redirect function name let fn_name = fn_ptr.fn_name(); let args_len = call_args.len() + fn_ptr.curry().len(); - // Recalculate hash - let hash = - hash_script.and_then(|_| calc_script_fn_hash(empty(), fn_name, args_len)); + // Recalculate hashes + let hash = FnHash::from_script(calc_fn_hash(empty(), fn_name, args_len)); // Arguments are passed as-is, adding the curried arguments let mut curry = fn_ptr.curry().iter().cloned().collect::>(); let mut arg_values = curry @@ -936,8 +953,10 @@ impl Engine { let fn_name = fn_ptr.fn_name(); let args_len = call_args.len() + fn_ptr.curry().len(); // Recalculate hash - let hash = - hash_script.and_then(|_| calc_script_fn_hash(empty(), fn_name, args_len)); + let hash = FnHash::from_script_and_native( + calc_fn_hash(empty(), fn_name, args_len), + calc_fn_hash(empty(), fn_name, args_len + 1), + ); // Replace the first argument with the object pointer, adding the curried arguments let mut curry = fn_ptr.curry().iter().cloned().collect::>(); let mut arg_values = once(obj) @@ -977,7 +996,6 @@ impl Engine { _ => { let _redirected; - let mut hash = hash_script; // Check if it is a map method call in OOP style #[cfg(not(feature = "no_object"))] @@ -995,17 +1013,14 @@ impl Engine { .enumerate() .for_each(|(i, v)| call_args.insert(i, v)); // Recalculate the hash based on the new function name and new arguments - hash = hash_script.and_then(|_| { - calc_script_fn_hash(empty(), fn_name, call_args.len()) - }); + hash = FnHash::from_script_and_native( + calc_fn_hash(empty(), fn_name, call_args.len()), + calc_fn_hash(empty(), fn_name, call_args.len() + 1), + ); } } }; - if hash_script.is_none() { - hash = None; - } - // Attached object pointer in front of the arguments let mut arg_values = once(obj) .chain(call_args.iter_mut()) @@ -1036,7 +1051,7 @@ impl Engine { this_ptr: &mut Option<&mut Dynamic>, fn_name: &str, args_expr: &[Expr], - mut hash_script: Option, + mut hash: FnHash, pos: Position, capture_scope: bool, level: usize, @@ -1074,7 +1089,11 @@ impl Engine { // Recalculate hash let args_len = args_expr.len() + curry.len(); - hash_script = calc_script_fn_hash(empty(), name, args_len); + hash = if !hash.is_native_only() { + FnHash::from_script(calc_fn_hash(empty(), name, args_len)) + } else { + FnHash::from_native(calc_fn_hash(empty(), name, args_len)) + }; } // Handle Fn() @@ -1145,8 +1164,8 @@ impl Engine { return Ok(if num_params < 0 { Dynamic::FALSE } else { - let hash_script = calc_script_fn_hash(empty(), &fn_name, num_params as usize); - self.has_override(Some(mods), state, lib, None, hash_script) + let hash_script = calc_fn_hash(empty(), &fn_name, num_params as usize); + self.has_override(Some(mods), state, lib, None, Some(hash_script)) .into() }); } @@ -1266,17 +1285,7 @@ impl Engine { let args = args.as_mut(); self.exec_fn_call( - mods, - state, - lib, - name, - hash_script, - args, - is_ref, - false, - pos, - capture, - level, + mods, state, lib, name, hash, args, is_ref, false, pos, capture, level, ) .map(|(v, _)| v) } @@ -1292,7 +1301,7 @@ impl Engine { namespace: Option<&NamespaceRef>, fn_name: &str, args_expr: &[Expr], - hash_script: NonZeroU64, + hash: u64, pos: Position, level: usize, ) -> RhaiResult { @@ -1357,20 +1366,13 @@ impl Engine { })?; // First search in script-defined functions (can override built-in) - let func = match module.get_qualified_fn(hash_script) { + let func = match module.get_qualified_fn(hash) { // Then search in Rust functions None => { self.inc_operations(state, pos)?; - // Namespace-qualified 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, - // and the actual list of argument `TypeId`'.s - let hash_fn_args = - calc_native_fn_hash(empty(), "", args.iter().map(|a| a.type_id())).unwrap(); - // 3) The two hashes are combined. - let hash_qualified_fn = combine_hashes(hash_script, hash_fn_args); + let hash_params = calc_fn_params_hash(args.iter().map(|a| a.type_id())); + let hash_qualified_fn = combine_hashes(hash, hash_params); module.get_qualified_fn(hash_qualified_fn) } diff --git a/src/fn_native.rs b/src/fn_native.rs index d7c299dd..ace60b56 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -1,6 +1,6 @@ //! Module defining interfaces to native-Rust functions. -use crate::ast::FnAccess; +use crate::ast::{FnAccess, FnHash}; use crate::engine::Imports; use crate::plugin::PluginFunction; use crate::stdlib::{ @@ -14,8 +14,8 @@ use crate::stdlib::{ }; use crate::token::is_valid_identifier; use crate::{ - calc_script_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, ImmutableString, Module, - Position, RhaiResult, + calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, ImmutableString, Module, Position, + RhaiResult, }; /// Trait that maps to `Send + Sync` only under the `sync` feature. @@ -189,13 +189,22 @@ impl<'a> NativeCallContext<'a> { is_method: bool, args: &mut [&mut Dynamic], ) -> RhaiResult { + let hash = if is_method { + FnHash::from_script_and_native( + calc_fn_hash(empty(), fn_name, args.len() - 1), + calc_fn_hash(empty(), fn_name, args.len()), + ) + } else { + FnHash::from_script(calc_fn_hash(empty(), fn_name, args.len())) + }; + self.engine() .exec_fn_call( &mut self.mods.cloned().unwrap_or_default(), &mut Default::default(), self.lib, fn_name, - calc_script_fn_hash(empty(), fn_name, args.len() - if is_method { 1 } else { 0 }), + hash, args, is_method, is_method, diff --git a/src/fn_register.rs b/src/fn_register.rs index ea7dc9b0..38f2d272 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -6,9 +6,7 @@ use crate::dynamic::{DynamicWriteLock, Variant}; use crate::fn_native::{CallableFunction, FnAny, FnCallArgs, SendSync}; use crate::r#unsafe::unsafe_cast_box; use crate::stdlib::{any::TypeId, boxed::Box, mem, string::String}; -use crate::{ - Dynamic, Engine, FnAccess, FnNamespace, ImmutableString, NativeCallContext, RhaiResult, -}; +use crate::{Dynamic, Engine, FnAccess, FnNamespace, NativeCallContext, RhaiResult}; /// Trait to register custom functions with the [`Engine`]. pub trait RegisterFn { @@ -154,20 +152,6 @@ pub fn map_result(data: RhaiResult) -> RhaiResult { data } -/// Remap `&str` | `String` to `ImmutableString`. -#[inline(always)] -fn map_type_id() -> TypeId { - let ref_id = TypeId::of::(); - - if ref_id == TypeId::of::<&str>() { - TypeId::of::() - } else if ref_id == TypeId::of::() { - TypeId::of::() - } else { - TypeId::of::() - } -} - macro_rules! def_register { () => { def_register!(imp from_pure :); @@ -188,7 +172,7 @@ macro_rules! def_register { #[inline(always)] fn register_fn(&mut self, name: &str, f: FN) -> &mut Self { self.global_namespace.set_fn(name, FnNamespace::Global, FnAccess::Public, None, - &[$(map_type_id::<$param, $par>()),*], + &[$(TypeId::of::<$par>()),*], CallableFunction::$abi(make_func!(f : map_dynamic ; $($par => $let => $clone => $arg),*)) ); self @@ -203,7 +187,7 @@ macro_rules! def_register { #[inline(always)] fn register_result_fn(&mut self, name: &str, f: FN) -> &mut Self { self.global_namespace.set_fn(name, FnNamespace::Global, FnAccess::Public, None, - &[$(map_type_id::<$param, $par>()),*], + &[$(TypeId::of::<$par>()),*], CallableFunction::$abi(make_func!(f : map_result ; $($par => $let => $clone => $arg),*)) ); self diff --git a/src/lib.rs b/src/lib.rs index 01d02910..1ed55ca3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -142,10 +142,10 @@ pub use fn_native::Shared; use fn_native::Locked; #[cfg(feature = "internals")] -pub use utils::{calc_native_fn_hash, calc_script_fn_hash, HashableHashMap}; +pub use utils::{calc_fn_hash, calc_fn_params_hash, combine_hashes, HashableHashMap}; #[cfg(not(feature = "internals"))] -pub(crate) use utils::{calc_native_fn_hash, calc_script_fn_hash}; +pub(crate) use utils::{calc_fn_hash, calc_fn_params_hash, combine_hashes}; pub use rhai_codegen::*; @@ -195,8 +195,8 @@ pub use token::{get_next_token, parse_string_literal, InputStream, Token, Tokeni #[cfg(feature = "internals")] #[deprecated = "this type is volatile and may change"] pub use ast::{ - ASTNode, BinaryExpr, CustomExpr, Expr, FloatWrapper, FnCallExpr, Ident, ReturnType, - ScriptFnDef, Stmt, + ASTNode, BinaryExpr, CustomExpr, Expr, FloatWrapper, FnCallExpr, FnHash, Ident, OpAssignment, + ReturnType, ScriptFnDef, Stmt, }; #[cfg(feature = "internals")] diff --git a/src/module/mod.rs b/src/module/mod.rs index ed9f2dab..c79e2396 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -10,16 +10,16 @@ use crate::stdlib::{ collections::HashMap, fmt, format, iter::empty, - num::NonZeroU64, num::NonZeroUsize, ops::{Add, AddAssign, Deref, DerefMut}, string::{String, ToString}, vec::Vec, }; use crate::token::Token; -use crate::utils::{combine_hashes, StraightHasherBuilder}; +use crate::utils::StraightHasherBuilder; use crate::{ - Dynamic, EvalAltResult, ImmutableString, NativeCallContext, Position, Shared, StaticVec, + calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, EvalAltResult, ImmutableString, + NativeCallContext, Position, Shared, StaticVec, }; #[cfg(not(feature = "no_index"))] @@ -101,6 +101,27 @@ impl FuncInfo { } } +/// _(INTERNALS)_ Calculate a [`u64`] hash key from a namespace-qualified function name and +/// parameter types. +/// Exported under the `internals` feature only. +/// +/// Module names are passed in via `&str` references from an iterator. +/// Parameter types are passed in via [`TypeId`] values from an iterator. +/// +/// # Note +/// +/// The first module name is skipped. Hashing starts from the _second_ module in the chain. +#[inline(always)] +fn calc_native_fn_hash<'a>( + modules: impl Iterator, + fn_name: &str, + params: &[TypeId], +) -> u64 { + let hash_script = calc_fn_hash(modules, fn_name, params.len()); + let hash_params = calc_fn_params_hash(params.iter().cloned()); + combine_hashes(hash_script, hash_params) +} + /// A module which may contain variables, sub-modules, external Rust functions, /// and/or script-defined functions. #[derive(Clone)] @@ -112,12 +133,12 @@ pub struct Module { /// [`Module`] variables. variables: HashMap, /// Flattened collection of all [`Module`] variables, including those in sub-modules. - all_variables: HashMap, + all_variables: HashMap, /// External Rust functions. - functions: HashMap, + functions: HashMap, /// Flattened collection of all external Rust functions, native or scripted. /// including those in sub-modules. - all_functions: HashMap, + all_functions: HashMap, /// Iterator functions, keyed by the type producing the iterator. type_iterators: HashMap, /// Flattened collection of iterator functions, including those in sub-modules. @@ -434,19 +455,13 @@ impl Module { ) -> &mut Self { self.variables.insert(name.into(), Dynamic::from(value)); self.indexed = false; - self.contains_indexed_global_functions = false; self } /// Get a reference to a namespace-qualified variable. /// Name and Position in [`EvalAltResult`] are [`None`] and [`NONE`][Position::NONE] and must be set afterwards. - /// - /// The [`NonZeroU64`] hash is calculated by the function [`calc_native_fn_hash`][crate::calc_native_fn_hash]. #[inline(always)] - pub(crate) fn get_qualified_var( - &self, - hash_var: NonZeroU64, - ) -> Result<&Dynamic, Box> { + pub(crate) fn get_qualified_var(&self, hash_var: u64) -> Result<&Dynamic, Box> { self.all_variables.get(&hash_var).ok_or_else(|| { EvalAltResult::ErrorVariableNotFound(String::new(), Position::NONE).into() }) @@ -460,12 +475,12 @@ impl Module { pub(crate) fn set_script_fn( &mut self, fn_def: impl Into>, - ) -> NonZeroU64 { + ) -> u64 { let fn_def = fn_def.into(); // None + function name + number of arguments. let num_params = fn_def.params.len(); - let hash_script = crate::calc_script_fn_hash(empty(), &fn_def.name, num_params).unwrap(); + let hash_script = crate::calc_fn_hash(empty(), &fn_def.name, num_params); let mut param_names: StaticVec<_> = fn_def.params.iter().cloned().collect(); param_names.push("Dynamic".into()); self.functions.insert( @@ -593,8 +608,7 @@ impl Module { /// Does the particular Rust function exist in the [`Module`]? /// - /// The [`NonZeroU64`] hash is calculated by the function [`calc_native_fn_hash`][crate::calc_native_fn_hash]. - /// It is also returned by the `set_fn_XXX` calls. + /// The [`u64`] hash is returned by the `set_fn_XXX` calls. /// /// # Example /// @@ -606,7 +620,7 @@ impl Module { /// assert!(module.contains_fn(hash, true)); /// ``` #[inline(always)] - pub fn contains_fn(&self, hash_fn: NonZeroU64, public_only: bool) -> bool { + pub fn contains_fn(&self, hash_fn: u64, public_only: bool) -> bool { if public_only { self.functions .get(&hash_fn) @@ -621,9 +635,7 @@ impl Module { /// Update the metadata (parameter names/types and return type) of a registered function. /// - /// The [`NonZeroU64`] hash is calculated either by the function - /// [`calc_native_fn_hash`][crate::calc_native_fn_hash] or the function - /// [`calc_script_fn_hash`][crate::calc_script_fn_hash]. + /// The [`u64`] hash is returned by the `set_fn_XXX` calls. /// /// ## Parameter Names and Types /// @@ -634,7 +646,7 @@ impl Module { /// The _last entry_ in the list should be the _return type_ of the function. /// In other words, the number of entries should be one larger than the number of parameters. #[inline(always)] - pub fn update_fn_metadata(&mut self, hash_fn: NonZeroU64, arg_names: &[&str]) -> &mut Self { + pub fn update_fn_metadata(&mut self, hash_fn: u64, arg_names: &[&str]) -> &mut Self { if let Some(f) = self.functions.get_mut(&hash_fn) { f.param_names = arg_names.iter().map(|&n| n.into()).collect(); } @@ -643,15 +655,9 @@ impl Module { /// Update the namespace of a registered function. /// - /// The [`NonZeroU64`] hash is calculated either by the function - /// [`calc_native_fn_hash`][crate::calc_native_fn_hash] or the function - /// [`calc_script_fn_hash`][crate::calc_script_fn_hash]. + /// The [`u64`] hash is returned by the `set_fn_XXX` calls. #[inline(always)] - pub fn update_fn_namespace( - &mut self, - hash_fn: NonZeroU64, - namespace: FnNamespace, - ) -> &mut Self { + pub fn update_fn_namespace(&mut self, hash_fn: u64, namespace: FnNamespace) -> &mut Self { if let Some(f) = self.functions.get_mut(&hash_fn) { f.namespace = namespace; } @@ -676,24 +682,34 @@ impl Module { arg_names: Option<&[&str]>, arg_types: &[TypeId], func: CallableFunction, - ) -> NonZeroU64 { + ) -> u64 { let name = name.into(); - - let hash_fn = - crate::calc_native_fn_hash(empty(), &name, arg_types.iter().cloned()).unwrap(); + let is_method = func.is_method(); let param_types = arg_types - .into_iter() + .iter() .cloned() - .map(|id| { - if id == TypeId::of::<&str>() || id == TypeId::of::() { - TypeId::of::() + .enumerate() + .map(|(i, type_id)| { + if !is_method || i > 0 { + if type_id == TypeId::of::<&str>() { + // Map &str to ImmutableString + TypeId::of::() + } else if type_id == TypeId::of::() { + // Map String to ImmutableString + TypeId::of::() + } else { + type_id + } } else { - id + // Do not map &mut parameter + type_id } }) .collect::>(); + let hash_fn = calc_native_fn_hash(empty(), &name, ¶m_types); + self.functions.insert( hash_fn, FuncInfo { @@ -793,7 +809,7 @@ impl Module { func: impl Fn(NativeCallContext, &mut FnCallArgs) -> Result> + SendSync + 'static, - ) -> NonZeroU64 { + ) -> u64 { let f = move |ctx: NativeCallContext, args: &mut FnCallArgs| func(ctx, args).map(Dynamic::from); @@ -829,7 +845,7 @@ impl Module { &mut self, name: impl Into, func: impl Fn() -> Result> + SendSync + 'static, - ) -> NonZeroU64 { + ) -> u64 { let f = move |_: NativeCallContext, _: &mut FnCallArgs| func().map(Dynamic::from); let arg_types = []; self.set_fn( @@ -864,7 +880,7 @@ impl Module { &mut self, name: impl Into, func: impl Fn(A) -> Result> + SendSync + 'static, - ) -> NonZeroU64 { + ) -> u64 { let f = move |_: NativeCallContext, args: &mut FnCallArgs| { func(cast_arg::(&mut args[0])).map(Dynamic::from) }; @@ -904,7 +920,7 @@ impl Module { name: impl Into, namespace: FnNamespace, func: impl Fn(&mut A) -> Result> + SendSync + 'static, - ) -> NonZeroU64 { + ) -> u64 { let f = move |_: NativeCallContext, args: &mut FnCallArgs| { func(&mut args[0].write_lock::().unwrap()).map(Dynamic::from) }; @@ -943,7 +959,7 @@ impl Module { &mut self, name: impl Into, func: impl Fn(&mut A) -> Result> + SendSync + 'static, - ) -> NonZeroU64 { + ) -> u64 { self.set_fn_1_mut( crate::engine::make_getter(&name.into()), FnNamespace::Global, @@ -975,7 +991,7 @@ impl Module { &mut self, name: impl Into, func: impl Fn(A, B) -> Result> + SendSync + 'static, - ) -> NonZeroU64 { + ) -> u64 { let f = move |_: NativeCallContext, args: &mut FnCallArgs| { let a = cast_arg::(&mut args[0]); let b = cast_arg::(&mut args[1]); @@ -1022,7 +1038,7 @@ impl Module { name: impl Into, namespace: FnNamespace, func: impl Fn(&mut A, B) -> Result> + SendSync + 'static, - ) -> NonZeroU64 { + ) -> u64 { let f = move |_: NativeCallContext, args: &mut FnCallArgs| { let b = cast_arg::(&mut args[1]); let a = &mut args[0].write_lock::().unwrap(); @@ -1068,7 +1084,7 @@ impl Module { &mut self, name: impl Into, func: impl Fn(&mut A, B) -> Result<(), Box> + SendSync + 'static, - ) -> NonZeroU64 { + ) -> u64 { self.set_fn_2_mut( crate::engine::make_setter(&name.into()), FnNamespace::Global, @@ -1107,7 +1123,7 @@ impl Module { pub fn set_indexer_get_fn( &mut self, func: impl Fn(&mut A, B) -> Result> + SendSync + 'static, - ) -> NonZeroU64 { + ) -> u64 { if TypeId::of::() == TypeId::of::() { panic!("Cannot register indexer for arrays."); } @@ -1154,7 +1170,7 @@ impl Module { &mut self, name: impl Into, func: impl Fn(A, B, C) -> Result> + SendSync + 'static, - ) -> NonZeroU64 { + ) -> u64 { let f = move |_: NativeCallContext, args: &mut FnCallArgs| { let a = cast_arg::(&mut args[0]); let b = cast_arg::(&mut args[1]); @@ -1207,7 +1223,7 @@ impl Module { name: impl Into, namespace: FnNamespace, func: impl Fn(&mut A, B, C) -> Result> + SendSync + 'static, - ) -> NonZeroU64 { + ) -> u64 { let f = move |_: NativeCallContext, args: &mut FnCallArgs| { let b = cast_arg::(&mut args[2]); let c = cast_arg::(&mut args[3]); @@ -1258,7 +1274,7 @@ impl Module { pub fn set_indexer_set_fn( &mut self, func: impl Fn(&mut A, B, C) -> Result<(), Box> + SendSync + 'static, - ) -> NonZeroU64 { + ) -> u64 { if TypeId::of::() == TypeId::of::() { panic!("Cannot register indexer for arrays."); } @@ -1330,7 +1346,7 @@ impl Module { &mut self, get_fn: impl Fn(&mut A, B) -> Result> + SendSync + 'static, set_fn: impl Fn(&mut A, B, T) -> Result<(), Box> + SendSync + 'static, - ) -> (NonZeroU64, NonZeroU64) { + ) -> (u64, u64) { ( self.set_indexer_get_fn(get_fn), self.set_indexer_set_fn(set_fn), @@ -1367,7 +1383,7 @@ impl Module { &mut self, name: impl Into, func: impl Fn(A, B, C, D) -> Result> + SendSync + 'static, - ) -> NonZeroU64 { + ) -> u64 { let f = move |_: NativeCallContext, args: &mut FnCallArgs| { let a = cast_arg::(&mut args[0]); let b = cast_arg::(&mut args[1]); @@ -1427,7 +1443,7 @@ impl Module { name: impl Into, namespace: FnNamespace, func: impl Fn(&mut A, B, C, D) -> Result> + SendSync + 'static, - ) -> NonZeroU64 { + ) -> u64 { let f = move |_: NativeCallContext, args: &mut FnCallArgs| { let b = cast_arg::(&mut args[1]); let c = cast_arg::(&mut args[2]); @@ -1454,14 +1470,9 @@ impl Module { /// Get a Rust function. /// - /// The [`NonZeroU64`] hash is calculated by the function [`calc_native_fn_hash`][crate::calc_native_fn_hash]. - /// It is also returned by the `set_fn_XXX` calls. + /// The [`u64`] hash is returned by the `set_fn_XXX` calls. #[inline(always)] - pub(crate) fn get_fn( - &self, - hash_fn: NonZeroU64, - public_only: bool, - ) -> Option<&CallableFunction> { + pub(crate) fn get_fn(&self, hash_fn: u64, public_only: bool) -> Option<&CallableFunction> { self.functions .get(&hash_fn) .and_then(|FuncInfo { access, func, .. }| match access { @@ -1473,24 +1484,17 @@ impl Module { /// Does the particular namespace-qualified function exist in the [`Module`]? /// - /// The [`NonZeroU64`] hash is calculated by the function - /// [`calc_native_fn_hash`][crate::calc_native_fn_hash] and must match - /// the hash calculated by [`build_index`][Module::build_index]. + /// The [`u64`] hash is calculated by [`build_index`][Module::build_index]. #[inline(always)] - pub fn contains_qualified_fn(&self, hash_fn: NonZeroU64) -> bool { + pub fn contains_qualified_fn(&self, hash_fn: u64) -> bool { self.all_functions.contains_key(&hash_fn) } /// Get a namespace-qualified function. /// - /// The [`NonZeroU64`] hash is calculated by the function - /// [`calc_native_fn_hash`][crate::calc_native_fn_hash] and must match - /// the hash calculated by [`build_index`][Module::build_index]. + /// The [`u64`] hash is calculated by [`build_index`][Module::build_index]. #[inline(always)] - pub(crate) fn get_qualified_fn( - &self, - hash_qualified_fn: NonZeroU64, - ) -> Option<&CallableFunction> { + pub(crate) fn get_qualified_fn(&self, hash_qualified_fn: u64) -> Option<&CallableFunction> { self.all_functions.get(&hash_qualified_fn) } @@ -1854,8 +1858,8 @@ impl Module { fn index_module<'a>( module: &'a Module, qualifiers: &mut Vec<&'a str>, - variables: &mut HashMap, - functions: &mut HashMap, + variables: &mut HashMap, + functions: &mut HashMap, type_iterators: &mut HashMap, ) -> bool { let mut contains_indexed_global_functions = false; @@ -1871,8 +1875,7 @@ impl Module { // Index all variables module.variables.iter().for_each(|(var_name, value)| { - let hash_var = - crate::calc_script_fn_hash(qualifiers.iter().map(|&v| v), var_name, 0).unwrap(); + let hash_var = crate::calc_fn_hash(qualifiers.iter().map(|&v| v), var_name, 0); variables.insert(hash_var, value.clone()); }); @@ -1909,26 +1912,13 @@ impl Module { FnAccess::Private => return, // Do not index private functions } - let hash_qualified_script = - crate::calc_script_fn_hash(qualifiers.iter().cloned(), name, *params) - .unwrap(); - if !func.is_script() { - assert_eq!(*params, param_types.len()); - - // Namespace-qualified 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, - // and the actual list of argument [`TypeId`]'.s - let hash_fn_args = - crate::calc_native_fn_hash(empty(), "", param_types.iter().cloned()) - .unwrap(); - // 3) The two hashes are combined. - let hash_qualified_fn = combine_hashes(hash_qualified_script, hash_fn_args); - + let hash_qualified_fn = + calc_native_fn_hash(qualifiers.iter().cloned(), name, param_types); functions.insert(hash_qualified_fn, func.clone()); } else if cfg!(not(feature = "no_function")) { + let hash_qualified_script = + crate::calc_fn_hash(qualifiers.iter().cloned(), name, *params); functions.insert(hash_qualified_script, func.clone()); } }, @@ -2016,7 +2006,7 @@ impl Module { /// _(INTERNALS)_ A chain of [module][Module] names to namespace-qualify a variable or function call. /// Exported under the `internals` feature only. /// -/// A [`NonZeroU64`] offset to the current [`Scope`][crate::Scope] is cached for quick search purposes. +/// A [`u64`] offset to the current [`Scope`][crate::Scope] is cached for quick search purposes. /// /// A [`StaticVec`] is used because most namespace-qualified access contains only one level, /// and it is wasteful to always allocate a [`Vec`] with one element. diff --git a/src/optimize.rs b/src/optimize.rs index 278f2588..653c05f0 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -15,8 +15,8 @@ use crate::stdlib::{ vec::Vec, }; use crate::token::is_valid_identifier; -use crate::utils::get_hasher; -use crate::{calc_native_fn_hash, Dynamic, Engine, Module, Position, Scope, StaticVec, AST}; +use crate::utils::{calc_fn_hash, get_hasher}; +use crate::{Dynamic, Engine, Module, Position, Scope, StaticVec, AST}; /// Level of optimization performed. #[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)] @@ -133,7 +133,7 @@ fn call_fn_with_constant_arguments( arg_values: &mut [Dynamic], ) -> Option { // Search built-in's and external functions - let hash_fn = calc_native_fn_hash(empty(), fn_name, arg_values.iter().map(|a| a.type_id())); + let hash_native = calc_fn_hash(empty(), fn_name, arg_values.len()); state .engine @@ -142,7 +142,7 @@ fn call_fn_with_constant_arguments( &mut Default::default(), state.lib, fn_name, - hash_fn.unwrap(), + hash_native, arg_values.iter_mut().collect::>().as_mut(), false, false, @@ -295,10 +295,10 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { match stmt { // expr op= expr Stmt::Assignment(x, _) => match x.0 { - Expr::Variable(_) => optimize_expr(&mut x.2, state), + Expr::Variable(_) => optimize_expr(&mut x.1, state), _ => { optimize_expr(&mut x.0, state); - optimize_expr(&mut x.2, state); + optimize_expr(&mut x.1, state); } }, @@ -518,7 +518,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) { Expr::Dot(x, _) => match (&mut x.lhs, &mut x.rhs) { // map.string (Expr::Map(m, pos), Expr::Property(p)) if m.iter().all(|(_, x)| x.is_pure()) => { - let prop = &p.2.name; + let prop = &p.4.name; // Map literal where everything is pure - promote the indexed item. // All other items can be thrown away. state.set_dirty(); @@ -676,7 +676,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) { let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect(); // Search for overloaded operators (can override built-in). - if !state.engine.has_override_by_name_and_arguments(Some(&Default::default()), &mut Default::default(), state.lib, x.name.as_ref(), arg_types.as_ref()) { + if !state.engine.has_native_override(Some(&Default::default()), &mut Default::default(), state.lib, x.name.as_ref(), arg_types.as_ref()) { if let Some(result) = get_builtin_binary_op_fn(x.name.as_ref(), &arg_values[0], &arg_values[1]) .and_then(|f| { let ctx = (state.engine, x.name.as_ref(), state.lib).into(); diff --git a/src/parser.rs b/src/parser.rs index dd73136d..3361921b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,6 +1,9 @@ //! Main module defining the lexer and parser. -use crate::ast::{BinaryExpr, CustomExpr, Expr, FnCallExpr, Ident, ReturnType, ScriptFnDef, Stmt}; +use crate::ast::{ + BinaryExpr, CustomExpr, Expr, FnCallExpr, FnHash, Ident, OpAssignment, ReturnType, ScriptFnDef, + Stmt, +}; use crate::dynamic::{AccessMode, Union}; use crate::engine::KEYWORD_THIS; use crate::module::NamespaceRef; @@ -13,7 +16,7 @@ use crate::stdlib::{ format, hash::{Hash, Hasher}, iter::empty, - num::{NonZeroU64, NonZeroUsize}, + num::NonZeroUsize, string::{String, ToString}, vec, vec::Vec, @@ -22,8 +25,8 @@ use crate::syntax::{CustomSyntax, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT}; use crate::token::{is_keyword_function, is_valid_identifier, Token, TokenStream}; use crate::utils::{get_hasher, StraightHasherBuilder}; use crate::{ - calc_script_fn_hash, Dynamic, Engine, ImmutableString, LexError, ParseError, ParseErrorType, - Position, Scope, StaticVec, AST, + calc_fn_hash, Dynamic, Engine, ImmutableString, LexError, ParseError, ParseErrorType, Position, + Scope, StaticVec, AST, }; #[cfg(not(feature = "no_float"))] @@ -34,7 +37,7 @@ use crate::FnAccess; type PERR = ParseErrorType; -type FunctionsLib = HashMap; +type FunctionsLib = HashMap; /// A type that encapsulates the current state of the parser. #[derive(Debug)] @@ -236,8 +239,11 @@ impl Expr { Self::Variable(x) if x.1.is_none() => { let ident = x.2; let getter = state.get_interned_string(crate::engine::make_getter(&ident.name)); + let hash_get = calc_fn_hash(empty(), &getter, 1); let setter = state.get_interned_string(crate::engine::make_setter(&ident.name)); - Self::Property(Box::new((getter, setter, ident.into()))) + let hash_set = calc_fn_hash(empty(), &setter, 2); + + Self::Property(Box::new((getter, hash_get, setter, hash_set, ident.into()))) } _ => self, } @@ -334,33 +340,25 @@ fn parse_fn_call( Token::RightParen => { eat_token(input, Token::RightParen); - let mut hash_script = if let Some(ref mut modules) = namespace { + let hash = if let Some(ref mut modules) = namespace { #[cfg(not(feature = "no_module"))] modules.set_index(state.find_module(&modules[0].name)); - // 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.name.as_str()); - calc_script_fn_hash(qualifiers, &id, 0) + calc_fn_hash(modules.iter().map(|m| m.name.as_str()), &id, 0) } else { - calc_script_fn_hash(empty(), &id, 0) + calc_fn_hash(empty(), &id, 0) }; - // script functions can only be valid identifiers - if !is_valid_identifier(id.chars()) { - hash_script = None; - } - return Ok(Expr::FnCall( Box::new(FnCallExpr { name: id.to_string().into(), capture, namespace, - hash_script, + hash: if is_valid_identifier(id.chars()) { + FnHash::from_script(hash) + } else { + FnHash::from_native(hash) + }, args, ..Default::default() }), @@ -385,33 +383,25 @@ fn parse_fn_call( (Token::RightParen, _) => { eat_token(input, Token::RightParen); - let mut hash_script = if let Some(modules) = namespace.as_mut() { + let hash = if let Some(modules) = namespace.as_mut() { #[cfg(not(feature = "no_module"))] modules.set_index(state.find_module(&modules[0].name)); - // 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.name.as_str()); - calc_script_fn_hash(qualifiers, &id, args.len()) + calc_fn_hash(modules.iter().map(|m| m.name.as_str()), &id, args.len()) } else { - calc_script_fn_hash(empty(), &id, args.len()) + calc_fn_hash(empty(), &id, args.len()) }; - // script functions can only be valid identifiers - if !is_valid_identifier(id.chars()) { - hash_script = None; - } - return Ok(Expr::FnCall( Box::new(FnCallExpr { name: id.to_string().into(), capture, namespace, - hash_script, + hash: if is_valid_identifier(id.chars()) { + FnHash::from_script(hash) + } else { + FnHash::from_native(hash) + }, args, ..Default::default() }), @@ -1013,10 +1003,8 @@ fn parse_primary( state.access_var(closure, *pos); }); - lib.insert( - calc_script_fn_hash(empty(), &func.name, func.params.len()).unwrap(), - func, - ); + let hash_script = calc_fn_hash(empty(), &func.name, func.params.len()); + lib.insert(hash_script, func); expr } @@ -1183,7 +1171,7 @@ fn parse_primary( } else { let mut ns: NamespaceRef = Default::default(); ns.push(var_name_def); - let index = NonZeroU64::new(42).unwrap(); // Dummy + let index = 42; // Dummy namespace = Some((index, ns)); } @@ -1243,8 +1231,7 @@ fn parse_primary( } .map(|x| match x.as_mut() { (_, Some((ref mut hash, ref mut namespace)), Ident { name, .. }) => { - *hash = - calc_script_fn_hash(namespace.iter().map(|v| v.name.as_str()), name, 0).unwrap(); + *hash = calc_fn_hash(namespace.iter().map(|v| v.name.as_str()), name, 0); #[cfg(not(feature = "no_module"))] namespace.set_index(state.find_module(&namespace[0].name)); @@ -1300,6 +1287,7 @@ fn parse_unary( Ok(Expr::FnCall( Box::new(FnCallExpr { name: op.into(), + hash: FnHash::from_native(calc_fn_hash(empty(), op, 1)), args, ..Default::default() }), @@ -1326,6 +1314,7 @@ fn parse_unary( Ok(Expr::FnCall( Box::new(FnCallExpr { name: op.into(), + hash: FnHash::from_native(calc_fn_hash(empty(), op, 1)), args, ..Default::default() }), @@ -1346,6 +1335,7 @@ fn parse_unary( Ok(Expr::FnCall( Box::new(FnCallExpr { name: op.into(), + hash: FnHash::from_native(calc_fn_hash(empty(), op, 1)), args, ..Default::default() }), @@ -1361,7 +1351,7 @@ fn parse_unary( /// Make an assignment statement. fn make_assignment_stmt<'a>( - fn_name: Cow<'static, str>, + op: Cow<'static, str>, state: &mut ParseState, lhs: Expr, rhs: Expr, @@ -1384,24 +1374,34 @@ fn make_assignment_stmt<'a>( } } + let op_info = if op.is_empty() { + None + } else { + let op2 = &op[..op.len() - 1]; // extract operator without = + + Some(OpAssignment { + hash_op_assign: calc_fn_hash(empty(), &op, 2), + hash_op: calc_fn_hash(empty(), op2, 2), + op, + }) + }; + match &lhs { // const_expr = rhs expr if expr.is_constant() => { Err(PERR::AssignmentToConstant("".into()).into_err(lhs.position())) } // var (non-indexed) = rhs - Expr::Variable(x) if x.0.is_none() => Ok(Stmt::Assignment( - Box::new((lhs, fn_name.into(), rhs)), - op_pos, - )), + Expr::Variable(x) if x.0.is_none() => { + Ok(Stmt::Assignment(Box::new((lhs, rhs, op_info)), op_pos)) + } // var (indexed) = rhs Expr::Variable(x) => { let (index, _, Ident { name, pos }) = x.as_ref(); match state.stack[(state.stack.len() - index.unwrap().get())].1 { - AccessMode::ReadWrite => Ok(Stmt::Assignment( - Box::new((lhs, fn_name.into(), rhs)), - op_pos, - )), + AccessMode::ReadWrite => { + Ok(Stmt::Assignment(Box::new((lhs, rhs, op_info)), op_pos)) + } // Constant values cannot be assigned to AccessMode::ReadOnly => { Err(PERR::AssignmentToConstant(name.to_string()).into_err(*pos)) @@ -1413,18 +1413,16 @@ fn make_assignment_stmt<'a>( match check_lvalue(&x.rhs, matches!(lhs, Expr::Dot(_, _))) { Position::NONE => match &x.lhs { // var[???] (non-indexed) = rhs, var.??? (non-indexed) = rhs - Expr::Variable(x) if x.0.is_none() => Ok(Stmt::Assignment( - Box::new((lhs, fn_name.into(), rhs)), - op_pos, - )), + Expr::Variable(x) if x.0.is_none() => { + Ok(Stmt::Assignment(Box::new((lhs, rhs, op_info)), op_pos)) + } // var[???] (indexed) = rhs, var.??? (indexed) = rhs Expr::Variable(x) => { let (index, _, Ident { name, pos }) = x.as_ref(); match state.stack[(state.stack.len() - index.unwrap().get())].1 { - AccessMode::ReadWrite => Ok(Stmt::Assignment( - Box::new((lhs, fn_name.into(), rhs)), - op_pos, - )), + AccessMode::ReadWrite => { + Ok(Stmt::Assignment(Box::new((lhs, rhs, op_info)), op_pos)) + } // Constant values cannot be assigned to AccessMode::ReadOnly => { Err(PERR::AssignmentToConstant(name.to_string()).into_err(*pos)) @@ -1506,8 +1504,11 @@ fn make_dot_expr( (lhs, Expr::Variable(x)) if x.1.is_none() => { let ident = x.2; let getter = state.get_interned_string(crate::engine::make_getter(&ident.name)); + let hash_get = calc_fn_hash(empty(), &getter, 1); let setter = state.get_interned_string(crate::engine::make_setter(&ident.name)); - let rhs = Expr::Property(Box::new((getter, setter, ident))); + let hash_set = calc_fn_hash(empty(), &setter, 2); + + let rhs = Expr::Property(Box::new((getter, hash_get, setter, hash_set, ident))); Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos) } @@ -1521,7 +1522,7 @@ fn make_dot_expr( } // lhs.dot_lhs.dot_rhs (lhs, Expr::Dot(x, pos)) => match x.lhs { - Expr::Variable(_) | Expr::Property(_) | Expr::FnCall(_, _) => { + Expr::Variable(_) | Expr::Property(_) => { let rhs = Expr::Dot( Box::new(BinaryExpr { lhs: x.lhs.into_property(state), @@ -1531,6 +1532,22 @@ fn make_dot_expr( ); Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos) } + Expr::FnCall(mut func, func_pos) => { + // Recalculate hash + func.hash = FnHash::from_script_and_native( + calc_fn_hash(empty(), &func.name, func.args.len()), + calc_fn_hash(empty(), &func.name, func.args.len() + 1), + ); + + let rhs = Expr::Dot( + Box::new(BinaryExpr { + lhs: Expr::FnCall(func, func_pos), + rhs: x.rhs, + }), + pos, + ); + Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos) + } _ => unreachable!("invalid dot expression: {:?}", x.lhs), }, // lhs.idx_lhs[idx_rhs] @@ -1544,6 +1561,10 @@ fn make_dot_expr( ); Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos) } + // lhs.nnn::func(...) + (_, Expr::FnCall(x, _)) if x.namespace.is_some() => { + unreachable!("method call should not be namespace-qualified") + } // lhs.Fn() or lhs.eval() (_, Expr::FnCall(x, pos)) if x.args.len() == 0 @@ -1567,8 +1588,14 @@ fn make_dot_expr( .into_err(pos)) } // lhs.func(...) - (lhs, func @ Expr::FnCall(_, _)) => { - Expr::Dot(Box::new(BinaryExpr { lhs, rhs: func }), op_pos) + (lhs, Expr::FnCall(mut func, func_pos)) => { + // Recalculate hash + func.hash = FnHash::from_script_and_native( + calc_fn_hash(empty(), &func.name, func.args.len()), + calc_fn_hash(empty(), &func.name, func.args.len() + 1), + ); + let rhs = Expr::FnCall(func, func_pos); + Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos) } // lhs.rhs (_, rhs) => return Err(PERR::PropertyExpected.into_err(rhs.position())), @@ -1792,9 +1819,11 @@ fn parse_binary_op( settings.ensure_level_within_max_limit(state.max_expr_depth)?; let op = op_token.syntax(); + let hash = calc_fn_hash(empty(), &op, 2); let op_base = FnCallExpr { name: op, + hash: FnHash::from_native(hash), capture: false, ..Default::default() }; @@ -1863,16 +1892,15 @@ fn parse_binary_op( .get(&s) .map_or(false, Option::is_some) => { - let hash_script = if is_valid_identifier(s.chars()) { - // Accept non-native functions for custom operators - calc_script_fn_hash(empty(), &s, 2) - } else { - None - }; + let hash = calc_fn_hash(empty(), &s, 2); Expr::FnCall( Box::new(FnCallExpr { - hash_script, + hash: if is_valid_identifier(s.chars()) { + FnHash::from_script(hash) + } else { + FnHash::from_native(hash) + }, args, ..op_base }), @@ -2604,7 +2632,7 @@ fn parse_stmt( }; let func = parse_fn(input, &mut new_state, lib, access, settings, _comments)?; - let hash = calc_script_fn_hash(empty(), &func.name, func.params.len()).unwrap(); + let hash = calc_fn_hash(empty(), &func.name, func.params.len()); if lib.contains_key(&hash) { return Err(PERR::FnDuplicatedDefinition( @@ -2871,12 +2899,10 @@ fn make_curry_from_externals(fn_expr: Expr, externals: StaticVec, pos: Po let curry_func = crate::engine::KEYWORD_FN_PTR_CURRY; - let hash_script = calc_script_fn_hash(empty(), curry_func, num_externals + 1); - let expr = Expr::FnCall( Box::new(FnCallExpr { name: curry_func.into(), - hash_script, + hash: FnHash::from_native(calc_fn_hash(empty(), curry_func, num_externals + 1)), args, ..Default::default() }), diff --git a/src/utils.rs b/src/utils.rs index b84ea2e3..a51501e9 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -10,8 +10,7 @@ use crate::stdlib::{ fmt, fmt::{Debug, Display}, hash::{BuildHasher, Hash, Hasher}, - iter::{empty, FromIterator}, - num::NonZeroU64, + iter::FromIterator, ops::{Add, AddAssign, Deref, DerefMut, Sub, SubAssign}, str::FromStr, string::{String, ToString}, @@ -19,18 +18,18 @@ use crate::stdlib::{ }; use crate::Shared; -/// A hasher that only takes one single [`NonZeroU64`] and returns it as a hash key. +/// 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 [`NonZeroU64`]. +/// Panics when hashing any data type other than a [`u64`]. #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct StraightHasher(NonZeroU64); +pub struct StraightHasher(u64); impl Hasher for StraightHasher { #[inline(always)] fn finish(&self) -> u64 { - self.0.get() + self.0 } #[inline(always)] fn write(&mut self, bytes: &[u8]) { @@ -39,9 +38,7 @@ impl Hasher for StraightHasher { let mut key = [0_u8; 8]; key.copy_from_slice(bytes); - // HACK - If it so happens to hash directly to zero (OMG!) then change it to 42... - self.0 = NonZeroU64::new(u64::from_ne_bytes(key)) - .unwrap_or_else(|| NonZeroU64::new(42).unwrap()); + self.0 = u64::from_ne_bytes(key); } } @@ -54,7 +51,7 @@ impl BuildHasher for StraightHasherBuilder { #[inline(always)] fn build_hasher(&self) -> Self::Hasher { - StraightHasher(NonZeroU64::new(42).unwrap()) + StraightHasher(42) } } @@ -64,26 +61,7 @@ pub fn get_hasher() -> ahash::AHasher { Default::default() } -/// _(INTERNALS)_ Calculate a [`NonZeroU64`] hash key from a namespace-qualified function name and -/// parameter types. -/// Exported under the `internals` feature only. -/// -/// Module names are passed in via `&str` references from an iterator. -/// Parameter types are passed in via [`TypeId`] values from an iterator. -/// -/// # Note -/// -/// The first module name is skipped. Hashing starts from the _second_ module in the chain. -#[inline(always)] -pub fn calc_native_fn_hash<'a>( - modules: impl Iterator, - fn_name: &str, - params: impl Iterator, -) -> Option { - calc_fn_hash(modules, fn_name, None, params) -} - -/// _(INTERNALS)_ Calculate a [`NonZeroU64`] hash key from a namespace-qualified function name +/// _(INTERNALS)_ Calculate a [`u64`] hash key from a namespace-qualified function name /// and the number of parameters, but no parameter types. /// Exported under the `internals` feature only. /// @@ -94,29 +72,11 @@ pub fn calc_native_fn_hash<'a>( /// /// The first module name is skipped. Hashing starts from the _second_ module in the chain. #[inline(always)] -pub fn calc_script_fn_hash<'a>( - modules: impl Iterator, - fn_name: &str, - num: usize, -) -> Option { - calc_fn_hash(modules, fn_name, Some(num), empty()) -} - -/// Calculate a [`NonZeroU64`] hash key from a namespace-qualified function name and parameter types. -/// -/// Module names are passed in via `&str` references from an iterator. -/// Parameter types are passed in via [`TypeId`] values from an iterator. -/// -/// # Note -/// -/// The first module name is skipped. Hashing starts from the _second_ module in the chain. -#[inline(always)] -fn calc_fn_hash<'a>( +pub fn calc_fn_hash<'a>( mut modules: impl Iterator, fn_name: &str, - num: Option, - params: impl Iterator, -) -> Option { + num: usize, +) -> u64 { let s = &mut get_hasher(); // Hash a boolean indicating whether the hash is namespace-qualified. @@ -124,20 +84,30 @@ fn calc_fn_hash<'a>( // We always skip the first module modules.for_each(|m| m.hash(s)); fn_name.hash(s); - if let Some(num) = num { - num.hash(s); - } else { - params.for_each(|t| t.hash(s)); - } - // HACK - If it so happens to hash directly to zero (OMG!) then change it to 42... - NonZeroU64::new(s.finish()).or_else(|| NonZeroU64::new(42)) + num.hash(s); + s.finish() } -/// Combine two [`NonZeroU64`] hashes by taking the XOR of them. +/// _(INTERNALS)_ Calculate a [`u64`] hash key from a list of parameter types. +/// Exported under the `internals` feature only. +/// +/// Parameter types are passed in via [`TypeId`] values from an iterator. #[inline(always)] -pub(crate) fn combine_hashes(a: NonZeroU64, b: NonZeroU64) -> NonZeroU64 { - // HACK - If it so happens to hash directly to zero (OMG!) then change it to 42... - NonZeroU64::new(a.get() ^ b.get()).unwrap_or_else(|| NonZeroU64::new(42).unwrap()) +pub fn calc_fn_params_hash(params: impl Iterator) -> u64 { + let s = &mut get_hasher(); + let mut len = 0; + params.for_each(|t| { + t.hash(s); + len += 1; + }); + len.hash(s); + s.finish() +} + +/// Combine two [`u64`] hashes by taking the XOR of them. +#[inline(always)] +pub(crate) fn combine_hashes(a: u64, b: u64) -> u64 { + a ^ b } /// _(INTERNALS)_ A type that wraps a [`HashMap`] and implements [`Hash`]. diff --git a/tests/bit_shift.rs b/tests/bit_shift.rs index 0b888e32..fb318e4d 100644 --- a/tests/bit_shift.rs +++ b/tests/bit_shift.rs @@ -1,4 +1,4 @@ -use rhai::{Engine, EvalAltResult, INT}; + use rhai::{Engine, EvalAltResult, INT}; #[test] fn test_left_shift() -> Result<(), Box> { diff --git a/tests/string.rs b/tests/string.rs index 0db10eb0..2078b90c 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -68,12 +68,12 @@ fn test_string_dynamic() -> Result<(), Box> { fn test_string_mut() -> Result<(), Box> { let mut engine = Engine::new(); - engine.register_fn("foo", |x: INT, s: &str| s.len() as INT + x); - engine.register_fn("bar", |x: INT, s: String| s.len() as INT + x); + engine.register_fn("foo", |s: &str| s.len() as INT); + engine.register_fn("bar", |s: String| s.len() as INT); engine.register_fn("baz", |s: &mut String| s.len()); - assert_eq!(engine.eval::(r#"foo(1, "hello")"#)?, 6); - assert_eq!(engine.eval::(r#"bar(1, "hello")"#)?, 6); + assert_eq!(engine.eval::(r#"foo("hello")"#)?, 5); + assert_eq!(engine.eval::(r#"bar("hello")"#)?, 5); assert!( matches!(*engine.eval::(r#"baz("hello")"#).expect_err("should error"), EvalAltResult::ErrorFunctionNotFound(f, _) if f == "baz (&str | ImmutableString | String)"