Skip to content

Commit 7697025

Browse files
committed
fix: wrap nil error objects on lua 5.5 to preserve throw undefined across pcall
1 parent edb829e commit 7697025

3 files changed

Lines changed: 41 additions & 4 deletions

File tree

src/LuaLib.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export enum LuaLibFeature {
5151
DescriptorGet = "DescriptorGet",
5252
DescriptorSet = "DescriptorSet",
5353
Error = "Error",
54+
ErrorObject = "ErrorObject",
5455
FunctionBind = "FunctionBind",
5556
Generator = "Generator",
5657
InstanceOf = "InstanceOf",

src/lualib/ErrorObject.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Lua 5.5 replaces a `nil` error object with a string ("<no error object>")
2+
// when an error propagates out of a protected call. This breaks `throw undefined`
3+
// round-tripping through `pcall`. To preserve the original value, we route
4+
// protected calls through `xpcall` with a message handler that wraps nil into
5+
// this sentinel table (Lua 5.5 only mangles nil, not other values), and unwrap
6+
// on the catch side.
7+
const ____TS__NilErrorObject: any = {};
8+
9+
export function __TS__WrapErrorObject(this: void, value: any): any {
10+
if (value === undefined) return ____TS__NilErrorObject;
11+
return value;
12+
}
13+
14+
export function __TS__UnwrapErrorObject(this: void, value: any): any {
15+
if (value === ____TS__NilErrorObject) return undefined;
16+
return value;
17+
}

src/transformation/visitors/errors.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as lua from "../../LuaAST";
44
import { FunctionVisitor, TransformationContext } from "../context";
55
import { unsupportedForTarget, unsupportedForTargetButOverrideAvailable } from "../utils/diagnostics";
66
import { createUnpackCall } from "../utils/lua-ast";
7-
import { transformLuaLibFunction } from "../utils/lualib";
7+
import { importLuaLibFeature, transformLuaLibFunction } from "../utils/lualib";
88
import { findScope, LoopContinued, Scope, ScopeType } from "../utils/scope";
99
import { isInAsyncFunction, isInGeneratorFunction } from "../utils/typescript";
1010
import { wrapInAsyncAwaiter } from "./async-await";
@@ -171,8 +171,22 @@ export const transformTryStatement: FunctionVisitor<ts.TryStatement> = (statemen
171171
const returnedIdentifier = lua.createIdentifier("____hasReturned");
172172
let returnCondition: lua.Expression | undefined;
173173

174-
const pCall = lua.createIdentifier("pcall");
175-
const tryCall = lua.createCallExpression(pCall, [lua.createFunctionExpression(tryBlock)]);
174+
// On Lua 5.5 (and Universal, which must work on 5.5), a `nil` error object
175+
// is replaced by a string when it propagates out of a protected call. To
176+
// preserve `throw undefined` semantics, route through xpcall with a message
177+
// handler that wraps nil into a sentinel, and unwrap before the user catch.
178+
const wrapErrorObjects =
179+
context.options.luaTarget === LuaTarget.Lua55 || context.options.luaTarget === LuaTarget.Universal;
180+
if (wrapErrorObjects) {
181+
importLuaLibFeature(context, LuaLibFeature.ErrorObject);
182+
}
183+
184+
const tryCall = wrapErrorObjects
185+
? lua.createCallExpression(lua.createIdentifier("xpcall"), [
186+
lua.createFunctionExpression(tryBlock),
187+
lua.createIdentifier("__TS__WrapErrorObject"),
188+
])
189+
: lua.createCallExpression(lua.createIdentifier("pcall"), [lua.createFunctionExpression(tryBlock)]);
176190

177191
if (statement.catchClause && statement.catchClause.block.statements.length > 0) {
178192
// try with catch
@@ -192,9 +206,14 @@ export const transformTryStatement: FunctionVisitor<ts.TryStatement> = (statemen
192206
}
193207
result.push(lua.createVariableDeclarationStatement(tryReturnIdentifiers, tryCall));
194208

209+
const catchArg = wrapErrorObjects
210+
? lua.createCallExpression(lua.createIdentifier("__TS__UnwrapErrorObject"), [
211+
lua.cloneIdentifier(returnedIdentifier),
212+
])
213+
: lua.cloneIdentifier(returnedIdentifier);
195214
const catchCall = lua.createCallExpression(
196215
catchIdentifier,
197-
statement.catchClause.variableDeclaration ? [lua.cloneIdentifier(returnedIdentifier)] : []
216+
statement.catchClause.variableDeclaration ? [catchArg] : []
198217
);
199218
const catchCallStatement = hasReturn
200219
? lua.createAssignmentStatement(

0 commit comments

Comments
 (0)