Space Lua: Align .. (concatenation) operator (#1648)

* Make fallback accept only strings and numbers (as per standard Lua).

* Remove `luaToString` from the fallback. It fits well presentation
  purposes but is too complicated for strict semantic checking in the
  evaluator and also wrongly coersed non-string and non-number value.
  It may also return a promise but we require synchronous error path.

* Concatenating `nil` now throws "attempt to concatenate nil value" as
  per standard Lua.

* Other types throw "attempt to concatenate a non-string or non-number"
  which is simplified message diverting from standard Lua which throws
  typed error but we do not strictly need that for Space Lua (and can be
  easily added anytime in the future).

Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
This commit is contained in:
Matouš Jan Fialka
2025-10-22 10:41:26 +02:00
committed by GitHub
parent 4b9829c59c
commit cd6f4e9c58
2 changed files with 167 additions and 10 deletions
+146
View File
@@ -746,3 +746,149 @@ do
assertEqual(rawequal(nan, nan), false)
end
-- Some `..` (concatenation) tests
-- Strings and numbers
assertEqual("a" .. "b", "ab")
assertEqual(1 .. "b", "1b")
assertEqual("a" .. 2, "a2")
assertEqual(4 .. 2, "42")
assertEqual("123" .. 45, "12345")
-- Multi-return (only first value is used)
do
local function f()
return "X", "Y"
end
assertEqual("A" .. f(), "AX")
local function g()
return 1, 2
end
assertEqual(g() .. "X", "1X")
end
local function expect_concat_nil_error(lhs, rhs)
local ok, err = pcall(
function()
return lhs .. rhs
end
)
assertEqual(ok, false, "concat must error on nil")
end
local function expect_concat_type_error(lhs, rhs)
local ok, err = pcall(
function()
return lhs .. rhs
end
)
assertEqual(ok, false, "concat must error on non-string/non-number")
end
-- Nil on either side
expect_concat_nil_error(nil, "x")
expect_concat_nil_error("x", nil)
expect_concat_nil_error(nil, 1)
expect_concat_nil_error(1, nil)
expect_concat_nil_error(nil, nil)
-- Non-string and non-number must error
expect_concat_type_error(true, "x")
expect_concat_type_error("x", false)
expect_concat_type_error(true, true)
do
local t = {}
expect_concat_type_error(t, "x")
expect_concat_type_error("x", t)
expect_concat_type_error(t, t)
end
-- JS arrays and JS objects behave as tables (no concat)
do
local arr = js.window.JSON.parse("[1, 2]")
expect_concat_type_error(arr, "x")
expect_concat_type_error("x", arr)
expect_concat_type_error(arr, arr)
local obj = js.window.JSON.parse('{ "a": 1 }')
expect_concat_type_error(obj, "x")
expect_concat_type_error("x", obj)
expect_concat_type_error(obj, obj)
end
-- `__tostring` alone does not relax concat rules
do
local t = {}
setmetatable(
t,
{
__tostring = function(_)
return "T"
end
}
)
expect_concat_type_error(t, "x")
expect_concat_type_error("x", t)
expect_concat_type_error(t, t)
end
-- `__concat` metamethod cases
do
local L = {}
setmetatable(
L,
{
__concat = function(a, b)
return "LEFT"
end
}
)
assertEqual(L .. "x", "LEFT")
local R = {}
setmetatable(
R,
{
__concat = function(a, b)
return "RIGHT"
end
}
)
assertEqual("x" .. R, "RIGHT")
assertEqual(L .. R, "LEFT",
"left-side __concat must be preferred when both defined")
end
-- `__concat` result handling (only first value used)
do
local M = {}
setmetatable(
M,
{
__concat = function(a, b)
return "A", "B"
end
}
)
assertEqual(M .. "x", "A",
"__concat must use only first return value")
end