Avoid repeating empty TypeId's when calculating hash.
This commit is contained in:
parent
3295060dba
commit
ab76a69b12
@ -12,7 +12,7 @@ use crate::r#unsafe::unsafe_cast_var_name_to_lifetime;
|
||||
use crate::result::EvalAltResult;
|
||||
use crate::scope::{EntryType as ScopeEntryType, Scope};
|
||||
use crate::token::Position;
|
||||
use crate::utils::{StaticVec, EMPTY_TYPE_ID};
|
||||
use crate::utils::StaticVec;
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
use crate::module::{resolvers, ModuleRef, ModuleResolver};
|
||||
@ -25,7 +25,7 @@ use crate::stdlib::{
|
||||
boxed::Box,
|
||||
collections::HashMap,
|
||||
format,
|
||||
iter::{empty, once, repeat},
|
||||
iter::{empty, once},
|
||||
mem,
|
||||
num::NonZeroUsize,
|
||||
ops::{Deref, DerefMut},
|
||||
@ -215,8 +215,7 @@ impl<'a> State<'a> {
|
||||
/// Since script-defined functions have `Dynamic` parameters, functions with the same name
|
||||
/// and number of parameters are considered equivalent.
|
||||
///
|
||||
/// The key of the `HashMap` is a `u64` hash calculated by the function `calc_fn_hash`
|
||||
/// with dummy parameter types `EMPTY_TYPE_ID()` repeated the correct number of times.
|
||||
/// 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, SharedFnDef>);
|
||||
|
||||
@ -226,9 +225,8 @@ impl FunctionsLib {
|
||||
FunctionsLib(
|
||||
vec.into_iter()
|
||||
.map(|fn_def| {
|
||||
// Qualifiers (none) + function name + placeholders (one for each parameter).
|
||||
let args_iter = repeat(EMPTY_TYPE_ID()).take(fn_def.params.len());
|
||||
let hash = calc_fn_hash(empty(), &fn_def.name, args_iter);
|
||||
// Qualifiers (none) + function name + number of arguments.
|
||||
let hash = calc_fn_hash(empty(), &fn_def.name, fn_def.params.len(), empty());
|
||||
(hash, fn_def.into())
|
||||
})
|
||||
.collect(),
|
||||
@ -253,8 +251,8 @@ impl FunctionsLib {
|
||||
params: usize,
|
||||
public_only: bool,
|
||||
) -> Option<&FnDef> {
|
||||
// Qualifiers (none) + function name + placeholders (one for each parameter).
|
||||
let hash_fn_def = calc_fn_hash(empty(), name, repeat(EMPTY_TYPE_ID()).take(params));
|
||||
// Qualifiers (none) + function name + number of arguments.
|
||||
let hash_fn_def = calc_fn_hash(empty(), name, params, empty());
|
||||
let fn_def = self.get_function(hash_fn_def);
|
||||
|
||||
match fn_def.as_ref().map(|f| f.access) {
|
||||
@ -873,8 +871,13 @@ impl Engine {
|
||||
pos: Position,
|
||||
level: usize,
|
||||
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
|
||||
// Qualifiers (none) + function name + argument `TypeId`'s.
|
||||
let hash_fn = calc_fn_hash(empty(), fn_name, args.iter().map(|a| a.type_id()));
|
||||
// Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
|
||||
let hash_fn = calc_fn_hash(
|
||||
empty(),
|
||||
fn_name,
|
||||
args.len(),
|
||||
args.iter().map(|a| a.type_id()),
|
||||
);
|
||||
let hashes = (hash_fn, hash_fn_def);
|
||||
|
||||
match fn_name {
|
||||
@ -1322,7 +1325,7 @@ impl Engine {
|
||||
Dynamic(Union::Array(mut rhs_value)) => {
|
||||
let op = "==";
|
||||
let def_value = false.into();
|
||||
let hash_fn_def = calc_fn_hash(empty(), op, repeat(EMPTY_TYPE_ID()).take(2));
|
||||
let hash_fn_def = calc_fn_hash(empty(), op, 2, empty());
|
||||
|
||||
// Call the `==` operator to compare each value
|
||||
for value in rhs_value.iter_mut() {
|
||||
@ -1331,7 +1334,8 @@ impl Engine {
|
||||
let pos = rhs.position();
|
||||
|
||||
// Qualifiers (none) + function name + argument `TypeId`'s.
|
||||
let hash_fn = calc_fn_hash(empty(), op, args.iter().map(|a| a.type_id()));
|
||||
let hash_fn =
|
||||
calc_fn_hash(empty(), op, args.len(), args.iter().map(|a| a.type_id()));
|
||||
let hashes = (hash_fn, hash_fn_def);
|
||||
|
||||
let (r, _) = self
|
||||
@ -1486,7 +1490,7 @@ impl Engine {
|
||||
let mut args: StaticVec<_> = arg_values.iter_mut().collect();
|
||||
|
||||
if name == KEYWORD_EVAL && args.len() == 1 && args.get(0).is::<String>() {
|
||||
let hash_fn = calc_fn_hash(empty(), name, once(TypeId::of::<String>()));
|
||||
let hash_fn = calc_fn_hash(empty(), name, 1, once(TypeId::of::<String>()));
|
||||
|
||||
if !self.has_override(state, (hash_fn, *hash_fn_def)) {
|
||||
// eval - only in function call style
|
||||
@ -1547,11 +1551,11 @@ impl Engine {
|
||||
|
||||
// Rust functions are indexed in two steps:
|
||||
// 1) Calculate a hash in a similar manner to script-defined functions,
|
||||
// i.e. qualifiers + function name + dummy parameter types (one for each parameter).
|
||||
// 2) Calculate a second hash with no qualifiers, empty function name, and
|
||||
// the actual list of parameter `TypeId`'.s
|
||||
// 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
|
||||
let hash_fn_args =
|
||||
calc_fn_hash(empty(), "", args.iter().map(|a| a.type_id()));
|
||||
calc_fn_hash(empty(), "", 0, args.iter().map(|a| a.type_id()));
|
||||
// 3) The final hash is the XOR of the two hashes.
|
||||
let hash_fn_native = *hash_fn_def ^ hash_fn_args;
|
||||
|
||||
|
@ -8,18 +8,22 @@ use crate::fn_native::{
|
||||
CallableFunction::{Method, Pure},
|
||||
FnCallArgs, IteratorFn, SharedFunction,
|
||||
};
|
||||
use crate::parser::{FnAccess, AST};
|
||||
use crate::parser::{
|
||||
FnAccess,
|
||||
FnAccess::{Private, Public},
|
||||
AST,
|
||||
};
|
||||
use crate::result::EvalAltResult;
|
||||
use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope};
|
||||
use crate::token::{Position, Token};
|
||||
use crate::utils::{StaticVec, EMPTY_TYPE_ID};
|
||||
use crate::utils::StaticVec;
|
||||
|
||||
use crate::stdlib::{
|
||||
any::TypeId,
|
||||
boxed::Box,
|
||||
collections::HashMap,
|
||||
fmt,
|
||||
iter::{empty, repeat},
|
||||
iter::empty,
|
||||
mem,
|
||||
num::NonZeroUsize,
|
||||
ops::{Deref, DerefMut},
|
||||
@ -30,9 +34,6 @@ use crate::stdlib::{
|
||||
vec::Vec,
|
||||
};
|
||||
|
||||
/// Default function access mode.
|
||||
const DEF_ACCESS: FnAccess = FnAccess::Public;
|
||||
|
||||
/// Return type of module-level Rust function.
|
||||
pub type FuncReturn<T> = Result<T, Box<EvalAltResult>>;
|
||||
|
||||
@ -281,7 +282,7 @@ impl Module {
|
||||
params: &[TypeId],
|
||||
func: CallableFunction,
|
||||
) -> u64 {
|
||||
let hash_fn = calc_fn_hash(empty(), &name, params.iter().cloned());
|
||||
let hash_fn = calc_fn_hash(empty(), &name, params.len(), params.iter().cloned());
|
||||
|
||||
let params = params.into_iter().cloned().collect();
|
||||
|
||||
@ -312,7 +313,7 @@ impl Module {
|
||||
) -> u64 {
|
||||
let f = move |_: &mut FnCallArgs| func().map(Dynamic::from);
|
||||
let arg_types = [];
|
||||
self.set_fn(name.into(), DEF_ACCESS, &arg_types, Pure(Box::new(f)))
|
||||
self.set_fn(name.into(), Public, &arg_types, Pure(Box::new(f)))
|
||||
}
|
||||
|
||||
/// Set a Rust function taking one parameter into the module, returning a hash key.
|
||||
@ -337,7 +338,7 @@ impl Module {
|
||||
let f =
|
||||
move |args: &mut FnCallArgs| func(mem::take(args[0]).cast::<A>()).map(Dynamic::from);
|
||||
let arg_types = [TypeId::of::<A>()];
|
||||
self.set_fn(name.into(), DEF_ACCESS, &arg_types, Pure(Box::new(f)))
|
||||
self.set_fn(name.into(), Public, &arg_types, Pure(Box::new(f)))
|
||||
}
|
||||
|
||||
/// Set a Rust function taking one mutable parameter into the module, returning a hash key.
|
||||
@ -363,7 +364,7 @@ impl Module {
|
||||
func(args[0].downcast_mut::<A>().unwrap()).map(Dynamic::from)
|
||||
};
|
||||
let arg_types = [TypeId::of::<A>()];
|
||||
self.set_fn(name.into(), DEF_ACCESS, &arg_types, Method(Box::new(f)))
|
||||
self.set_fn(name.into(), Public, &arg_types, Method(Box::new(f)))
|
||||
}
|
||||
|
||||
/// Set a Rust function taking two parameters into the module, returning a hash key.
|
||||
@ -394,7 +395,7 @@ impl Module {
|
||||
func(a, b).map(Dynamic::from)
|
||||
};
|
||||
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>()];
|
||||
self.set_fn(name.into(), DEF_ACCESS, &arg_types, Pure(Box::new(f)))
|
||||
self.set_fn(name.into(), Public, &arg_types, Pure(Box::new(f)))
|
||||
}
|
||||
|
||||
/// Set a Rust function taking two parameters (the first one mutable) into the module,
|
||||
@ -429,7 +430,7 @@ impl Module {
|
||||
func(a, b).map(Dynamic::from)
|
||||
};
|
||||
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>()];
|
||||
self.set_fn(name.into(), DEF_ACCESS, &arg_types, Method(Box::new(f)))
|
||||
self.set_fn(name.into(), Public, &arg_types, Method(Box::new(f)))
|
||||
}
|
||||
|
||||
/// Set a Rust function taking three parameters into the module, returning a hash key.
|
||||
@ -467,7 +468,7 @@ impl Module {
|
||||
func(a, b, c).map(Dynamic::from)
|
||||
};
|
||||
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
|
||||
self.set_fn(name.into(), DEF_ACCESS, &arg_types, Pure(Box::new(f)))
|
||||
self.set_fn(name.into(), Public, &arg_types, Pure(Box::new(f)))
|
||||
}
|
||||
|
||||
/// Set a Rust function taking three parameters (the first one mutable) into the module,
|
||||
@ -506,7 +507,7 @@ impl Module {
|
||||
func(a, b, c).map(Dynamic::from)
|
||||
};
|
||||
let arg_types = [TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>()];
|
||||
self.set_fn(name.into(), DEF_ACCESS, &arg_types, Method(Box::new(f)))
|
||||
self.set_fn(name.into(), Public, &arg_types, Method(Box::new(f)))
|
||||
}
|
||||
|
||||
/// Get a Rust function.
|
||||
@ -617,27 +618,24 @@ impl Module {
|
||||
// Index all variables
|
||||
for (var_name, value) in &module.variables {
|
||||
// Qualifiers + variable name
|
||||
let hash_var = calc_fn_hash(qualifiers.iter().map(|&v| v), var_name, empty());
|
||||
let hash_var = calc_fn_hash(qualifiers.iter().map(|&v| v), var_name, 0, empty());
|
||||
variables.push((hash_var, value.clone()));
|
||||
}
|
||||
// Index all Rust functions
|
||||
for (name, access, params, func) in module.functions.values() {
|
||||
match access {
|
||||
// Private functions are not exported
|
||||
FnAccess::Private => continue,
|
||||
FnAccess::Public => (),
|
||||
Private => continue,
|
||||
Public => (),
|
||||
}
|
||||
// Rust functions are indexed in two steps:
|
||||
// 1) Calculate a hash in a similar manner to script-defined functions,
|
||||
// i.e. qualifiers + function name + dummy parameter types (one for each parameter).
|
||||
let hash_fn_def = calc_fn_hash(
|
||||
qualifiers.iter().map(|&v| v),
|
||||
name,
|
||||
repeat(EMPTY_TYPE_ID()).take(params.len()),
|
||||
);
|
||||
// 2) Calculate a second hash with no qualifiers, empty function name, and
|
||||
// the actual list of parameter `TypeId`'.s
|
||||
let hash_fn_args = calc_fn_hash(empty(), "", params.iter().cloned());
|
||||
// i.e. qualifiers + function name + number of arguments.
|
||||
let hash_fn_def =
|
||||
calc_fn_hash(qualifiers.iter().map(|&v| v), name, params.len(), empty());
|
||||
// 2) Calculate a second hash with no qualifiers, empty function name,
|
||||
// zero number of arguments, and the actual list of argument `TypeId`'.s
|
||||
let hash_fn_args = calc_fn_hash(empty(), "", 0, params.iter().cloned());
|
||||
// 3) The final hash is the XOR of the two hashes.
|
||||
let hash_fn_native = hash_fn_def ^ hash_fn_args;
|
||||
|
||||
@ -647,14 +645,15 @@ impl Module {
|
||||
for fn_def in module.fn_lib.values() {
|
||||
match fn_def.access {
|
||||
// Private functions are not exported
|
||||
FnAccess::Private => continue,
|
||||
DEF_ACCESS => (),
|
||||
Private => continue,
|
||||
Public => (),
|
||||
}
|
||||
// Qualifiers + function name + placeholders (one for each parameter)
|
||||
// Qualifiers + function name + number of arguments.
|
||||
let hash_fn_def = calc_fn_hash(
|
||||
qualifiers.iter().map(|&v| v),
|
||||
&fn_def.name,
|
||||
repeat(EMPTY_TYPE_ID()).take(fn_def.params.len()),
|
||||
fn_def.params.len(),
|
||||
empty(),
|
||||
);
|
||||
functions.push((hash_fn_def, CallableFunction::Script(fn_def.clone()).into()));
|
||||
}
|
||||
|
@ -119,7 +119,12 @@ fn call_fn(
|
||||
pos: Position,
|
||||
) -> Result<Option<Dynamic>, Box<EvalAltResult>> {
|
||||
// Search built-in's and external functions
|
||||
let hash_fn = calc_fn_hash(empty(), fn_name, args.iter().map(|a| a.type_id()));
|
||||
let hash_fn = calc_fn_hash(
|
||||
empty(),
|
||||
fn_name,
|
||||
args.len(),
|
||||
args.iter().map(|a| a.type_id()),
|
||||
);
|
||||
|
||||
global_module
|
||||
.get_fn(hash_fn)
|
||||
|
@ -7,7 +7,7 @@ use crate::error::{LexError, ParseError, ParseErrorType};
|
||||
use crate::optimize::{optimize_into_ast, OptimizationLevel};
|
||||
use crate::scope::{EntryType as ScopeEntryType, Scope};
|
||||
use crate::token::{Position, Token, TokenIterator};
|
||||
use crate::utils::{StaticVec, EMPTY_TYPE_ID};
|
||||
use crate::utils::StaticVec;
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
use crate::module::ModuleRef;
|
||||
@ -22,7 +22,7 @@ use crate::stdlib::{
|
||||
char,
|
||||
collections::HashMap,
|
||||
format,
|
||||
iter::{empty, repeat, Peekable},
|
||||
iter::{empty, Peekable},
|
||||
num::NonZeroUsize,
|
||||
ops::{Add, Deref, DerefMut},
|
||||
rc::Rc,
|
||||
@ -767,13 +767,14 @@ fn parse_call_expr<'a>(
|
||||
|
||||
// Rust functions are indexed in two steps:
|
||||
// 1) Calculate a hash in a similar manner to script-defined functions,
|
||||
// i.e. qualifiers + function name + no parameters.
|
||||
// 2) Calculate a second hash with no qualifiers, empty function name, and
|
||||
// the actual list of parameter `TypeId`'.s
|
||||
// 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.
|
||||
calc_fn_hash(modules.iter().map(|(m, _)| m.as_str()), &id, empty())
|
||||
let qualifiers = modules.iter().map(|(m, _)| m.as_str());
|
||||
calc_fn_hash(qualifiers, &id, 0, empty())
|
||||
} else {
|
||||
calc_fn_hash(empty(), &id, empty())
|
||||
calc_fn_hash(empty(), &id, 0, empty())
|
||||
}
|
||||
};
|
||||
// Qualifiers (none) + function name + no parameters.
|
||||
@ -799,7 +800,6 @@ fn parse_call_expr<'a>(
|
||||
// id(...args)
|
||||
(Token::RightParen, _) => {
|
||||
eat_token(input, Token::RightParen);
|
||||
let args_iter = repeat(EMPTY_TYPE_ID()).take(args.len());
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
let hash_fn_def = {
|
||||
@ -808,13 +808,14 @@ fn parse_call_expr<'a>(
|
||||
|
||||
// Rust functions are indexed in two steps:
|
||||
// 1) Calculate a hash in a similar manner to script-defined functions,
|
||||
// i.e. qualifiers + function name + dummy parameter types (one for each parameter).
|
||||
// 2) Calculate a second hash with no qualifiers, empty function name, and
|
||||
// the actual list of parameter `TypeId`'.s
|
||||
// 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.
|
||||
calc_fn_hash(modules.iter().map(|(m, _)| m.as_str()), &id, args_iter)
|
||||
let qualifiers = modules.iter().map(|(m, _)| m.as_str());
|
||||
calc_fn_hash(qualifiers, &id, args.len(), empty())
|
||||
} else {
|
||||
calc_fn_hash(empty(), &id, args_iter)
|
||||
calc_fn_hash(empty(), &id, args.len(), empty())
|
||||
}
|
||||
};
|
||||
// Qualifiers (none) + function name + dummy parameter types (one for each parameter).
|
||||
@ -1297,7 +1298,7 @@ fn parse_primary<'a>(
|
||||
let modules = modules.as_mut().unwrap();
|
||||
|
||||
// Qualifiers + variable name
|
||||
*hash = calc_fn_hash(modules.iter().map(|(v, _)| v.as_str()), name, empty());
|
||||
*hash = calc_fn_hash(modules.iter().map(|(v, _)| v.as_str()), name, 0, empty());
|
||||
modules.set_index(state.find_module(&modules.get(0).0));
|
||||
}
|
||||
_ => (),
|
||||
@ -1362,7 +1363,7 @@ fn parse_unary<'a>(
|
||||
// Call negative function
|
||||
expr => {
|
||||
let op = "-";
|
||||
let hash = calc_fn_hash(empty(), op, repeat(EMPTY_TYPE_ID()).take(2));
|
||||
let hash = calc_fn_hash(empty(), op, 2, empty());
|
||||
let mut args = StaticVec::new();
|
||||
args.push(expr);
|
||||
|
||||
@ -1388,7 +1389,7 @@ fn parse_unary<'a>(
|
||||
args.push(parse_primary(input, state, level + 1, allow_stmt_expr)?);
|
||||
|
||||
let op = "!";
|
||||
let hash = calc_fn_hash(empty(), op, repeat(EMPTY_TYPE_ID()).take(2));
|
||||
let hash = calc_fn_hash(empty(), op, 2, empty());
|
||||
|
||||
Ok(Expr::FnCall(Box::new((
|
||||
(op.into(), pos),
|
||||
@ -1492,7 +1493,7 @@ fn parse_op_assignment_stmt<'a>(
|
||||
args.push(lhs_copy);
|
||||
args.push(rhs);
|
||||
|
||||
let hash = calc_fn_hash(empty(), &op, repeat(EMPTY_TYPE_ID()).take(args.len()));
|
||||
let hash = calc_fn_hash(empty(), &op, args.len(), empty());
|
||||
let rhs_expr = Expr::FnCall(Box::new(((op, pos), None, hash, args, None)));
|
||||
|
||||
make_assignment_stmt(state, lhs, rhs_expr, pos)
|
||||
@ -1780,7 +1781,7 @@ fn parse_binary_op<'a>(
|
||||
|
||||
let cmp_def = Some(false.into());
|
||||
let op = op_token.syntax();
|
||||
let hash = calc_fn_hash(empty(), &op, repeat(EMPTY_TYPE_ID()).take(2));
|
||||
let hash = calc_fn_hash(empty(), &op, 2, empty());
|
||||
|
||||
let mut args = StaticVec::new();
|
||||
args.push(root);
|
||||
@ -1839,8 +1840,7 @@ fn parse_binary_op<'a>(
|
||||
Expr::FnCall(x) => {
|
||||
let ((id, _), _, hash, args, _) = x.as_mut();
|
||||
// Recalculate function call hash because there is an additional argument
|
||||
let args_iter = repeat(EMPTY_TYPE_ID()).take(args.len() + 1);
|
||||
*hash = calc_fn_hash(empty(), id, args_iter);
|
||||
*hash = calc_fn_hash(empty(), id, args.len() + 1, empty());
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
@ -2540,12 +2540,8 @@ fn parse_global_level<'a>(
|
||||
let mut state = ParseState::new(max_expr_depth.1);
|
||||
let func = parse_fn(input, &mut state, access, 0, true)?;
|
||||
|
||||
// Qualifiers (none) + function name + argument `TypeId`'s
|
||||
let hash = calc_fn_hash(
|
||||
empty(),
|
||||
&func.name,
|
||||
repeat(EMPTY_TYPE_ID()).take(func.params.len()),
|
||||
);
|
||||
// Qualifiers (none) + function name + number of arguments.
|
||||
let hash = calc_fn_hash(empty(), &func.name, func.params.len(), empty());
|
||||
|
||||
functions.insert(hash, func);
|
||||
continue;
|
||||
|
@ -21,11 +21,6 @@ use crate::stdlib::collections::hash_map::DefaultHasher;
|
||||
#[cfg(feature = "no_std")]
|
||||
use ahash::AHasher;
|
||||
|
||||
#[inline(always)]
|
||||
pub fn EMPTY_TYPE_ID() -> TypeId {
|
||||
TypeId::of::<()>()
|
||||
}
|
||||
|
||||
/// 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.
|
||||
@ -37,6 +32,7 @@ pub fn EMPTY_TYPE_ID() -> TypeId {
|
||||
pub fn calc_fn_spec<'a>(
|
||||
modules: impl Iterator<Item = &'a str>,
|
||||
fn_name: &str,
|
||||
num: usize,
|
||||
params: impl Iterator<Item = TypeId>,
|
||||
) -> u64 {
|
||||
#[cfg(feature = "no_std")]
|
||||
@ -47,6 +43,7 @@ pub fn calc_fn_spec<'a>(
|
||||
// We always skip the first module
|
||||
modules.skip(1).for_each(|m| m.hash(&mut s));
|
||||
s.write(fn_name.as_bytes());
|
||||
s.write_usize(num);
|
||||
params.for_each(|t| t.hash(&mut s));
|
||||
s.finish()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user