X Tutup
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
5a19a37
track Scope in transformClassExpression
TheLartians Sep 24, 2019
46fdc43
allow throwing arbitrary types and add builtin error classes
TheLartians Sep 24, 2019
a0e3f14
allow subclassing Error
TheLartians Sep 24, 2019
d70d3f9
Update test/unit/error.spec.ts
TheLartians Sep 24, 2019
4f5734b
use custom traceback in errors
TheLartians Sep 25, 2019
e88392b
return full stack on fallback
TheLartians Sep 25, 2019
d30982f
use builder API for tests
TheLartians Sep 25, 2019
771152d
cleanup
TheLartians Sep 25, 2019
91363b9
add error name to type and reduce dependencies
TheLartians Sep 26, 2019
11b8610
test subclasses for stack
TheLartians Sep 26, 2019
cdb2754
test string representation of subclassed errors
TheLartians Sep 26, 2019
92b680d
fix throwing as function test
TheLartians Sep 26, 2019
99584de
style changes
TheLartians Sep 26, 2019
d1ca812
formatting
TheLartians Sep 26, 2019
f0fab20
add sourceMapTraceback option to test
TheLartians Sep 26, 2019
f28da88
add 'This error serves to prevent false positives from .expectNoExecu…
TheLartians Sep 26, 2019
5cd860a
Update test/unit/error.spec.ts
TheLartians Sep 27, 2019
37ba17a
rollback to use debug.traceback
TheLartians Sep 28, 2019
21c27ea
Update src/lualib/Error.ts
TheLartians Sep 28, 2019
893cc7a
fix throwing strings and update tests
TheLartians Sep 29, 2019
ffa9d6d
Update src/lualib/Error.ts
TheLartians Sep 29, 2019
613d118
test extending from error with custom toString()
TheLartians Sep 29, 2019
dd77294
update identifiers test for new exception handling
TheLartians Sep 29, 2019
df05bb6
use constructor assignment for message property
TheLartians Sep 29, 2019
cbd980d
print stack for uncaught errors
TheLartians Sep 29, 2019
52f15c6
Update test/unit/error.spec.ts
TheLartians Oct 1, 2019
ebf0c1c
Update test/unit/error.spec.ts
TheLartians Oct 1, 2019
371c841
rollback toString() support
TheLartians Oct 1, 2019
b9fdd40
add more test cases
TheLartians Oct 1, 2019
a0b63e1
formatting
TheLartians Oct 1, 2019
6066a7e
set __tostring metamethod in constructor
TheLartians Oct 5, 2019
5400f89
update tests
TheLartians Oct 9, 2019
817b4e7
remove generic from ErrorType
TheLartians Oct 9, 2019
7b23ae3
add info string to tests
TheLartians Oct 9, 2019
d6d2855
style changes
TheLartians Oct 9, 2019
3dea2cc
style changes
TheLartians Oct 9, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/LuaLib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export enum LuaLibFeature {
ClassIndex = "ClassIndex",
ClassNewIndex = "ClassNewIndex",
Decorate = "Decorate",
Error = "Error",
FunctionApply = "FunctionApply",
FunctionBind = "FunctionBind",
FunctionCall = "FunctionCall",
Expand Down Expand Up @@ -63,6 +64,7 @@ export enum LuaLibFeature {
const luaLibDependencies: { [lib in LuaLibFeature]?: LuaLibFeature[] } = {
ArrayFlat: [LuaLibFeature.ArrayConcat],
ArrayFlatMap: [LuaLibFeature.ArrayConcat],
Error: [LuaLibFeature.FunctionCall],
InstanceOf: [LuaLibFeature.Symbol],
Iterator: [LuaLibFeature.Symbol],
ObjectFromEntries: [LuaLibFeature.Iterator, LuaLibFeature.Symbol],
Expand Down
36 changes: 23 additions & 13 deletions src/LuaTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,10 @@ export class LuaTransformer {
// Get type that is extended
const extendsType = tsHelper.getExtendedType(statement, this.checker);

if (extendsType) {
this.checkForLuaLibType(extendsType);
}

if (!(isExtension || isMetaExtension) && extendsType) {
// Non-extensions cannot extend extension classes
const extendsDecorators = tsHelper.getCustomDecorators(extendsType, this.checker);
Expand Down Expand Up @@ -2843,20 +2847,17 @@ export class LuaTransformer {
}

public transformThrowStatement(statement: ts.ThrowStatement): StatementVisitResult {
if (statement.expression === undefined) {
throw TSTLErrors.InvalidThrowExpression(statement);
}
const parameters: tstl.Expression[] = [];

const type = this.checker.getTypeAtLocation(statement.expression);
if (tsHelper.isStringType(type, this.checker, this.program)) {
const error = tstl.createIdentifier("error");
return tstl.createExpressionStatement(
tstl.createCallExpression(error, [this.transformExpression(statement.expression)]),
statement
);
} else {
throw TSTLErrors.InvalidThrowExpression(statement.expression);
if (statement.expression) {
parameters.push(this.transformExpression(statement.expression));
parameters.push(tstl.createNumericLiteral(0));
}

return tstl.createExpressionStatement(
tstl.createCallExpression(tstl.createIdentifier("error"), parameters),
statement
);
}

public transformContinueStatement(statement: ts.ContinueStatement): StatementVisitResult {
Expand Down Expand Up @@ -3679,7 +3680,10 @@ export class LuaTransformer {
className = tstl.createAnonymousIdentifier();
}

this.pushScope(ScopeType.Function);
const classDeclaration = this.transformClassDeclaration(expression, className);
this.popScope();

return this.createImmediatelyInvokedFunctionExpression(
this.statementVisitResultToArray(classDeclaration),
className,
Expand Down Expand Up @@ -4205,6 +4209,7 @@ export class LuaTransformer {

const expressionType = this.checker.getTypeAtLocation(expression.expression);
if (tsHelper.isStandardLibraryType(expressionType, undefined, this.program)) {
this.checkForLuaLibType(expressionType);
const result = this.transformGlobalFunctionCall(expression);
if (result) {
return result;
Expand Down Expand Up @@ -5501,7 +5506,8 @@ export class LuaTransformer {

protected checkForLuaLibType(type: ts.Type): void {
if (type.symbol) {
switch (this.checker.getFullyQualifiedName(type.symbol)) {
const name = this.checker.getFullyQualifiedName(type.symbol);
switch (name) {
case "Map":
this.importLuaLibFeature(LuaLibFeature.Map);
return;
Expand All @@ -5515,6 +5521,10 @@ export class LuaTransformer {
this.importLuaLibFeature(LuaLibFeature.WeakSet);
return;
}

if (tsHelper.isBuiltinErrorTypeName(name)) {
this.importLuaLibFeature(LuaLibFeature.Error);
}
}
}

Expand Down
20 changes: 20 additions & 0 deletions src/TSHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,22 @@ const defaultArrayCallMethodNames = new Set<string>([
"flatMap",
]);

// TODO [2019-09-27/Perry]: Refactor lualib detection to consistent map
const builtinErrorTypeNames = new Set([
"Error",
"ErrorConstructor",
"RangeError",
"RangeErrorConstructor",
"ReferenceError",
"ReferenceErrorConstructor",
"SyntaxError",
"SyntaxErrorConstructor",
"TypeError",
"TypeErrorConstructor",
"URIError",
"URIErrorConstructor",
]);

export function getExtendedTypeNode(
node: ts.ClassLikeDeclarationBase,
checker: ts.TypeChecker
Expand Down Expand Up @@ -1041,3 +1057,7 @@ export function formatPathToLuaPath(filePath: string): string {
}
return filePath.replace(/\.\//g, "").replace(/\//g, ".");
}

export function isBuiltinErrorTypeName(name: string): boolean {
return builtinErrorTypeNames.has(name);
}
3 changes: 0 additions & 3 deletions src/TSTLErrors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,6 @@ export const InvalidPropertyCall = (node: ts.Node) =>
export const InvalidElementCall = (node: ts.Node) =>
new TranspileError(`Tried to transpile a non-element call as an element call.`, node);

export const InvalidThrowExpression = (node: ts.Node) =>
new TranspileError(`Invalid throw expression, only strings can be thrown.`, node);

export const ForbiddenStaticClassPropertyName = (node: ts.Node, name: string) =>
new TranspileError(`Cannot use "${name}" as a static class property or method name.`, node);

Expand Down
70 changes: 70 additions & 0 deletions src/lualib/Error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
interface ErrorType {
name: string;
new (...args: any[]): Error;
}

function __TS__GetErrorStack(constructor: Function): string {
let level = 1;
while (true) {
const info = debug.getinfo(level, "f");
level += 1;
if (!info) {
// constructor is not in call stack
level = 1;
break;
} else if (info.func === constructor) {
break;
}
}

return debug.traceback(undefined, level);
}

function __TS__WrapErrorToString<T extends Error>(getDescription: (this: T) => string): (this: T) => string {
return function(this: Error): string {
const description = getDescription.call(this);
const caller = debug.getinfo(3, "f");
if (_VERSION === "Lua 5.1" || (caller && caller.func !== error)) {
return description;
} else {
return `${description}\n${this.stack}`;
}
};
}

function __TS__InitErrorClass(Type: ErrorType, name: string): any {
Type.name = name;
return setmetatable(Type, {
__call: (_self: any, message: string) => new Type(message),
});
}

Error = __TS__InitErrorClass(
class implements Error {
public name = "Error";
public stack: string;

constructor(public message = "") {
this.stack = __TS__GetErrorStack((this.constructor as any).new);
const metatable = getmetatable(this);
if (!metatable.__errorToStringPatched) {
metatable.__errorToStringPatched = true;
metatable.__tostring = __TS__WrapErrorToString(metatable.__tostring);
}
}

public toString(): string {
return this.message !== "" ? `${this.name}: ${this.message}` : this.name;
}
},
"Error"
);

for (const errorName of ["RangeError", "ReferenceError", "SyntaxError", "TypeError", "URIError"]) {
globalThis[errorName] = __TS__InitErrorClass(
class extends Error {
public name = errorName;
},
errorName
);
}
18 changes: 18 additions & 0 deletions src/lualib/declarations/debug.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
/** @noSelfInFile */

declare const _VERSION: string;
declare function error(...args: any[]): never;

declare namespace debug {
function traceback(...args: any[]): string;

interface FunctionInfo<T extends Function = Function> {
func: T;
name?: string;
namewhat: "global" | "local" | "method" | "field" | "";
source: string;
short_src: string;
linedefined: number;
lastlinedefined: number;
what: "Lua" | "C" | "main";
currentline: number;
nups: number;
}

function getinfo(i: number, what?: string): Partial<FunctionInfo>;
}
1 change: 1 addition & 0 deletions src/lualib/declarations/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ declare function type(
value: any
): "nil" | "number" | "string" | "boolean" | "table" | "function" | "thread" | "userdata";
declare function setmetatable<T extends object>(table: T, metatable: any): T;
declare function getmetatable<T extends object>(table: T): any;
declare function rawget<T, K extends keyof T>(table: T, key: K): T[K];
declare function rawset<T, K extends keyof T>(table: T, key: K, val: T[K]): void;
/** @tupleReturn */
Expand Down
58 changes: 51 additions & 7 deletions test/unit/error.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import * as TSTLErrors from "../../src/TSTLErrors";
import * as util from "../util";

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

test("throwError", () => {
util.testFunction`
throw Error("Some Error")
`.expectToHaveDiagnosticOfError(TSTLErrors.InvalidThrowExpression(util.nodeStub));
});

test.skip.each([0, 1, 2])("re-throw (%p)", i => {
util.testFunction`
const i: number = ${i};
Expand Down Expand Up @@ -292,3 +285,54 @@ test("return from nested finally", () => {
`;
expect(util.transpileAndExecute(code)).toBe("finally AB");
});

test.each([
`"error string"`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be a bit cleaner to pass actual values there and convert them with util.valueToString

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, won't atm due to util.valueToString(undefined) = undefined (#733).

`42`,
`3.141`,
`true`,
`false`,
`undefined`,
`{ x: "error object" }`,
`() => "error function"`,
])("throw and catch %s", error => {
util.testFunction`
try {
throw ${error};
} catch (error) {
if (typeof error == 'function') {
return error();
} else {
return error;
}
}
`.expectToMatchJsResult();
});

const builtinErrors = ["Error", "RangeError", "ReferenceError", "SyntaxError", "TypeError", "URIError"];

test.each([...builtinErrors, ...builtinErrors.map(type => `new ${type}`)])("%s properties", errorType => {
util.testFunction`
const error = ${errorType}();
return { name: error.name, message: error.message, string: error.toString() };
`.expectToMatchJsResult();
});

test.each([...builtinErrors, "CustomError"])("get stack from %s", errorType => {
const stack = util.testFunction`
class CustomError extends Error {
public name = "CustomError";
}

let stack: string | undefined;

function innerFunction() { stack = new ${errorType}().stack; }
function outerFunction() { innerFunction(); }
outerFunction();

return stack;
`.getLuaExecutionResult();

expect(stack).toMatch("innerFunction");
expect(stack).toMatch("outerFunction");
});
2 changes: 1 addition & 1 deletion test/unit/identifiers.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ describe("lua keyword as identifier doesn't interfere with lua's value", () => {
const error = "foobar";
throw error;`;

expect(() => util.transpileAndExecute(code)).toThrow(/^LUA ERROR: .+ foobar$/);
expect(() => util.transpileAndExecute(code)).toThrow(/^LUA ERROR: foobar$/);
});

test("variable (assert)", () => {
Expand Down
X Tutup