Skip to content

Commit 46fdc43

Browse files
committed
allow throwing arbitrary types and add builtin error classes
1 parent 5a19a37 commit 46fdc43

6 files changed

Lines changed: 195 additions & 18 deletions

File tree

src/LuaLib.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export enum LuaLibFeature {
2525
ClassIndex = "ClassIndex",
2626
ClassNewIndex = "ClassNewIndex",
2727
Decorate = "Decorate",
28+
Error = "Error",
2829
FunctionApply = "FunctionApply",
2930
FunctionBind = "FunctionBind",
3031
FunctionCall = "FunctionCall",

src/LuaTransformer.ts

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2843,19 +2843,14 @@ export class LuaTransformer {
28432843
}
28442844

28452845
public transformThrowStatement(statement: ts.ThrowStatement): StatementVisitResult {
2846+
const error = tstl.createIdentifier("error");
28462847
if (statement.expression === undefined) {
2847-
throw TSTLErrors.InvalidThrowExpression(statement);
2848-
}
2849-
2850-
const type = this.checker.getTypeAtLocation(statement.expression);
2851-
if (tsHelper.isStringType(type, this.checker, this.program)) {
2852-
const error = tstl.createIdentifier("error");
2848+
return tstl.createExpressionStatement(tstl.createCallExpression(error, []), statement);
2849+
} else {
28532850
return tstl.createExpressionStatement(
28542851
tstl.createCallExpression(error, [this.transformExpression(statement.expression)]),
28552852
statement
28562853
);
2857-
} else {
2858-
throw TSTLErrors.InvalidThrowExpression(statement.expression);
28592854
}
28602855
}
28612856

@@ -4208,6 +4203,7 @@ export class LuaTransformer {
42084203

42094204
const expressionType = this.checker.getTypeAtLocation(expression.expression);
42104205
if (tsHelper.isStandardLibraryType(expressionType, undefined, this.program)) {
4206+
this.checkForLuaLibType(expressionType);
42114207
const result = this.transformGlobalFunctionCall(expression);
42124208
if (result) {
42134209
return result;
@@ -5517,6 +5513,42 @@ export class LuaTransformer {
55175513
case "WeakSet":
55185514
this.importLuaLibFeature(LuaLibFeature.WeakSet);
55195515
return;
5516+
case "Error":
5517+
this.importLuaLibFeature(LuaLibFeature.Error);
5518+
return;
5519+
case "ErrorConstructor":
5520+
this.importLuaLibFeature(LuaLibFeature.Error);
5521+
return;
5522+
case "RangeError":
5523+
this.importLuaLibFeature(LuaLibFeature.Error);
5524+
return;
5525+
case "RangeErrorConstructor":
5526+
this.importLuaLibFeature(LuaLibFeature.Error);
5527+
return;
5528+
case "ReferenceError":
5529+
this.importLuaLibFeature(LuaLibFeature.Error);
5530+
return;
5531+
case "ReferenceErrorConstructor":
5532+
this.importLuaLibFeature(LuaLibFeature.Error);
5533+
return;
5534+
case "SyntaxError":
5535+
this.importLuaLibFeature(LuaLibFeature.Error);
5536+
return;
5537+
case "SyntaxErrorConstructor":
5538+
this.importLuaLibFeature(LuaLibFeature.Error);
5539+
return;
5540+
case "TypeError":
5541+
this.importLuaLibFeature(LuaLibFeature.Error);
5542+
return;
5543+
case "TypeErrorConstructor":
5544+
this.importLuaLibFeature(LuaLibFeature.Error);
5545+
return;
5546+
case "URIError":
5547+
this.importLuaLibFeature(LuaLibFeature.Error);
5548+
return;
5549+
case "URIErrorConstructor":
5550+
this.importLuaLibFeature(LuaLibFeature.Error);
5551+
return;
55205552
}
55215553
}
55225554
}

src/TSTLErrors.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,6 @@ export const InvalidPropertyCall = (node: ts.Node) =>
5454
export const InvalidElementCall = (node: ts.Node) =>
5555
new TranspileError(`Tried to transpile a non-element call as an element call.`, node);
5656

57-
export const InvalidThrowExpression = (node: ts.Node) =>
58-
new TranspileError(`Invalid throw expression, only strings can be thrown.`, node);
59-
6057
export const ForbiddenStaticClassPropertyName = (node: ts.Node, name: string) =>
6158
new TranspileError(`Cannot use "${name}" as a static class property or method name.`, node);
6259

src/lualib/Error.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
Error = class {
2+
public static captureStackTrace(targetObject: {}, depth: number): void {
3+
targetObject["stack"] = debug.traceback(undefined, depth);
4+
}
5+
6+
public message: string;
7+
public name: string;
8+
public stack?: string;
9+
10+
constructor(message?: string, name?: string) {
11+
this.message = message || "";
12+
this.name = name || "Error";
13+
Error["captureStackTrace"](this, name ? 5 : 4);
14+
}
15+
16+
public __tostring(): string {
17+
return `${this.name}: ${this.message}`;
18+
}
19+
} as any;
20+
21+
setmetatable(Error, { __call: (_self: any, message: string) => new Error(message) });
22+
23+
/** Standard error types */
24+
25+
RangeError = class extends Error {
26+
constructor(message?: string) {
27+
// @ts-ignore
28+
super(message, "RangeError");
29+
}
30+
public __tostring(): string {
31+
return `${this.name}: ${this.message}`;
32+
}
33+
} as any;
34+
setmetatable(RangeError, {
35+
__call: (_self: any, message: string) => new RangeError(message),
36+
__index: getmetatable(RangeError),
37+
});
38+
39+
ReferenceError = class extends Error {
40+
constructor(message?: string) {
41+
// @ts-ignore
42+
super(message, "ReferenceError");
43+
}
44+
public __tostring(): string {
45+
return `${this.name}: ${this.message}`;
46+
}
47+
} as any;
48+
setmetatable(ReferenceError, {
49+
__call: (_self: any, message: string) => new ReferenceError(message),
50+
__index: getmetatable(ReferenceError),
51+
});
52+
53+
SyntaxError = class extends Error {
54+
constructor(message?: string) {
55+
// @ts-ignore
56+
super(message, "SyntaxError");
57+
}
58+
public __tostring(): string {
59+
return `${this.name}: ${this.message}`;
60+
}
61+
} as any;
62+
setmetatable(SyntaxError, {
63+
__call: (_self: any, message: string) => new SyntaxError(message),
64+
__index: getmetatable(SyntaxError),
65+
});
66+
67+
TypeError = class extends Error {
68+
constructor(message?: string) {
69+
// @ts-ignore
70+
super(message, "TypeError");
71+
}
72+
public __tostring(): string {
73+
return `${this.name}: ${this.message}`;
74+
}
75+
} as any;
76+
setmetatable(TypeError, {
77+
__call: (_self: any, message: string) => new TypeError(message),
78+
__index: getmetatable(TypeError),
79+
});
80+
81+
URIError = class extends Error {
82+
constructor(message?: string) {
83+
// @ts-ignore
84+
super(message, "URIError");
85+
}
86+
public __tostring(): string {
87+
return `${this.name}: ${this.message}`;
88+
}
89+
} as any;
90+
setmetatable(URIError, {
91+
__call: (_self: any, message: string) => new URIError(message),
92+
__index: getmetatable(URIError),
93+
});

src/lualib/declarations/global.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ declare function type(
1010
value: any
1111
): "nil" | "number" | "string" | "boolean" | "table" | "function" | "thread" | "userdata";
1212
declare function setmetatable<T extends object>(table: T, metatable: any): T;
13+
declare function getmetatable<T extends object>(table: T): any;
1314
declare function rawget<T, K extends keyof T>(table: T, key: K): T[K];
1415
declare function rawset<T, K extends keyof T>(table: T, key: K, val: T[K]): void;
1516
/** @tupleReturn */

test/unit/error.spec.ts

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import * as TSTLErrors from "../../src/TSTLErrors";
21
import * as util from "../util";
32

43
test("throwString", () => {
@@ -7,12 +6,6 @@ test("throwString", () => {
76
`.expectToEqual(new util.ExecutionError("Some Error"));
87
});
98

10-
test("throwError", () => {
11-
util.testFunction`
12-
throw Error("Some Error")
13-
`.expectToHaveDiagnosticOfError(TSTLErrors.InvalidThrowExpression(util.nodeStub));
14-
});
15-
169
test.skip.each([0, 1, 2])("re-throw (%p)", i => {
1710
util.testFunction`
1811
const i: number = ${i};
@@ -292,3 +285,63 @@ test("return from nested finally", () => {
292285
`;
293286
expect(util.transpileAndExecute(code)).toBe("finally AB");
294287
});
288+
289+
test("throw and catch custom error object", () => {
290+
const code = `
291+
try {
292+
throw {x: "Hello error object!"};
293+
} catch (error) {
294+
return error.x;
295+
}
296+
`;
297+
expect(util.transpileAndExecute(code)).toBe("Hello error object!");
298+
});
299+
300+
test.each(["Error", "RangeError", "ReferenceError", "SyntaxError", "TypeError", "URIError"])(
301+
"throw builtin Errors as classes",
302+
errorType => {
303+
const code = `
304+
try {
305+
throw new ${errorType}("message")
306+
} catch (error) {
307+
if (error instanceof Error) {
308+
return \`\${error}\`;
309+
}
310+
}
311+
`;
312+
expect(util.transpileAndExecute(code)).toBe(`${errorType}: message`);
313+
}
314+
);
315+
316+
test.each(["Error", "RangeError", "ReferenceError", "SyntaxError", "TypeError", "URIError"])(
317+
"throw builtin Errors as functions",
318+
errorType => {
319+
const code = `
320+
try {
321+
throw ${errorType}("message")
322+
} catch (error) {
323+
if (error instanceof Error) {
324+
return \`\${error}\`;
325+
}
326+
}
327+
`;
328+
expect(util.transpileAndExecute(code)).toBe(`${errorType}: message`);
329+
}
330+
);
331+
332+
test("get stack from builtin error object", () => {
333+
const code = `
334+
function innerFunctionThatThrows() { throw RangeError(); }
335+
function outerFunctionThatThrows() { innerFunctionThatThrows(); }
336+
try {
337+
outerFunctionThatThrows();
338+
} catch (error) {
339+
if (error instanceof Error) {
340+
return error.stack;
341+
}
342+
}
343+
`;
344+
const stack = util.transpileAndExecute(code);
345+
expect(stack).toMatch("innerFunctionThatThrows");
346+
expect(stack).toMatch("outerFunctionThatThrows");
347+
});

0 commit comments

Comments
 (0)