diff --git a/src/func/call.rs b/src/func/call.rs index 62f92c0b..55300581 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -165,7 +165,7 @@ impl Engine { ) } - /// Resolve a function call. + /// Resolve a normal (non-qualified) function call. /// /// Search order: /// 1) AST - script functions in the AST @@ -201,11 +201,8 @@ impl Engine { .entry(hash) .or_insert_with(|| { let num_args = args.as_ref().map_or(0, |a| a.len()); - let max_bitmask = if !allow_dynamic { - 0 - } else { - 1usize << usize::min(num_args, MAX_DYNAMIC_PARAMETERS) - }; + 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 { @@ -247,62 +244,85 @@ impl Engine { }) }); - match func { - // Specific version found - Some(f) => return Some(f), + // Specific version found + if let Some(f) = func { + return Some(f); + } - // Stop when all permutations are exhausted - None if bitmask >= max_bitmask => { - if num_args != 2 { - return None; - } + // Check `Dynamic` parameters for functions with parameters + if allow_dynamic && max_bitmask == 0 && num_args > 0 { + let is_dynamic = lib.iter().any(|&m| m.contains_dynamic_fn(hash_script)) + || self + .global_modules + .iter() + .any(|m| m.contains_dynamic_fn(hash_script)); - return args.and_then(|args| { - if !is_op_assignment { - get_builtin_binary_op_fn(fn_name, &args[0], &args[1]).map(|f| { - FnResolutionCacheEntry { - func: CallableFunction::from_method( - Box::new(f) as Box - ), - source: None, - } - }) - } else { - let (first_arg, rest_args) = args.split_first().unwrap(); + #[cfg(not(feature = "no_module"))] + let is_dynamic = is_dynamic + || _global + .iter_imports_raw() + .any(|(_, m)| m.contains_dynamic_fn(hash_script)) + || self + .global_sub_modules + .values() + .any(|m| m.contains_dynamic_fn(hash_script)); - get_builtin_op_assignment_fn(fn_name, *first_arg, rest_args[0]) - .map(|f| FnResolutionCacheEntry { - func: CallableFunction::from_method( - Box::new(f) as Box - ), - source: None, - }) - } - }); - } - - // Try all permutations with `Dynamic` wildcards - None => { - let hash_params = calc_fn_params_hash( - args.as_ref() - .expect("no permutations") - .iter() - .enumerate() - .map(|(i, a)| { - let mask = 1usize << (num_args - i - 1); - if bitmask & mask != 0 { - // Replace with `Dynamic` - TypeId::of::() - } else { - a.type_id() - } - }), - ); - hash = combine_hashes(hash_script, hash_params); - - bitmask += 1; + // 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; + } + + return args.and_then(|args| { + if !is_op_assignment { + get_builtin_binary_op_fn(fn_name, &args[0], &args[1]).map(|f| { + FnResolutionCacheEntry { + func: CallableFunction::from_method( + Box::new(f) as Box + ), + source: None, + } + }) + } else { + let (first_arg, rest_args) = args.split_first().unwrap(); + + get_builtin_op_assignment_fn(fn_name, *first_arg, rest_args[0]).map( + |f| FnResolutionCacheEntry { + func: CallableFunction::from_method( + Box::new(f) as Box + ), + source: None, + }, + ) + } + }); + } + + // Try all permutations with `Dynamic` wildcards + let hash_params = calc_fn_params_hash( + args.as_ref() + .expect("no permutations") + .iter() + .enumerate() + .map(|(i, a)| { + let mask = 1usize << (num_args - i - 1); + if bitmask & mask != 0 { + // Replace with `Dynamic` + TypeId::of::() + } else { + a.type_id() + } + }), + ); + hash = combine_hashes(hash_script, hash_params); + + bitmask += 1; } }); diff --git a/src/module/mod.rs b/src/module/mod.rs index 1b80bc9f..66c693e0 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -7,7 +7,7 @@ use crate::func::{ }; use crate::types::{dynamic::Variant, CustomTypesCollection}; use crate::{ - calc_fn_params_hash, calc_qualified_fn_hash, combine_hashes, Dynamic, Identifier, + calc_fn_hash, calc_fn_params_hash, calc_qualified_fn_hash, combine_hashes, Dynamic, Identifier, ImmutableString, NativeCallContext, RhaiResultOf, Shared, StaticVec, }; #[cfg(feature = "no_std")] @@ -241,11 +241,13 @@ pub struct Module { variables: BTreeMap, /// Flattened collection of all [`Module`] variables, including those in sub-modules. all_variables: BTreeMap, - /// External Rust functions. + /// Functions (both native Rust and scripted). functions: BTreeMap>, - /// Flattened collection of all external Rust functions, native or scripted. + /// Flattened collection of all functions, native Rust and scripted. /// including those in sub-modules. all_functions: BTreeMap, + /// Native Rust functions (in scripted hash format) that contain [`Dynamic`] parameters. + dynamic_functions: BTreeSet, /// Iterator functions, keyed by the type producing the iterator. type_iterators: BTreeMap>, /// Flattened collection of iterator functions, including those in sub-modules. @@ -348,6 +350,7 @@ impl Module { all_variables: BTreeMap::new(), functions: BTreeMap::new(), all_functions: BTreeMap::new(), + dynamic_functions: BTreeSet::new(), type_iterators: BTreeMap::new(), all_type_iterators: BTreeMap::new(), indexed: true, @@ -417,6 +420,25 @@ impl Module { self } + /// Clear the [`Module`]. + #[inline(always)] + pub fn clear(&mut self) { + self.id.clear(); + self.internal = false; + self.standard = false; + self.custom_types.clear(); + self.modules.clear(); + self.variables.clear(); + self.all_variables.clear(); + self.functions.clear(); + self.all_functions.clear(); + self.dynamic_functions.clear(); + self.type_iterators.clear(); + self.all_type_iterators.clear(); + self.indexed = false; + self.contains_indexed_global_functions = false; + } + /// Map a custom type to a friendly display name. /// /// # Example @@ -964,6 +986,10 @@ impl Module { .collect(); param_types.shrink_to_fit(); + let is_dynamic = param_types + .iter() + .any(|&type_id| type_id == TypeId::of::()); + #[cfg(feature = "metadata")] let (param_names, return_type_name) = { let mut names = _arg_names @@ -982,6 +1008,11 @@ impl Module { let hash_fn = calc_native_fn_hash(None, name.as_ref(), ¶m_types); + if is_dynamic { + self.dynamic_functions + .insert(calc_fn_hash(name.as_ref(), param_types.len())); + } + self.functions.insert( hash_fn, FuncInfo { @@ -1444,19 +1475,30 @@ impl Module { ) } - /// Get a Rust function. + /// Look up a Rust function by hash. /// /// The [`u64`] hash is returned by the [`set_native_fn`][Module::set_native_fn] call. #[inline] #[must_use] - pub(crate) fn get_fn(&self, hash_fn: u64) -> Option<&CallableFunction> { + pub(crate) fn get_fn(&self, hash_native: u64) -> Option<&CallableFunction> { if !self.functions.is_empty() { - self.functions.get(&hash_fn).map(|f| &f.func) + self.functions.get(&hash_native).map(|f| &f.func) } else { None } } + /// Does the particular function with [`Dynamic`] parameter(s) exist in the [`Module`]? + #[inline(always)] + #[must_use] + pub(crate) fn contains_dynamic_fn(&self, hash_script: u64) -> bool { + if !self.dynamic_functions.is_empty() { + self.dynamic_functions.contains(&hash_script) + } else { + false + } + } + /// Does the particular namespace-qualified function exist in the [`Module`]? /// /// The [`u64`] hash is calculated by [`build_index`][Module::build_index]. diff --git a/src/types/custom_types.rs b/src/types/custom_types.rs index 34759d1f..4331d545 100644 --- a/src/types/custom_types.rs +++ b/src/types/custom_types.rs @@ -25,6 +25,11 @@ impl CustomTypesCollection { pub fn new() -> Self { Self(BTreeMap::new()) } + /// Clear the [`CustomTypesCollection`]. + #[inline(always)] + pub fn clear(&mut self) { + self.0.clear(); + } /// Register a custom type. #[inline(always)] pub fn add(&mut self, type_name: impl Into, name: impl Into) {