Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
e8aff2b
LuaTable draft
hazzard993 Apr 9, 2019
59d25a7
Refactor and added tests
hazzard993 Apr 10, 2019
174f191
Fixed tripped watch file
hazzard993 Apr 10, 2019
600616f
Missing semicolon
hazzard993 Apr 10, 2019
aea50a8
Prettified
hazzard993 Apr 10, 2019
ce82a03
Merge remote-tracking branch 'upstream/master' into lua-table
hazzard993 Apr 11, 2019
9a55666
Merge remote-tracking branch 'upstream/master' into lua-table
hazzard993 Apr 12, 2019
94c8217
Merge remote-tracking branch 'upstream/master' into lua-table
hazzard993 Apr 13, 2019
fe81a01
Cannot extend LuaTable classes
hazzard993 Apr 13, 2019
65fea6f
luaTable classes can only be declared
hazzard993 Apr 13, 2019
2d0300c
Restricted LuaTable parameter length
hazzard993 Apr 13, 2019
6848f11
string -> LuaTable correction
hazzard993 Apr 14, 2019
213a0a4
LuaTable length cannot be re-assigned
hazzard993 Apr 14, 2019
152bf96
Prettier
hazzard993 Apr 14, 2019
de40026
Merge remote-tracking branch 'upstream/master' into lua-table
hazzard993 Apr 15, 2019
6e849e1
LuaTable interface tests
hazzard993 Apr 15, 2019
cc81891
LuaTables cannot be constructed with arguments
hazzard993 Apr 15, 2019
c51c7df
Merge remote-tracking branch 'upstream/master' into lua-table
hazzard993 Apr 18, 2019
eb15d5c
Test splitting and more interface tests
hazzard993 Apr 20, 2019
54e4aac
instanceof LuaTable not allowed
hazzard993 Apr 24, 2019
32ad53d
Purpose in life for exception
hazzard993 May 1, 2019
781fd95
No more other methods
hazzard993 May 4, 2019
ed5c4c5
Broke down transformLuaTableCallExpression into other methods
hazzard993 May 4, 2019
3f9de1d
LuaTable -> luaTable
hazzard993 May 4, 2019
0762631
Merge remote-tracking branch 'upstream/master' into lua-table
hazzard993 May 4, 2019
49ff675
luaTables must be within an ambient context
hazzard993 May 4, 2019
8691eee
Added isAmbient and disallowed argument spreading
hazzard993 May 4, 2019
17f1f13
Unsupported method consistency
hazzard993 May 4, 2019
f3d832b
tsHelper.isDeclared is unused
hazzard993 May 4, 2019
b22c364
public -> private
hazzard993 May 4, 2019
baca079
Missed one more public
hazzard993 May 4, 2019
b75f0ef
Allowed transpileAndExecute to use ambient code
hazzard993 May 4, 2019
0756141
luaTable functional tests
hazzard993 May 4, 2019
2a48670
transpileString signature change
hazzard993 May 4, 2019
a2dd7ab
Revert util.ts changes
hazzard993 May 4, 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
3 changes: 3 additions & 0 deletions src/Decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export class Decorator {
return DecoratorKind.TupleReturn;
case "luaiterator":
return DecoratorKind.LuaIterator;
case "luatable":
return DecoratorKind.LuaTable;
case "noself":
return DecoratorKind.NoSelf;
case "noselfinfile":
Expand Down Expand Up @@ -56,6 +58,7 @@ export enum DecoratorKind {
Phantom = "Phantom",
TupleReturn = "TupleReturn",
LuaIterator = "LuaIterator",
LuaTable = "LuaTable",
NoSelf = "NoSelf",
NoSelfInFile = "NoSelfInFile",
}
172 changes: 172 additions & 0 deletions src/LuaTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,19 @@ export class LuaTransformer {
}
}

// You cannot extend LuaTable classes
if (extendsType) {
const decorators = tsHelper.getCustomDecorators(extendsType, this.checker);
if (decorators.has(DecoratorKind.LuaTable)) {
throw TSTLErrors.InvalidExtendsLuaTable(statement);
}
}

// LuaTable classes must be ambient
if (decorators.has(DecoratorKind.LuaTable) && !tsHelper.isAmbient(statement)) {
throw TSTLErrors.ForbiddenLuaTableNonDeclaration(statement);
}

// Get all properties with value
const properties = statement.members.filter(ts.isPropertyDeclaration).filter(member => member.initializer);

Expand Down Expand Up @@ -1981,6 +1994,22 @@ export class LuaTransformer {
);
}

if (ts.isCallExpression(expression) && ts.isPropertyAccessExpression(expression.expression)) {
const ownerType = this.checker.getTypeAtLocation(expression.expression.expression);
const classDecorators = tsHelper.getCustomDecorators(ownerType, this.checker);
if (classDecorators.has(DecoratorKind.LuaTable)) {
this.validateLuaTableCall(
expression as ts.CallExpression & { expression: ts.PropertyAccessExpression },
true
);
return this.transformLuaTableExpressionStatement(
statement as ts.ExpressionStatement
& { expression: ts.CallExpression }
& { expression: { expression: ts.PropertyAccessExpression } }
);
}
}

return tstl.createExpressionStatement(this.expectExpression(this.transformExpression(expression)));
}

Expand Down Expand Up @@ -2666,6 +2695,10 @@ export class LuaTransformer {
throw TSTLErrors.InvalidInstanceOfExtension(expression);
}

if (decorators.has(DecoratorKind.LuaTable)) {
throw TSTLErrors.InvalidInstanceOfLuaTable(expression);
}

if (tsHelper.isStandardLibraryType(rhsType, "ObjectConstructor", this.program)) {
return this.transformLuaLibFunction(LuaLibFeature.InstanceOfObject, expression, lhs);
}
Expand Down Expand Up @@ -2697,6 +2730,7 @@ export class LuaTransformer {
const rightType = this.checker.getTypeAtLocation(expression.right);
const leftType = this.checker.getTypeAtLocation(expression.left);
this.validateFunctionAssignment(expression.right, rightType, leftType);
this.validatePropertyAssignment(expression);

if (tsHelper.isArrayLengthAssignment(expression, this.checker, this.program)) {
// array.length = x
Expand Down Expand Up @@ -3393,6 +3427,17 @@ export class LuaTransformer {
);
}

if (classDecorators.has(DecoratorKind.LuaTable)) {
if (node.arguments && node.arguments.length > 0) {
throw TSTLErrors.ForbiddenLuaTableUseException(
"No parameters are allowed when constructing a LuaTable object.",
node
);
} else {
return tstl.createTableExpression();
}
}

return tstl.createCallExpression(
tstl.createTableIndexExpression(name, tstl.createStringLiteral("new")),
params,
Expand Down Expand Up @@ -3535,6 +3580,18 @@ export class LuaTransformer {
return this.transformSymbolCallExpression(node);
}

const classDecorators = tsHelper.getCustomDecorators(ownerType, this.checker);

if (classDecorators.has(DecoratorKind.LuaTable)) {
this.validateLuaTableCall(
node as ts.CallExpression & { expression: ts.PropertyAccessExpression },
false
);
return this.transformLuaTableCallExpression(
node as ts.CallExpression & { expression: ts.PropertyAccessExpression }
);
}

switch (ownerType.flags) {
case ts.TypeFlags.String:
case ts.TypeFlags.StringLiteral:
Expand Down Expand Up @@ -3712,6 +3769,10 @@ export class LuaTransformer {
return tstl.createIdentifier(property, node);
}

if (decorators.has(DecoratorKind.LuaTable)) {
return this.transformLuaTableProperty(node);
}

// Catch math expressions
if (ts.isIdentifier(node.expression)) {
const ownerType = this.checker.getTypeAtLocation(node.expression);
Expand Down Expand Up @@ -3851,6 +3912,16 @@ export class LuaTransformer {
}
}

private transformLuaTableProperty(node: ts.PropertyAccessExpression): tstl.UnaryExpression | undefined {
switch (node.name.escapedText) {
case "length":
const propertyAccessExpression = this.expectExpression(this.transformExpression(node.expression));
return tstl.createUnaryExpression(propertyAccessExpression, tstl.SyntaxKind.LengthOperator, node);
default:
throw TSTLErrors.UnsupportedProperty("LuaTable", node.name.escapedText as string, node);
}
}

public transformElementAccessExpression(expression: ts.ElementAccessExpression): ExpressionVisitResult {
const table = this.expectExpression(this.transformExpression(expression.expression));
const index = this.expectExpression(this.transformExpression(expression.argumentExpression));
Expand Down Expand Up @@ -4219,6 +4290,91 @@ export class LuaTransformer {
}
}

private validateLuaTableCall(
expression: ts.CallExpression & { expression: ts.PropertyAccessExpression },
isWithinExpressionStatement: boolean
): void {
const methodName = expression.expression.name.escapedText;
if (expression.arguments.some(argument => ts.isSpreadElement(argument))) {
throw TSTLErrors.ForbiddenLuaTableUseException("Arguments cannot be spread.", expression);
}

switch (methodName) {
case "get":
if (expression.arguments.length !== 1) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It also should check that it's argument isn't a SpreadElement

throw TSTLErrors.ForbiddenLuaTableUseException(
"One parameter is required for get().",
expression
);
}
break;
case "set":
if (expression.arguments.length !== 2) {
throw TSTLErrors.ForbiddenLuaTableUseException(
"Two parameters are required for set().",
expression
);
}
if (!isWithinExpressionStatement) {
throw TSTLErrors.ForbiddenLuaTableSetExpression(expression);
}
break;
}
}

private transformLuaTableExpressionStatement(
node: ts.ExpressionStatement
& { expression: ts.CallExpression }
& { expression: { expression: ts.PropertyAccessExpression }}
): tstl.VariableDeclarationStatement | tstl.AssignmentStatement {
const methodName = node.expression.expression.name.escapedText;
const signature = this.checker.getResolvedSignature(node.expression);
const tableName = (node.expression.expression.expression as ts.Identifier).escapedText;
const luaTable = tstl.createIdentifier(tableName);
const params = this.transformArguments((node.expression as ts.CallExpression).arguments, signature);

switch (methodName) {
case "get":
return tstl.createVariableDeclarationStatement(
tstl.createAnonymousIdentifier(node.expression),
tstl.createTableIndexExpression(luaTable, params[0], node.expression),
node.expression
);
case "set":
return tstl.createAssignmentStatement(
tstl.createTableIndexExpression(luaTable, params[0], node.expression),
params.splice(1),
node.expression
);
default:
throw TSTLErrors.ForbiddenLuaTableUseException(
"Unsupported method.",
node.expression
);
}
}

private transformLuaTableCallExpression(
expression: ts.CallExpression & { expression: ts.PropertyAccessExpression }
): tstl.Expression {
const method = expression.expression;
const methodName = method.name.escapedText;
const signature = this.checker.getResolvedSignature(expression);
const tableName = (method.expression as ts.Identifier).escapedText;
const luaTable = tstl.createIdentifier(tableName);
const params = this.transformArguments(expression.arguments, signature);

switch (methodName) {
case "get":
return tstl.createTableIndexExpression(luaTable, params[0], expression);
default:
throw TSTLErrors.ForbiddenLuaTableUseException(
"Unsupported method.",
expression
);
}
}

private transformArrayCallExpression(node: ts.CallExpression): tstl.CallExpression {
const expression = node.expression as ts.PropertyAccessExpression;
const signature = this.checker.getResolvedSignature(node);
Expand Down Expand Up @@ -4823,6 +4979,22 @@ export class LuaTransformer {
}
}

private validatePropertyAssignment(node: ts.Node): void {
if (ts.isBinaryExpression(node) && ts.isPropertyAccessExpression(node.left)) {
const leftType = this.checker.getTypeAtLocation(node.left.expression);
const decorators = tsHelper.getCustomDecorators(leftType, this.checker);
if (decorators.has(DecoratorKind.LuaTable)) {
switch (node.left.name.escapedText as string) {
case "length":
throw TSTLErrors.ForbiddenLuaTableUseException(
`A LuaTable object's length cannot be re-assigned.`,
node
);
}
}
}
}

private wrapInFunctionCall(expression: tstl.Expression): tstl.FunctionExpression {
const returnStatement = tstl.createReturnStatement([expression]);
return tstl.createFunctionExpression(
Expand Down
4 changes: 4 additions & 0 deletions src/TSHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@ export class TSHelper {
return false;
}

public static isAmbient(node: ts.Declaration): boolean {
return !((ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Ambient) === 0);
}

public static isStatic(node: ts.Node): boolean {
return node.modifiers !== undefined && node.modifiers.some(m => m.kind === ts.SyntaxKind.StaticKeyword);
}
Expand Down
16 changes: 16 additions & 0 deletions src/TSTLErrors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,22 @@ export class TSTLErrors {
public static ForbiddenForIn = (node: ts.Node) =>
new TranspileError(`Iterating over arrays with 'for ... in' is not allowed.`, node);

public static ForbiddenLuaTableSetExpression = (node: ts.Node) => new TranspileError(
`A '@luaTable' object's 'set()' method can only be used as a Statement, not an Expression.`,
node);

public static ForbiddenLuaTableNonDeclaration = (node: ts.Node) =>
new TranspileError(`Classes with the '@luaTable' decorator must be declared.`, node);

public static InvalidExtendsLuaTable = (node: ts.Node) =>
new TranspileError(`Cannot extend classes with the decorator '@luaTable'.`, node);

public static InvalidInstanceOfLuaTable = (node: ts.Node) =>
new TranspileError(`The instanceof operator cannot be used with a '@luaTable' class.`, node);

public static ForbiddenLuaTableUseException = (description: string, node: ts.Node) =>
new TranspileError(`Invalid @luaTable usage: ${description}`, node);

public static HeterogeneousEnum = (node: ts.Node) => new TranspileError(
`Invalid heterogeneous enum. Enums should either specify no member values, ` +
`or specify values (of the same type) for all members.`,
Expand Down
10 changes: 10 additions & 0 deletions test/translation/__snapshots__/transformation.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,16 @@ exports[`Transformation (interfaceIndex) 1`] = `
a.abc = \\"def\\""
`;

exports[`Transformation (luaTable) 1`] = `
"tbl = {}
tbl.value = 5
local value = tbl.value
local tblLength = #tbl
itbl.value = 5
local ivalue = itbl.value
local ilength = #tbl"
`;

exports[`Transformation (methodRestArguments) 1`] = `
"MyClass = {}
MyClass.name = \\"MyClass\\"
Expand Down
23 changes: 23 additions & 0 deletions test/translation/transformation/luaTable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/** @luaTable */
declare class Table<K extends {} = {}, V = any> {
public readonly length: number;
public set(key: K, value: V): void;
public get(key: K): V;
}
declare let tbl: Table;
tbl = new Table();
tbl.set("value", 5);
const value = tbl.get("value");
const tblLength = tbl.length;

/** @luaTable */
declare interface InterfaceTable<K extends {} = {}, V = any> {
readonly length: number;
set(key: K, value: V): void;
get(key: K): V;
}

declare const itbl: InterfaceTable;
itbl.set("value", 5);
const ivalue = itbl.get("value");
const ilength = tbl.length;
Loading