fix(rt): honor a custom __index meta-method on fieldless userdata - #38
Merged
Merged
Conversation
create_userdata assembled the metatable in two phases: user meta-methods were set first, then the has_fields branch unconditionally overwrote __index with the method table when the type registered no fields. A custom __index registered via add_meta_method was therefore silently discarded, and dynamic-proxy userdata (e.g. JSON cursors resolving obj["key"] in Rust) returned nil. Route the fieldless branch through a dispatcher that calls the user's __index first and falls back to the method table when it returns nil. Applied to both create_userdata and the scoped variant.
- Extract one `install_index_dispatcher` used by both `create_userdata` and `create_scoped_userdata` (the two copies had drifted apart). - Cover the field-ful branch too: it overwrote a custom `__index` with the getter dispatcher, exactly like the fieldless branch did. - Use mlua's precedence (field getters -> method table -> custom `__index`) instead of resolving the custom `__index` first, so a method or field is not shadowed by the user handler. - Keep the fast path: with no fields and no custom `__index`, `__index` stays the method table itself. - Tests: field-ful, precedence, and scoped-userdata cases alongside the original fieldless one. All four fail without the src change. cargo fmt --all --check now passes.
Owner
|
Verified locally and pushed a follow-up commit to this branch (010cc47) — thanks, this is a real bug and the silent- What I changed on top of yours:
One note on the test evidence in the description: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
create_userdata(and the scoped variant) assemble the metatable in two phases:add_meta_methodare set on the metatable — including a custom__index.has_fieldsbranch then runs. When the type registered no fields, it unconditionally overwrites__indexwith the method table:A custom
__indexregistered throughadd_meta_method(MetaMethod::Index, ...)is silently discarded. The dynamic-proxy pattern —obj["key"]resolved in Rust against external data — returnsnilfor every key. mlua honors the custom handler in this situation. Because the failure is silent (nil, not an error), it is easy to miss.Fix
When no fields are registered but a custom
__indexexists, install a dispatcher that calls the user's__indexfirst and falls back to the method table when it returns nil. Applied to bothcreate_userdataandcreate_scoped_userdata.Field-ful userdata keeps the existing getter→method dispatcher unchanged.
Testing
test_custom_index_metamethod_without_fieldsinmlua_userdata.rs: a fieldless userdata with a custom__indexresolvesobj["key"], returnsnilfor missing keys, and keeps regular methods reachable.cargo test -p luaur-rt: 248 passed, 0 failed (the new test included; all existing userdata tests pass unchanged).Found while embedding luaur-rt in a game engine: a read-only JSON data cursor exposed to gameplay scripts relied on a custom
__indexand silently returned nil on every key.