Skip to content

Commit 45ea013

Browse files
ark120202Perryvw
authored andcommitted
Move new operator and some class setup boilerplate to lualib (#759)
* Move `new` utility to lualib * Move some class setup boilerplate to lualib * Fix failing tests * Remove duplicated `@tupleReturn` tests * Move `Class.prototype.__index = Class.prototype` to helper * Add helper call to sourcemap tests
1 parent dc18e47 commit 45ea013

File tree

13 files changed

+54
-308
lines changed

13 files changed

+54
-308
lines changed

src/LuaLib.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export enum LuaLibFeature {
2424
ArrayFlat = "ArrayFlat",
2525
ArrayFlatMap = "ArrayFlatMap",
2626
ArraySetLength = "ArraySetLength",
27+
Class = "Class",
2728
ClassIndex = "ClassIndex",
2829
ClassNewIndex = "ClassNewIndex",
2930
Decorate = "Decorate",
@@ -36,6 +37,7 @@ export enum LuaLibFeature {
3637
InstanceOfObject = "InstanceOfObject",
3738
Iterator = "Iterator",
3839
Map = "Map",
40+
New = "New",
3941
NewIndex = "NewIndex",
4042
Number = "Number",
4143
NumberIsFinite = "NumberIsFinite",
@@ -67,7 +69,7 @@ export enum LuaLibFeature {
6769
const luaLibDependencies: { [lib in LuaLibFeature]?: LuaLibFeature[] } = {
6870
ArrayFlat: [LuaLibFeature.ArrayConcat],
6971
ArrayFlatMap: [LuaLibFeature.ArrayConcat],
70-
Error: [LuaLibFeature.FunctionCall],
72+
Error: [LuaLibFeature.New, LuaLibFeature.FunctionCall],
7173
InstanceOf: [LuaLibFeature.Symbol],
7274
Iterator: [LuaLibFeature.Symbol],
7375
ObjectFromEntries: [LuaLibFeature.Iterator, LuaLibFeature.Symbol],

src/lualib/Class.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
function __TS__Class(): LuaClass {
2+
const c = {} as LuaClass;
3+
c.__index = c;
4+
c.prototype = {};
5+
c.prototype.__index = c.prototype;
6+
c.prototype.constructor = c;
7+
return c;
8+
}

src/lualib/New.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
function __TS__New(this: void, target: LuaClass, ...args: Vararg<any>): any {
2+
const instance: any = setmetatable({}, target.prototype);
3+
instance.____constructor(...args);
4+
return instance;
5+
}

src/lualib/declarations/tstl.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ interface LuaClass {
1111
____super?: LuaClass;
1212
____getters?: { [key: string]: (self: LuaClass) => any };
1313
____setters?: { [key: string]: (self: LuaClass, val: any) => void };
14+
__index?: any;
1415
}
1516

1617
interface LuaObject {
1718
constructor: LuaClass;
1819
____getters?: { [key: string]: (self: LuaObject) => any };
1920
____setters?: { [key: string]: (self: LuaObject, val: any) => void };
21+
__index?: any;
2022
}

src/transformation/utils/errors.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,6 @@ export const InvalidInstanceOfExtension = (node: ts.Node) =>
5151

5252
export const InvalidJsonFileContent = (node: ts.Node) => new TranspileError("Invalid JSON file content", node);
5353

54-
export const ForbiddenStaticClassPropertyName = (node: ts.Node, name: string) =>
55-
new TranspileError(`Cannot use "${name}" as a static class property or method name.`, node);
56-
5754
export const MissingClassName = (node: ts.Node) => new TranspileError(`Class declarations must have a name.`, node);
5855

5956
export const MissingForOfVariables = (node: ts.Node) =>

src/transformation/utils/lualib.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export function importLuaLibFeature(context: TransformationContext, feature: Lua
1818
export function transformLuaLibFunction(
1919
context: TransformationContext,
2020
feature: LuaLibFeature,
21-
tsParent?: ts.Expression,
21+
tsParent?: ts.Node,
2222
...params: lua.Expression[]
2323
): lua.CallExpression {
2424
importLuaLibFeature(context, feature);

src/transformation/visitors/class/index.ts

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { FunctionVisitor, TransformationContext } from "../../context";
55
import { AnnotationKind, getTypeAnnotations } from "../../utils/annotations";
66
import {
77
ForbiddenLuaTableNonDeclaration,
8-
ForbiddenStaticClassPropertyName,
98
InvalidExportsExtension,
109
InvalidExtendsExtension,
1110
InvalidExtendsLuaTable,
@@ -80,16 +79,13 @@ export function transformClassDeclaration(
8079
} else if (classDeclaration.name !== undefined) {
8180
className = transformIdentifier(context, classDeclaration.name);
8281
classNameText = classDeclaration.name.text;
82+
} else if (hasDefaultExportModifier(classDeclaration)) {
83+
const left = createExportedIdentifier(context, createDefaultExportIdentifier(classDeclaration));
84+
const right = transformClassAsExpression(classDeclaration, context, true);
85+
86+
return lua.createAssignmentStatement(left, right, classDeclaration);
8387
} else {
84-
const isDefaultExport = hasDefaultExportModifier(classDeclaration);
85-
if (isDefaultExport) {
86-
const left = createExportedIdentifier(context, createDefaultExportIdentifier(classDeclaration));
87-
const right = transformClassAsExpression(classDeclaration, context, true);
88-
89-
return lua.createAssignmentStatement(left, right, classDeclaration);
90-
} else {
91-
throw MissingClassName(classDeclaration);
92-
}
88+
throw MissingClassName(classDeclaration);
9389
}
9490

9591
const annotations = getTypeAnnotations(context, context.checker.getTypeAtLocation(classDeclaration));
@@ -136,14 +132,6 @@ export function transformClassDeclaration(
136132
throw ForbiddenLuaTableNonDeclaration(classDeclaration);
137133
}
138134

139-
for (const member of classDeclaration.members) {
140-
if (member.name && (ts.isStringLiteral(member.name) || ts.isIdentifier(member.name))) {
141-
if (isStaticNode(member) && member.name.text === "new") {
142-
throw ForbiddenStaticClassPropertyName(member, member.name.text);
143-
}
144-
}
145-
}
146-
147135
// Get all properties with value
148136
const properties = classDeclaration.members.filter(ts.isPropertyDeclaration).filter(member => member.initializer);
149137

src/transformation/visitors/class/new.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as lua from "../../../LuaAST";
33
import { FunctionVisitor, TransformationContext } from "../../context";
44
import { AnnotationKind, getTypeAnnotations } from "../../utils/annotations";
55
import { InvalidAnnotationArgumentNumber, InvalidNewExpressionOnExtension } from "../../utils/errors";
6-
import { importLuaLibFeature, LuaLibFeature } from "../../utils/lualib";
6+
import { importLuaLibFeature, LuaLibFeature, transformLuaLibFunction } from "../../utils/lualib";
77
import { transformArguments } from "../call";
88
import { transformLuaTableNewExpression } from "../lua-table";
99

@@ -82,5 +82,5 @@ export const transformNewExpression: FunctionVisitor<ts.NewExpression> = (node,
8282
);
8383
}
8484

85-
return lua.createCallExpression(lua.createTableIndexExpression(name, lua.createStringLiteral("new")), params, node);
85+
return transformLuaLibFunction(context, LuaLibFeature.New, node, name, ...params);
8686
};

src/transformation/visitors/class/setup.ts

Lines changed: 15 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,7 @@ import {
88
getIdentifierExportScope,
99
hasDefaultExportModifier,
1010
} from "../../utils/export";
11-
import {
12-
createExportsIdentifier,
13-
createLocalOrExportedOrGlobalDeclaration,
14-
createSelfIdentifier,
15-
} from "../../utils/lua-ast";
11+
import { createExportsIdentifier, createLocalOrExportedOrGlobalDeclaration } from "../../utils/lua-ast";
1612
import { importLuaLibFeature, LuaLibFeature, transformLuaLibFunction } from "../../utils/lualib";
1713
import { hasMemberInClassOrAncestor } from "./members/accessors";
1814
import { getExtendedTypeNode, isStaticNode } from "./utils";
@@ -27,20 +23,19 @@ export function createClassSetup(
2723
): lua.Statement[] {
2824
const result: lua.Statement[] = [];
2925

30-
// [____exports.]className = {}
31-
const classTable: lua.Expression = lua.createTableExpression();
32-
33-
const isDefaultExport = hasDefaultExportModifier(statement);
26+
// __TS__Class()
27+
const classInitializer = transformLuaLibFunction(context, LuaLibFeature.Class, statement);
3428

35-
const defaultExportLeftHandSide = isDefaultExport
29+
const defaultExportLeftHandSide = hasDefaultExportModifier(statement)
3630
? lua.createTableIndexExpression(createExportsIdentifier(), createDefaultExportStringLiteral(statement))
3731
: undefined;
3832

39-
const classVar = defaultExportLeftHandSide
40-
? [lua.createAssignmentStatement(defaultExportLeftHandSide, classTable, statement)]
41-
: createLocalOrExportedOrGlobalDeclaration(context, className, classTable, statement);
42-
43-
result.push(...classVar);
33+
// [____exports.]className = __TS__Class()
34+
if (defaultExportLeftHandSide) {
35+
result.push(lua.createAssignmentStatement(defaultExportLeftHandSide, classInitializer, statement));
36+
} else {
37+
result.push(...createLocalOrExportedOrGlobalDeclaration(context, className, classInitializer, statement));
38+
}
4439

4540
if (defaultExportLeftHandSide) {
4641
// local localClassName = ____exports.default
@@ -79,14 +74,6 @@ export function createClassSetup(
7974
importLuaLibFeature(context, LuaLibFeature.ClassIndex);
8075
}
8176

82-
// localClassName.__index = localClassName
83-
const classIndex = lua.createTableIndexExpression(
84-
lua.cloneIdentifier(localClassName),
85-
lua.createStringLiteral("__index")
86-
);
87-
const assignClassIndex = lua.createAssignmentStatement(classIndex, lua.cloneIdentifier(localClassName), statement);
88-
result.push(assignClassIndex);
89-
9077
// localClassName.____setters = {}
9178
if (statement.members.some(m => ts.isSetAccessor(m) && isStaticNode(m))) {
9279
const classSetters = lua.createTableIndexExpression(
@@ -99,12 +86,9 @@ export function createClassSetup(
9986
importLuaLibFeature(context, LuaLibFeature.ClassNewIndex);
10087
}
10188

102-
// localClassName.prototype = {}
89+
// localClassName.prototype
10390
const createClassPrototype = () =>
10491
lua.createTableIndexExpression(lua.cloneIdentifier(localClassName), lua.createStringLiteral("prototype"));
105-
const classPrototypeTable = lua.createTableExpression();
106-
const assignClassPrototype = lua.createAssignmentStatement(createClassPrototype(), classPrototypeTable, statement);
107-
result.push(assignClassPrototype);
10892

10993
// localClassName.prototype.____getters = {}
11094
if (statement.members.some(m => ts.isGetAccessor(m) && !isStaticNode(m))) {
@@ -120,23 +104,15 @@ export function createClassSetup(
120104
result.push(assignClassPrototypeGetters);
121105
}
122106

123-
const classPrototypeIndex = lua.createTableIndexExpression(
124-
createClassPrototype(),
125-
lua.createStringLiteral("__index")
126-
);
127107
if (hasMemberInClassOrAncestor(context, statement, m => ts.isGetAccessor(m) && !isStaticNode(m))) {
128108
// localClassName.prototype.__index = __TS__Index(localClassName.prototype)
129-
const assignClassPrototypeIndex = lua.createAssignmentStatement(
130-
classPrototypeIndex,
131-
transformLuaLibFunction(context, LuaLibFeature.Index, undefined, createClassPrototype()),
132-
statement
109+
const classPrototypeIndex = lua.createTableIndexExpression(
110+
createClassPrototype(),
111+
lua.createStringLiteral("__index")
133112
);
134-
result.push(assignClassPrototypeIndex);
135-
} else {
136-
// localClassName.prototype.__index = localClassName.prototype
137113
const assignClassPrototypeIndex = lua.createAssignmentStatement(
138114
classPrototypeIndex,
139-
createClassPrototype(),
115+
transformLuaLibFunction(context, LuaLibFeature.Index, undefined, createClassPrototype()),
140116
statement
141117
);
142118
result.push(assignClassPrototypeIndex);
@@ -170,18 +146,6 @@ export function createClassSetup(
170146
result.push(assignClassPrototypeIndex);
171147
}
172148

173-
// localClassName.prototype.constructor = localClassName
174-
const classPrototypeConstructor = lua.createTableIndexExpression(
175-
createClassPrototype(),
176-
lua.createStringLiteral("constructor")
177-
);
178-
const assignClassPrototypeConstructor = lua.createAssignmentStatement(
179-
classPrototypeConstructor,
180-
lua.cloneIdentifier(localClassName),
181-
statement
182-
);
183-
result.push(assignClassPrototypeConstructor);
184-
185149
const hasStaticGetters = hasMemberInClassOrAncestor(
186150
context,
187151
statement,
@@ -303,46 +267,5 @@ export function createClassSetup(
303267
result.push(setClassMetatable);
304268
}
305269

306-
const newFuncStatements: lua.Statement[] = [];
307-
308-
// local self = setmetatable({}, localClassName.prototype)
309-
const assignSelf = lua.createVariableDeclarationStatement(
310-
createSelfIdentifier(),
311-
lua.createCallExpression(lua.createIdentifier("setmetatable"), [
312-
lua.createTableExpression(),
313-
createClassPrototype(),
314-
]),
315-
statement
316-
);
317-
newFuncStatements.push(assignSelf);
318-
319-
// self:____constructor(...)
320-
const callConstructor = lua.createExpressionStatement(
321-
lua.createMethodCallExpression(createSelfIdentifier(), lua.createIdentifier("____constructor"), [
322-
lua.createDotsLiteral(),
323-
]),
324-
statement
325-
);
326-
newFuncStatements.push(callConstructor);
327-
328-
// return self
329-
const returnSelf = lua.createReturnStatement([createSelfIdentifier()], statement);
330-
newFuncStatements.push(returnSelf);
331-
332-
// function localClassName.new(construct, ...) ... end
333-
// or function export.localClassName.new(construct, ...) ... end
334-
const newFunc = lua.createAssignmentStatement(
335-
lua.createTableIndexExpression(lua.cloneIdentifier(localClassName), lua.createStringLiteral("new")),
336-
lua.createFunctionExpression(
337-
lua.createBlock(newFuncStatements),
338-
undefined,
339-
lua.createDotsLiteral(),
340-
undefined,
341-
lua.FunctionExpressionFlags.Declaration
342-
),
343-
statement
344-
);
345-
result.push(newFunc);
346-
347270
return result;
348271
}

test/translation/__snapshots__/transformation.spec.ts.snap

Lines changed: 10 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -40,17 +40,9 @@ end"
4040
`;
4141

4242
exports[`Transformation (classPureAbstract) 1`] = `
43-
"ClassB = {}
43+
"require(\\"lualib_bundle\\");
44+
ClassB = __TS__Class()
4445
ClassB.name = \\"ClassB\\"
45-
ClassB.__index = ClassB
46-
ClassB.prototype = {}
47-
ClassB.prototype.__index = ClassB.prototype
48-
ClassB.prototype.constructor = ClassB
49-
function ClassB.new(...)
50-
local self = setmetatable({}, ClassB.prototype)
51-
self:____constructor(...)
52-
return self
53-
end
5446
function ClassB.prototype.____constructor(self)
5547
end"
5648
`;
@@ -82,17 +74,9 @@ return ____exports"
8274
`;
8375

8476
exports[`Transformation (methodRestArguments) 1`] = `
85-
"MyClass = {}
77+
"require(\\"lualib_bundle\\");
78+
MyClass = __TS__Class()
8679
MyClass.name = \\"MyClass\\"
87-
MyClass.__index = MyClass
88-
MyClass.prototype = {}
89-
MyClass.prototype.__index = MyClass.prototype
90-
MyClass.prototype.constructor = MyClass
91-
function MyClass.new(...)
92-
local self = setmetatable({}, MyClass.prototype)
93-
self:____constructor(...)
94-
return self
95-
end
9680
function MyClass.prototype.____constructor(self)
9781
end
9882
function MyClass.prototype.varargsFunction(self, a, ...)
@@ -106,38 +90,22 @@ return ____exports"
10690
`;
10791

10892
exports[`Transformation (modulesClassExport) 1`] = `
109-
"local ____exports = {}
110-
____exports.TestClass = {}
93+
"require(\\"lualib_bundle\\");
94+
local ____exports = {}
95+
____exports.TestClass = __TS__Class()
11196
local TestClass = ____exports.TestClass
11297
TestClass.name = \\"TestClass\\"
113-
TestClass.__index = TestClass
114-
TestClass.prototype = {}
115-
TestClass.prototype.__index = TestClass.prototype
116-
TestClass.prototype.constructor = TestClass
117-
function TestClass.new(...)
118-
local self = setmetatable({}, TestClass.prototype)
119-
self:____constructor(...)
120-
return self
121-
end
12298
function TestClass.prototype.____constructor(self)
12399
end
124100
return ____exports"
125101
`;
126102

127103
exports[`Transformation (modulesClassWithMemberExport) 1`] = `
128-
"local ____exports = {}
129-
____exports.TestClass = {}
104+
"require(\\"lualib_bundle\\");
105+
local ____exports = {}
106+
____exports.TestClass = __TS__Class()
130107
local TestClass = ____exports.TestClass
131108
TestClass.name = \\"TestClass\\"
132-
TestClass.__index = TestClass
133-
TestClass.prototype = {}
134-
TestClass.prototype.__index = TestClass.prototype
135-
TestClass.prototype.constructor = TestClass
136-
function TestClass.new(...)
137-
local self = setmetatable({}, TestClass.prototype)
138-
self:____constructor(...)
139-
return self
140-
end
141109
function TestClass.prototype.____constructor(self)
142110
end
143111
function TestClass.prototype.memberFunc(self)

0 commit comments

Comments
 (0)