in rhai/src/func/call.rs [163:327]
fn resolve_fn<'s>(
&self,
_global: &GlobalRuntimeState,
caches: &'s mut Caches,
local_entry: &'s mut Option<FnResolutionCacheEntry>,
op_token: Option<&Token>,
hash_base: u64,
args: Option<&mut FnCallArgs>,
allow_dynamic: bool,
) -> Option<&'s FnResolutionCacheEntry> {
let mut hash = args.as_deref().map_or(hash_base, |args| {
calc_fn_hash_full(hash_base, args.iter().map(|a| a.type_id()))
});
let cache = caches.fn_resolution_cache_mut();
match cache.map.entry(hash) {
Entry::Occupied(entry) => entry.into_mut().as_ref(),
Entry::Vacant(entry) => {
let num_args = args.as_deref().map_or(0, FnCallArgs::len);
let mut max_bitmask = 0; // One above maximum bitmask based on number of parameters.
// Set later when a specific matching function is not found.
let mut bitmask = 1usize; // Bitmask of which parameter to replace with `Dynamic`
loop {
#[cfg(not(feature = "no_function"))]
let func = _global
.lib
.iter()
.rev()
.chain(self.global_modules.iter())
.find_map(|m| m.get_fn(hash).map(|f| (f, m.id_raw())));
#[cfg(feature = "no_function")]
let func = None;
let func = func.or_else(|| {
self.global_modules
.iter()
.find_map(|m| m.get_fn(hash).map(|f| (f, m.id_raw())))
});
#[cfg(not(feature = "no_module"))]
let func = func
.or_else(|| _global.get_qualified_fn(hash, true))
.or_else(|| {
self.global_sub_modules
.as_ref()
.into_iter()
.flatten()
.filter(|(_, m)| m.contains_indexed_global_functions())
.find_map(|(_, m)| {
m.get_qualified_fn(hash).map(|f| (f, m.id_raw()))
})
});
if let Some((f, s)) = func {
// Specific version found
let new_entry = FnResolutionCacheEntry {
func: f.clone(),
source: s.cloned(),
};
return if cache.filter.is_absent_and_set(hash) {
// Do not cache "one-hit wonders"
*local_entry = Some(new_entry);
local_entry.as_ref()
} else {
// Cache entry
entry.insert(Some(new_entry)).as_ref()
};
}
// Check `Dynamic` parameters for functions with parameters
if allow_dynamic && max_bitmask == 0 && num_args > 0 {
let is_dynamic = self
.global_modules
.iter()
.any(|m| m.may_contain_dynamic_fn(hash_base));
#[cfg(not(feature = "no_function"))]
let is_dynamic = is_dynamic
|| _global
.lib
.iter()
.any(|m| m.may_contain_dynamic_fn(hash_base));
#[cfg(not(feature = "no_module"))]
let is_dynamic = is_dynamic
|| _global.may_contain_dynamic_fn(hash_base)
|| self.global_sub_modules.as_ref().map_or(false, |m| {
m.values().any(|m| m.may_contain_dynamic_fn(hash_base))
});
// Set maximum bitmask when there are dynamic versions of the function
if is_dynamic {
max_bitmask = 1usize << usize::min(num_args, MAX_DYNAMIC_PARAMETERS);
}
}
// Stop when all permutations are exhausted
if bitmask >= max_bitmask {
if num_args != 2 {
return None;
}
// Try to find a built-in version
let builtin =
args.and_then(|args| match op_token {
None => None,
Some(token) if token.is_op_assignment() => {
let (first_arg, rest_args) = args.split_first().unwrap();
get_builtin_op_assignment_fn(token, first_arg, rest_args[0])
.map(|(f, has_context)| FnResolutionCacheEntry {
func: CallableFunction::Method {
func: Shared::new(f),
has_context,
is_pure: false,
},
source: None,
})
}
Some(token) => get_builtin_binary_op_fn(token, args[0], args[1])
.map(|(f, has_context)| FnResolutionCacheEntry {
func: CallableFunction::Method {
func: Shared::new(f),
has_context,
is_pure: true,
},
source: None,
}),
});
return if cache.filter.is_absent_and_set(hash) {
// Do not cache "one-hit wonders"
*local_entry = builtin;
local_entry.as_ref()
} else {
// Cache entry
entry.insert(builtin).as_ref()
};
}
// Try all permutations with `Dynamic` wildcards
hash = calc_fn_hash_full(
hash_base,
args.as_ref()
.expect("no permutations")
.iter()
.enumerate()
.map(|(i, a)| {
let mask = 1usize << (num_args - i - 1);
if bitmask & mask == 0 {
a.type_id()
} else {
// Replace with `Dynamic`
TypeId::of::<Dynamic>()
}
}),
);
bitmask += 1;
}
}
}
}