types, vm, lib: add metamethod support for prototyped values - #434
Open
jow- wants to merge 1 commit into
Open
Conversation
Add __get__, __set__, __delete__, __call__ and __tostring__ metamethods
for values carrying a prototype chain. Metamethods are resolved at
runtime from the contents of that chain, no new bytecode and no new VM
API are involved.
The semantics follow Lua's __index / __newindex convention:
__get__(key) fires on a read which neither the value nor its
prototype chain answers; returning a table delegates
the lookup to that table
__set__(key, val) fires on writing a key an object does not have yet;
its return value is discarded, an assignment always
evaluates to the assigned value
__delete__(key) fires on deleting a key an object does not have yet;
a truthy result means "deleted"
__call__(...) makes the value invocable, through uc_vm_call() and
ucv_is_callable() alike
__tostring__ generalizes the existing "tostring" convention, the
legacy name stays valid as an alias
Own keys and numeric array indices always win, metamethods are the
fallback: object literals store their members without dispatching, and
arrays keep their dense storage, so a key which is a valid index never
dispatches. The `in` operator walks the prototype chain for keys but
does not dispatch, a purely virtual property stays hidden.
ucv_metamethod_lookup() walks the prototype chain starting at the
direct prototype and accepts a function only: an own property with a
dunder name is plain data, so ordinary property writes cannot
accidentally install a metamethod, and a value holding itself as
__call__ cannot report itself as its own metamethod and recurse
through C. No other re-entrancy guard exists: a metamethod re-entering
the operation it customizes runs into the VM's call depth limit, which
raises "Too much recursion".
The dispatch lives in one place, ucv_key_get(), ucv_key_set() and
ucv_key_delete() in types.c, called directly by the instruction handlers
for property loads, stores, updates and deletes. Their shared call helper
pushes receiver, metamethod, key and value on the VM stack, keeping them
reachable for a garbage collection running inside the metamethod, and
restores the stack when the call raises. An exception raised by a
metamethod propagates to the interpreter loop; uc_vm_insn_update_val()
saves the arithmetic opcode before dispatching and skips the write half
when the read half raised. ucv_object_add() and ucv_array_set() take
ownership of the value stored but leave it with the caller where they
reject it, which the raw helpers account for so that a rejected store
does not leak.
rawget(), rawset() and rawdelete() reach the storage below a metamethod
and are exposed to scripts, and as ucv_key_rawget(), ucv_key_rawset()
and ucv_key_rawdelete() to C modules.
Add tests/custom/06_metamethods covering the five metamethods, own key
and index precedence, the raw accessors, exception propagation and the
recursion limit, and document the feature as the official
docs/tutorials/06-metamethods.md article.
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.
Add get, set, delete, call and tostring metamethods for values carrying a prototype chain. Metamethods are resolved at runtime from the contents of that chain, no new bytecode and no new VM API are involved.
The semantics follow Lua's __index / __newindex convention:
get(key) fires on a read which neither the value nor its
prototype chain answers; returning a table delegates
the lookup to that table
set(key, val) fires on writing a key an object does not have yet;
its return value is discarded, an assignment always
evaluates to the assigned value
delete(key) fires on deleting a key an object does not have yet;
a truthy result means "deleted"
call(...) makes the value invocable, through uc_vm_call() and
ucv_is_callable() alike
tostring generalizes the existing "tostring" convention, the
legacy name stays valid as an alias
Own keys and numeric array indices always win, metamethods are the fallback: object literals store their members without dispatching, and arrays keep their dense storage, so a key which is a valid index never dispatches. The
inoperator walks the prototype chain for keys but does not dispatch, a purely virtual property stays hidden.ucv_metamethod_lookup() walks the prototype chain starting at the direct prototype and accepts a function only: an own property with a dunder name is plain data, so ordinary property writes cannot accidentally install a metamethod, and a value holding itself as call cannot report itself as its own metamethod and recurse through C. No other re-entrancy guard exists: a metamethod re-entering the operation it customizes runs into the VM's call depth limit, which raises "Too much recursion".
The dispatch lives in one place, ucv_key_get(), ucv_key_set() and ucv_key_delete() in types.c, called directly by the instruction handlers for property loads, stores, updates and deletes. Their shared call helper pushes receiver, metamethod, key and value on the VM stack, keeping them reachable for a garbage collection running inside the metamethod, and restores the stack when the call raises. An exception raised by a metamethod propagates to the interpreter loop; uc_vm_insn_update_val() saves the arithmetic opcode before dispatching and skips the write half when the read half raised. ucv_object_add() and ucv_array_set() take ownership of the value stored but leave it with the caller where they reject it, which the raw helpers account for so that a rejected store does not leak.
rawget(), rawset() and rawdelete() reach the storage below a metamethod and are exposed to scripts, and as ucv_key_rawget(), ucv_key_rawset() and ucv_key_rawdelete() to C modules.
Add tests/custom/06_metamethods covering the five metamethods, own key and index precedence, the raw accessors, exception propagation and the recursion limit, and document the feature as the official docs/tutorials/06-metamethods.md article.