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).
This commit is contained in:
Matouš Jan Fialka
2025-10-20 17:34:45 +02:00
committed by GitHub
parent b7870f36e7
commit b363bd7e59
4 changed files with 61 additions and 13 deletions
+33 -11
View File
@@ -268,7 +268,7 @@ export function evalExpression(
return +singleResult(value);
}
case "not": {
return !singleResult(value);
return !luaTruthy(singleResult(value));
}
case "~": {
const arg = singleResult(value);
@@ -307,7 +307,7 @@ export function evalExpression(
return +singleResult(value);
}
case "not": {
return !singleResult(value);
return !luaTruthy(singleResult(value));
}
case "~": {
const arg = singleResult(value);
@@ -493,7 +493,7 @@ function evalPrefixExpression(
}
case "FunctionCall": {
const prefixValue = evalPrefixExpression(e.prefix, env, sf);
if (!prefixValue) {
if (prefixValue === null || prefixValue === undefined) {
throw new LuaRuntimeError(
`Attempting to call nil as a function`,
sf.withCtx(e.prefix.ctx),
@@ -604,14 +604,16 @@ function evalLogical(
const decide = (lv: any) => {
if (op === "or") {
if (luaTruthy(lv)) {
return lv;
return singleResult(lv);
}
return evalExpression(rightExpr, env, sf);
const rv = evalExpression(rightExpr, env, sf);
return rv instanceof Promise ? rv.then(singleResult) : singleResult(rv);
} else {
if (!luaTruthy(lv)) {
return lv;
return singleResult(lv);
}
return evalExpression(rightExpr, env, sf);
const rv = evalExpression(rightExpr, env, sf);
return rv instanceof Promise ? rv.then(singleResult) : singleResult(rv);
}
};
@@ -901,12 +903,32 @@ function evalExpressions(
const argsVal = evalPromiseValues(
es.map((arg) => evalExpression(arg, env, sf)),
);
// In Lua multi-returns propagate only in tail position of an expression
// list.
const finalize = (argsResolved: any[]) => {
if (argsResolved.length === 0) {
return [];
}
const out: LuaValue[] = [];
// All but last expression produce a single value
for (let i = 0; i < argsResolved.length - 1; i++) {
out.push(singleResult(argsResolved[i]));
}
// Last expression preserves multiple results
const last = argsResolved[argsResolved.length - 1];
if (last instanceof LuaMultiRes) {
out.push(...last.flatten().values);
} else {
out.push(singleResult(last));
}
return out;
};
if (argsVal instanceof Promise) {
return argsVal.then((argsResolved) =>
new LuaMultiRes(argsResolved).flatten().values
);
return argsVal.then(finalize);
} else {
return new LuaMultiRes(argsVal).flatten().values;
return finalize(argsVal as any[]);
}
}
+1 -1
View File
@@ -10,7 +10,7 @@ Deno.test("[Lua] Core language", async () => {
});
Deno.test("[Lua] Core language (truthiness)", async () => {
await runLuaTest("./arithmetic_test.lua");
await runLuaTest("./truthiness_test.lua");
});
Deno.test("[Lua] Core language (arithmetic)", async () => {
+1 -1
View File
@@ -1,6 +1,6 @@
import {
LuaBuiltinFunction,
LuaEnv,
type LuaEnv,
LuaMultiRes,
type LuaStackFrame,
type LuaValue,
+26
View File
@@ -226,3 +226,29 @@ 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")