You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Lua.eval!/3 with decode: true (the default) returns tables as lists of {key, value} tuples with string/number keys. But that shape cannot be passed back into the VM: Lua.VM.Value.encode/3 has no clause for a {binary, term} (or {number, term}) 2-tuple, so round-tripping any decoded table crashes with a bare FunctionClauseError:
lua=Lua.new(){[decoded],lua}=Lua.eval!(lua,"return {x = 1}")# decoded == [{"x", 1}]Lua.set!(lua,[:t],decoded)# ** (FunctionClauseError) no function clause matching in Lua.VM.Value.encode/3
The decode boundary and the encode boundary are asymmetric: what one produces, the other refuses — and refuses with an internal crash rather than a clear error.
Why it happens
encode/3's list clause checks keyword_list?/1. A decoded table like [{"x", 1}] has binary keys, so it falls into the positional branch, which Enum.with_index/2s the list and tries to encode each element — and the element {"x", 1} is a 2-tuple that matches no encode/3 clause.
Options
Make decoded tables encodable (round-trip symmetry): add a clause/branch for lists of {k, v} pairs where k is a binary or number, encoding them as table entries. This makes decode → modify → set! work, which is a natural workflow.
Refuse clearly: raise Lua.RuntimeException with an actionable message (mirroring the struct-refusal clause and the cyclic-tref clause from Fix unbounded recursion on cyclic tables at the eval boundary #407), telling the caller to convert to a map first (Map.new(decoded) works today).
Note that option 1 has an ambiguity to resolve: a list of 2-tuples could in principle be a Lua sequence of pair-tables. Today the positional branch already can't represent that (it crashes), so treating pair-lists as table entries is not a behavior regression — but it should be documented.
Related oddity
keyword_list?/1 treats any atom-keyed 2-tuple list as a keyword list, so Lua.set!(lua, [:w], [{:tref, 12}]) silently encodes as the table {tref = 12}. That is arguably correct keyword-list semantics, but combined with #407 (where decode can leave a literal {:tref, id} inside decoded output at a cycle), a decoded-then-re-encoded value can silently change meaning instead of erroring. Worth considering while touching this code.
Context
Found while reviewing #407, which made encode/3 refuse a bare {:tref, _} with a clear error. The pair-tuple gap predates that PR and exists on main independently of cyclic tables.
The problem
Lua.eval!/3withdecode: true(the default) returns tables as lists of{key, value}tuples with string/number keys. But that shape cannot be passed back into the VM:Lua.VM.Value.encode/3has no clause for a{binary, term}(or{number, term}) 2-tuple, so round-tripping any decoded table crashes with a bareFunctionClauseError:The decode boundary and the encode boundary are asymmetric: what one produces, the other refuses — and refuses with an internal crash rather than a clear error.
Why it happens
encode/3's list clause checkskeyword_list?/1. A decoded table like[{"x", 1}]has binary keys, so it falls into the positional branch, whichEnum.with_index/2s the list and tries to encode each element — and the element{"x", 1}is a 2-tuple that matches noencode/3clause.Options
{k, v}pairs wherekis a binary or number, encoding them as table entries. This makesdecode → modify → set!work, which is a natural workflow.Lua.RuntimeExceptionwith an actionable message (mirroring the struct-refusal clause and the cyclic-tref clause from Fix unbounded recursion on cyclic tables at the eval boundary #407), telling the caller to convert to a map first (Map.new(decoded)works today).Note that option 1 has an ambiguity to resolve: a list of 2-tuples could in principle be a Lua sequence of pair-tables. Today the positional branch already can't represent that (it crashes), so treating pair-lists as table entries is not a behavior regression — but it should be documented.
Related oddity
keyword_list?/1treats any atom-keyed 2-tuple list as a keyword list, soLua.set!(lua, [:w], [{:tref, 12}])silently encodes as the table{tref = 12}. That is arguably correct keyword-list semantics, but combined with #407 (wheredecodecan leave a literal{:tref, id}inside decoded output at a cycle), a decoded-then-re-encoded value can silently change meaning instead of erroring. Worth considering while touching this code.Context
Found while reviewing #407, which made
encode/3refuse a bare{:tref, _}with a clear error. The pair-tuple gap predates that PR and exists onmainindependently of cyclic tables.