Files
plainleaf/client/space_lua/truthiness_test.lua
T
Matouš Jan FialkaandGitHub b363bd7e59 Fix serious truthiness evaluation flaws. (#1644)
In Lua, only the last expression in an argument list preserves multiple
results and all earlier expressions yield exactly one value (their first).

For example:

```
f(a(), b(), c())
```

where

```
a -> (1,2)
b -> (3,4)
c -> (5,6)
```

becomes

```
f(1, 3, 5, 6)
```

which matches Lua behaviour (tested in version 5.4).

Changes:

* Function argument lists keep multiple results only from the last
  argument.

* Fixes failing truthiness tests (`true and ret_one_two()`).

Sidenote: the `truthiness_test.lua` was not called at all because of
my obvious copy/paste typo (the `arithmetical_test.lua` was called twice
instead; now fixed too and both test suites pass ok).
2025-10-20 17:34:45 +02:00

255 lines
5.6 KiB
Lua

local function assert_eq(actual, expected, msg)
if actual ~= expected then
error('assert_eq failed: ' .. msg)
end
end
local function assert_true(cond, msg)
if not cond then
error('assert_true failed: ' .. msg)
end
end
local function assert_false(cond, msg)
if cond then
error('assert_false failed: ' .. msg)
end
end
-- Helpers for multi-return tests
local function ret_nil_one()
return nil, 1
end
local function ret_false_one()
return false, 1
end
local function ret_zero_one()
return 0, 1
end
local function ret_emptystr_one()
return "", 1
end
local function ret_tbl_one()
return {}, 1
end
local function ret_one_two()
return 1, 2
end
-- 1. Basic boolean/nil
assert_false(false, "false must be falsey")
assert_true(true, "true must be truthy")
assert_false(nil, "nil must be falsey")
-- 2. Numbers: all numbers are truthy
assert_true(0, "0 must be truthy")
assert_true(1, "1 must be truthy")
assert_true(-1, "-1 must be truthy")
assert_true(0.0, "0.0 must be truthy")
assert_true(-0.0, "-0.0 must be truthy")
-- 3. Strings: empty and non-empty strings are truthy
assert_true("", "empty string must be truthy")
assert_true("0", '"0" must be truthy')
assert_true("a", '"a" must be truthy')
-- 4. Tables: empty and non-empty tables are truthy
assert_true({}, "empty table must be truthy")
assert_true({1}, "non-empty table must be truthy")
-- 5. Functions are truthy
local fn = function() end
assert_true(fn, "function must be truthy")
assert_true(function() end, "function must be truthy")
-- 6. Multi-return: only the first value determines truthiness
if ret_nil_one() then
error("ret_nil_one() must be falsey (first result nil)")
end
if ret_false_one() then
error("ret_false_one() must be falsey (first result false)")
end
if not ret_zero_one() then
error("ret_zero_one() must be truthy (first result 0)")
end
if not ret_emptystr_one() then
error("ret_emptystr_one() must be truthy (first result empty string)")
end
if not ret_tbl_one() then
error("ret_tbl_one() must be truthy (first result table)")
end
-- 7. `and`/`or` short-circuiting and value returns
-- 7.1. `and` returns first falsey value, otherwise last value
assert_eq((true and 2), 2, "and: true and 2 -> 2")
assert_eq((false and 2), false, "and: false and 2 -> false")
assert_eq((nil and 2), nil, "and: nil and 2 -> nil")
assert_eq((0 and "x"), "x", "and: 0 and 'x' -> 'x' (0 is truthy)")
-- 7.2. `or` returns first truthy value, otherwise last value
assert_eq((false or 3), 3, "or: false or 3 -> 3")
assert_eq((nil or 3), 3, "or: nil or 3 -> 3")
assert_eq((0 or 3), 0, "or: 0 or 3 -> 0 (0 is truthy)")
assert_eq(("" or "x"), "", "or: '' or 'x' -> '' ('' is truthy)")
-- 7.3. Multi-return: operand evaluation yields first value
assert_eq((true and ret_one_two()), 1,
"and: true and (1,2) yields first result 1")
assert_eq((false or ret_one_two()), 1,
"or: false or (1,2) yields first result 1")
-- 8. Litmus tests involving tables and `and`/`or`
-- {} and 2 or 3 -> 2
-- {} or 2 and 3 -> {} (because {} is truthy, or returns left operand)
local t = {}
assert_eq((t and 2 or 3), 2, "{} and 2 or 3 must be 2")
-- `v` should be the same table reference `t`
local v = (t or 2 and 3)
assert_true(type(v) == "table",
"{} or 2 and 3 must return the table (truthy left operand)")
-- changing `t` reflects in `v` (same reference)
t.key = "ok"
assert_eq(v.key, "ok",
"{} or 2 and 3 returns original table reference")
-- 9. `if`/`while` with truthiness
local ran_if_truthy = false
if {} then
ran_if_truthy = true
end
assert_true(ran_if_truthy, "if {} must run branch")
local ran_if_falsey = false
if nil then
ran_if_falsey = true
end
assert_false(ran_if_falsey, "if nil must not run branch")
-- `while` must stop only on `false`/`nil`
local n = 0
local function next_or_nil()
n = n + 1
if n <= 2 then
return 0
-- 0 truthy -> loop two times
end
return nil -- stop
end
local count = 0
while next_or_nil() do
count = count + 1
end
assert_eq(count, 2,
"while should loop while condition is truthy")
-- 10. Short-circuit evaluation (no RHS evaluation when not needed)
local side = 0
local function bump()
side = side + 1
return true
end
-- For `true or bump()` RHS must not be evaluated
local s0 = side
local _ = (true or bump())
assert_eq(side, s0, "or short-circuit must avoid RHS when LHS truthy")
-- For `false and bump()` RHS must not be evaluated
local _ = (false and bump())
assert_eq(side, s0, "and short-circuit must avoid RHS when LHS falsey")
-- 11. Multi-return short-circuit
local function falsy_pair()
return nil, "x"
end
local function truthy_pair()
return 1, "x"
end
-- `or`: pick RHS only when LHS falsey (first result)
local s1 = (falsy_pair() or 5)
assert_eq(s1, 5, "or should see falsy first result and evaluate RHS")
local s2 = (truthy_pair() or 5)
assert_eq(s2, 1, "or should return first result of LHS when truthy")
-- 12. Only the last expression expands
local function ret() return "A", "B" end
local function a() return "A1", "A2" end
local function b() return "B1", "B2" end
local function c() return "C1", "C2" end
assert_eq(string.format("%s-%s", ret()), "A-B",
"last-only: single arg expands")
assert_eq(string.format("%s-%s", ret(), "Z"), "A-Z",
"last-only: earlier arg is single")
assert_eq(string.format("%s-%s-%s-%s", a(), b(), c()), "A1-B1-C1-C2",
"last-only: only last expands, earlier args single")
-- 13. Only false and `nil` are falsey
assert_false(not 0, "not 0 must be false")
assert_false(not -0.0, "not -0.0 must be false")
assert_false(not "", "not '' must be false")
assert_false(not {}, "not {} must be false")
assert_true(not nil, "not nil must be true")
assert_true(not false, "not false must be true")