diff --git a/CHANGELOG.md b/CHANGELOG.md index 678524e8..3f5f6319 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ Rhai Release Notes ================== +Version 1.7.0 +============= + +Bug fixes +--------- + +* Functions with `Dynamic` parameters now work in qualified calls from `import`ed modules. + + Version 1.6.0 ============= diff --git a/src/func/call.rs b/src/func/call.rs index 3b256349..dbe502d2 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -1301,13 +1301,14 @@ impl Engine { } } + // Search for the root namespace let module = self .search_imports(global, state, namespace) .ok_or_else(|| ERR::ErrorModuleNotFound(namespace.to_string(), namespace.position()))?; - // First search in script-defined functions (can override built-in) - let func = match module.get_qualified_fn(hash) { - // Then search in Rust functions + // First search script-defined functions in namespace (can override built-in) + let mut func = match module.get_qualified_fn(hash) { + // Then search native Rust functions None => { #[cfg(not(feature = "unchecked"))] self.inc_operations(&mut global.num_operations, pos)?; @@ -1320,6 +1321,41 @@ impl Engine { r => r, }; + // Check for `Dynamic` parameters. + // + // Note - This is done during every function call mismatch without cache, + // so hopefully the number of arguments should not be too many + // (expected because closures cannot be qualified). + if func.is_none() && !args.is_empty() { + let num_args = args.len(); + let max_bitmask = 1usize << usize::min(num_args, MAX_DYNAMIC_PARAMETERS); + let mut bitmask = 1usize; // Bitmask of which parameter to replace with `Dynamic` + + // Try all permutations with `Dynamic` wildcards + while bitmask < max_bitmask { + let hash_params = calc_fn_params_hash(args.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() + } + })); + let hash_qualified_fn = combine_hashes(hash, hash_params); + + #[cfg(not(feature = "unchecked"))] + self.inc_operations(&mut global.num_operations, pos)?; + + if let Some(f) = module.get_qualified_fn(hash_qualified_fn) { + func = Some(f); + break; + } + + bitmask += 1; + } + } + // Clone first argument if the function is not a method after-all if !func.map(|f| f.is_method()).unwrap_or(true) { if let Some(first) = first_arg_value { diff --git a/tests/modules.rs b/tests/modules.rs index 2b952a8d..4394d893 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -562,3 +562,29 @@ fn test_module_environ() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_module_dynamic() -> Result<(), Box> { + fn test_fn(input: Dynamic, x: INT) -> Result> { + let s = input.into_string().unwrap(); + Ok(s.len() as INT + x) + } + + let mut engine = rhai::Engine::new(); + let mut module = Module::new(); + module.set_native_fn("test", test_fn); + + let mut static_modules = rhai::module_resolvers::StaticModuleResolver::new(); + static_modules.insert("test", module); + engine.set_module_resolver(static_modules); + engine.register_result_fn("test2", test_fn); + + assert_eq!(engine.eval::(r#"test2("test", 38);"#)?, 42); + + assert_eq!( + engine.eval::(r#"import "test" as test; test::test("test", 38);"#)?, + 42 + ); + + Ok(()) +}