feat: add on_missing_method callback hook (internals)#1067
feat: add on_missing_method callback hook (internals)#1067schungx merged 9 commits intorhaiscript:mainfrom
Conversation
Add Engine::on_missing_method() which is called when a method call fails to resolve. The callback can return Ok(Some(value)) to provide a result, Ok(None) to fall through to ErrorFunctionNotFound, or Err() to propagate a custom error. Modeled after on_map_missing_property and on_invalid_array_index. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Great addition. However, it seems to work on all functions, not just method calls. So perhaps In which case, you'd need parameters in the callback to distinguish between two cases:
And you have to search for all the places where function resolution is performed (via the |
|
Just checking on the status of this PR. There are a number of CI failing also. And pls check my review items. Thanks! |
…hod_call param - Rename callback from on_missing_method to on_missing_function since it handles both method-style and function-style calls - Move hook from exec_native_fn_call up to exec_fn_call where scope and is_method_call flag are available - Add is_method_call: bool parameter to the callback so callers can distinguish method calls (obj.method()) from function calls (func()) - Pass real scope instead of Scope::new() - Align doc comments with on_missing_map_property style - Document that qualified calls (module::function()) are not covered, leaving room for a future on_missing_qualified_function if needed - Add #[deprecated] volatile marker consistent with other internals APIs - Add test for is_method_call flag Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Hi @schungx, thanks for the review and sorry for the delay. I've addressed all your feedback: Renamed to Added Fn(&str, &mut [&mut Dynamic], bool, EvalContext) -> RhaiResultOf<Option<Dynamic>>Moved the hook from Qualified calls ( Doc comments now follow the Tests renamed to The CI runs need your approval to start (fork contributor restriction). Could you approve them? Happy to fix anything they flag. |
|
BTW the new tests... do they need to be in a new file? Or can you just put them under |
|
Some CI have failed... You may also need to do a |
- Add #[cfg(not(feature = "no_object"))] to all tests using dot notation since method-call syntax is unavailable with no_object - Use rhai::INT instead of i64 for only_i32 compatibility - Use INT in register_fn signatures to match the engine's integer type Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
CI fixes pushed:
Should be ready for another CI run when you get a chance to approve. Thanks! |
|
Final question: are you sure this needs to require
This one doesn't involve any |
|
There still seems to be failed tests under Incidentally, we'd need to decide whether to omit the |
Move all on_missing_function callback tests from the separate on_missing_function.rs file into the existing functions.rs test file, as suggested in PR review. Each test is gated with #[cfg(feature = "internals")] and #[cfg(not(feature = "no_object"))]. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Thanks @schungx, I've addressed all three points: Tests moved — done in the latest commit (8db04c9). All
|
|
Agree on all three counts. Let's leave it as it is. But shouldn't there also be tests even under |
|
Other than the formatting, it is failing on the example in the doc comments: let result = engine.eval::<String>(r#"let x = 42; x.greet()"#)?;Need to gate it under Or put in two versions: /// # #[cfg(not(feature = "no_object"))]
/// let result = engine.eval::<String>(r#"let x = 42; x.greet()"#)?;
/// # #[cfg(eature = "no_object")]
/// let result = engine.eval::<String>(r#"let x = 42; greet(x)"#)?;
|
The doc example for on_missing_function uses x.greet() which requires dot notation unavailable under no_object. Add cfg gates to provide alternative function-style call for no_object builds. Also add #[allow(deprecated)] since the function carries a volatile API marker. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
I have a hunch that it may fail because it is not a method call: let result = engine.eval::<String>(r#"greet(42)"#)?;EDIT: Yup, it does. I suggest putting the Or you'd need to do: if (cfg!("feature = "no_object") || is_method_call) && name == "greet" { |
|
Yay! Merge! |
|
Thanks Stephen! Really appreciate the thorough review and the quick merge. Rhai is a very important cornerstone in an object store database I'm building, so having |
Summary
Add an
on_missing_methodcallback to theEngine, gated behind theinternalsfeature. When a method call fails to resolve to any registered function, this callback is invoked before raisingErrorFunctionNotFound, giving embedders a chance to handle the call dynamically.This follows the same pattern as the existing
on_missing_map_propertycallback.Motivation
When embedding Rhai in systems with dynamic method dispatch (e.g., object-oriented stores where methods are defined at runtime), there's currently no way to intercept failed method calls. The only option is to register every possible method upfront, which isn't feasible when methods are user-defined and change at runtime.
With
on_missing_method, the host application can:API
Changes
src/api/events.rs—Engine::on_missing_method()registration methodsrc/engine.rs—missing_methodfield onEnginesrc/func/native.rs—OnMissingMethodCallbacktype alias (sync/non-sync variants)src/func/call.rs— Invoke callback beforeErrorFunctionNotFoundin method dispatchtests/on_missing_method.rs— 7 tests covering basic usage, fallthrough, argument passing, existing method priority, error propagation, custom types, and multiple aritiesTest plan
All 7 new tests pass. Full test suite passes with
--features internals.