[Space Lua] tostring() respects __tostring metamethod with live stack frame

`tostring()` was delegating to `luaToString()` which calls `__tostring`
with a dead stack frame, breaking metamethods that call back into
globals. Now `tostringFunction` receives the live stack frame, looks up
`__tostring` via `rawGet`, calls it through `luaCall` with the live
frame, and validates the return is a string.

Also add tests covering the basic case, the nested-call case, and the
error path for a non-string return.

Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
This commit is contained in:
Matouš Jan Fialka
2026-02-20 07:59:16 +01:00
parent d2eb8fe6ec
commit 8cecd0985c
2 changed files with 59 additions and 3 deletions
+27
View File
@@ -25,3 +25,30 @@ assertEqual(some(" \n"), nil)
assertEqual(some({}), nil)
assertEqual(some({"baz"})[1], "baz") -- compare an element to ensure passthrough
assertEqual(some({foo="bar"})["foo"], "bar")
-- tostring: primitives
assertEqual(tostring(nil), "nil")
assertEqual(tostring(true), "true")
assertEqual(tostring(false), "false")
assertEqual(tostring(42), "42")
assertEqual(tostring(3.14), "3.14")
-- tostring: `__tostring` metamethod is called with the live SF
local mt = { __tostring = function(t) return "custom:" .. t.name end }
local obj = setmetatable({ name = "lua" }, mt)
assertEqual(tostring(obj), "custom:lua")
-- tostring: `__tostring` returning non-string must error
local bad_mt = { __tostring = function(_) return 99 end }
local bad_obj = setmetatable({}, bad_mt)
local ok, err = pcall(tostring, bad_obj)
assertEqual(ok, false)
-- tostring: `__tostring` on a nested call (SF propagation via `luaCall`)
local inner_mt = {
__tostring = function(t)
return "inner:" .. tostring(t.val)
end
}
local inner = setmetatable({ val = 7 }, inner_mt)
assertEqual(tostring(inner), "inner:7")