Skip to content

Commit 2d6a138

Browse files
authored
Static get/set accessors (#424)
* static get/set accessors * fixed stuff from merge and a little cleanup * tests - updated exiting getter/setter tests to all reference 'this' - added static variants of all tests * removed FocusTests
1 parent ed9241f commit 2d6a138

File tree

6 files changed

+465
-65
lines changed

6 files changed

+465
-65
lines changed

src/LuaLib.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ export enum LuaLibFeature {
1616
ArraySlice = "ArraySlice",
1717
ArraySome = "ArraySome",
1818
ArraySplice = "ArraySplice",
19+
ClassIndex = "ClassIndex",
20+
ClassNewIndex = "ClassNewIndex",
1921
FunctionApply = "FunctionApply",
2022
FunctionBind = "FunctionBind",
2123
FunctionCall = "FunctionCall",

src/LuaTransformer.ts

Lines changed: 137 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -426,9 +426,8 @@ export class LuaTransformer {
426426
const properties = statement.members.filter(ts.isPropertyDeclaration).filter(member => member.initializer);
427427

428428
// Divide properties into static and non-static
429-
const isStatic = prop => prop.modifiers && prop.modifiers.some(m => m.kind === ts.SyntaxKind.StaticKeyword);
430-
const staticFields = properties.filter(isStatic);
431-
const instanceFields = properties.filter(prop => !isStatic(prop));
429+
const staticFields = properties.filter(tsHelper.isStatic);
430+
const instanceFields = properties.filter(prop => !tsHelper.isStatic(prop));
432431

433432
const result: tstl.Statement[] = [];
434433

@@ -605,6 +604,22 @@ export class LuaTransformer {
605604

606605
const createClassNameWithExport = () => this.addExportToIdentifier(tstl.cloneIdentifier(className));
607606

607+
// className.____getters = {}
608+
if (statement.members.some(m => ts.isGetAccessor(m) && tsHelper.isStatic(m))) {
609+
const classGetters = tstl.createTableIndexExpression(
610+
createClassNameWithExport(),
611+
tstl.createStringLiteral("____getters")
612+
);
613+
const assignClassGetters = tstl.createAssignmentStatement(
614+
classGetters,
615+
tstl.createTableExpression(),
616+
statement
617+
);
618+
result.push(assignClassGetters);
619+
620+
this.importLuaLibFeature(LuaLibFeature.ClassIndex);
621+
}
622+
608623
// className.__index = className
609624
const classIndex = tstl.createTableIndexExpression(
610625
createClassNameWithExport(),
@@ -613,6 +628,22 @@ export class LuaTransformer {
613628
const assignClassIndex = tstl.createAssignmentStatement(classIndex, createClassNameWithExport(), statement);
614629
result.push(assignClassIndex);
615630

631+
// className.____setters = {}
632+
if (statement.members.some(m => ts.isSetAccessor(m) && tsHelper.isStatic(m))) {
633+
const classSetters = tstl.createTableIndexExpression(
634+
createClassNameWithExport(),
635+
tstl.createStringLiteral("____setters")
636+
);
637+
const assignClassSetters = tstl.createAssignmentStatement(
638+
classSetters,
639+
tstl.createTableExpression(),
640+
statement
641+
);
642+
result.push(assignClassSetters);
643+
644+
this.importLuaLibFeature(LuaLibFeature.ClassNewIndex);
645+
}
646+
616647
// className.prototype = className.prototype or {}
617648
const createClassPrototype = () => tstl.createTableIndexExpression(
618649
createClassNameWithExport(),
@@ -628,12 +659,8 @@ export class LuaTransformer {
628659
const assignClassPrototype = tstl.createAssignmentStatement(createClassPrototype(), classPrototypeTable);
629660
result.push(assignClassPrototype);
630661

631-
const classPrototypeIndex = tstl.createTableIndexExpression(
632-
createClassPrototype(),
633-
tstl.createStringLiteral("__index")
634-
);
635-
if (tsHelper.hasGetAccessorInClassOrAncestor(statement, this.checker)) {
636-
// className.prototype.____getters = {}
662+
// className.prototype.____getters = {}
663+
if (statement.members.some(m => ts.isGetAccessor(m) && !tsHelper.isStatic(m))) {
637664
const classPrototypeGetters = tstl.createTableIndexExpression(
638665
createClassPrototype(),
639666
tstl.createStringLiteral("____getters")
@@ -643,7 +670,13 @@ export class LuaTransformer {
643670
tstl.createTableExpression()
644671
);
645672
result.push(assignClassPrototypeGetters);
673+
}
646674

675+
const classPrototypeIndex = tstl.createTableIndexExpression(
676+
createClassPrototype(),
677+
tstl.createStringLiteral("__index")
678+
);
679+
if (tsHelper.hasGetAccessorInClassOrAncestor(statement, false, this.checker)) {
647680
// className.prototype.__index = __TS_Index(className.prototype)
648681
const assignClassPrototypeIndex = tstl.createAssignmentStatement(
649682
classPrototypeIndex,
@@ -660,7 +693,7 @@ export class LuaTransformer {
660693
result.push(assignClassPrototypeIndex);
661694
}
662695

663-
if (tsHelper.hasSetAccessorInClassOrAncestor(statement, this.checker)) {
696+
if (statement.members.some(m => ts.isSetAccessor(m) && !tsHelper.isStatic(m))) {
664697
// className.prototype.____setters = {}
665698
const classPrototypeSetters = tstl.createTableIndexExpression(
666699
createClassPrototype(),
@@ -671,7 +704,9 @@ export class LuaTransformer {
671704
tstl.createTableExpression()
672705
);
673706
result.push(assignClassPrototypeSetters);
707+
}
674708

709+
if (tsHelper.hasSetAccessorInClassOrAncestor(statement, false, this.checker)) {
675710
// className.prototype.__newindex = __TS_NewIndex(className.prototype)
676711
const classPrototypeNewIndex = tstl.createTableIndexExpression(
677712
createClassPrototype(),
@@ -696,6 +731,9 @@ export class LuaTransformer {
696731
);
697732
result.push(assignClassPrototypeConstructor);
698733

734+
const hasStaticGetters = tsHelper.hasGetAccessorInClassOrAncestor(statement, true, this.checker);
735+
const hasStaticSetters = tsHelper.hasSetAccessorInClassOrAncestor(statement, true, this.checker);
736+
699737
if (extendsType) {
700738
const extendedTypeNode = tsHelper.getExtendedTypeNode(statement, this.checker);
701739
const baseName = this.transformExpression(extendedTypeNode.expression);
@@ -708,14 +746,51 @@ export class LuaTransformer {
708746
const assignClassBase = tstl.createAssignmentStatement(createClassBase(), baseName, statement);
709747
result.push(assignClassBase);
710748

711-
// setmetatable(className, className.____super)
712-
const setClassMetatable = tstl.createExpressionStatement(
713-
tstl.createCallExpression(
714-
tstl.createIdentifier("setmetatable"),
715-
[createClassNameWithExport(), createClassBase()]
716-
)
717-
);
718-
result.push(setClassMetatable);
749+
if (hasStaticGetters || hasStaticSetters) {
750+
const metatableFields: tstl.TableFieldExpression[] = [];
751+
if (hasStaticGetters) {
752+
// __index = __TS__ClassIndex
753+
metatableFields.push(
754+
tstl.createTableFieldExpression(
755+
tstl.createIdentifier("__TS__ClassIndex"),
756+
tstl.createStringLiteral("__index")
757+
)
758+
);
759+
} else {
760+
// __index = className.____super
761+
metatableFields.push(
762+
tstl.createTableFieldExpression(createClassBase(), tstl.createStringLiteral("__index"))
763+
);
764+
}
765+
766+
if (hasStaticSetters) {
767+
// __newindex = __TS__ClassNewIndex
768+
metatableFields.push(
769+
tstl.createTableFieldExpression(
770+
tstl.createIdentifier("__TS__ClassNewIndex"),
771+
tstl.createStringLiteral("__newindex")
772+
)
773+
);
774+
}
775+
776+
const setClassMetatable = tstl.createExpressionStatement(
777+
tstl.createCallExpression(
778+
tstl.createIdentifier("setmetatable"),
779+
[createClassNameWithExport(), tstl.createTableExpression(metatableFields)]
780+
)
781+
);
782+
result.push(setClassMetatable);
783+
784+
} else {
785+
// setmetatable(className, className.____super)
786+
const setClassMetatable = tstl.createExpressionStatement(
787+
tstl.createCallExpression(
788+
tstl.createIdentifier("setmetatable"),
789+
[createClassNameWithExport(), createClassBase()]
790+
)
791+
);
792+
result.push(setClassMetatable);
793+
}
719794

720795
// setmetatable(className.prototype, className.____super.prototype)
721796
const basePrototype = tstl.createTableIndexExpression(
@@ -729,6 +804,36 @@ export class LuaTransformer {
729804
)
730805
);
731806
result.push(setClassPrototypeMetatable);
807+
808+
} else if (hasStaticGetters || hasStaticSetters) {
809+
const metatableFields: tstl.TableFieldExpression[] = [];
810+
if (hasStaticGetters) {
811+
// __index = __TS__ClassIndex
812+
metatableFields.push(
813+
tstl.createTableFieldExpression(
814+
tstl.createIdentifier("__TS__ClassIndex"),
815+
tstl.createStringLiteral("__index")
816+
)
817+
);
818+
}
819+
820+
if (hasStaticSetters) {
821+
// __newindex = __TS__ClassNewIndex
822+
metatableFields.push(
823+
tstl.createTableFieldExpression(
824+
tstl.createIdentifier("__TS__ClassNewIndex"),
825+
tstl.createStringLiteral("__newindex")
826+
)
827+
);
828+
}
829+
830+
const setClassMetatable = tstl.createExpressionStatement(
831+
tstl.createCallExpression(
832+
tstl.createIdentifier("setmetatable"),
833+
[createClassNameWithExport(), tstl.createTableExpression(metatableFields)]
834+
)
835+
);
836+
result.push(setClassMetatable);
732837
}
733838

734839
const newFuncStatements: tstl.Statement[] = [];
@@ -904,12 +1009,13 @@ export class LuaTransformer {
9041009
[this.createSelfIdentifier()]
9051010
);
9061011

907-
const classPrototype = tstl.createTableIndexExpression(
908-
this.addExportToIdentifier(tstl.cloneIdentifier(className)),
909-
tstl.createStringLiteral("prototype")
910-
);
1012+
const classNameWithExport = this.addExportToIdentifier(tstl.cloneIdentifier(className));
1013+
const methodTable = tsHelper.isStatic(getAccessor)
1014+
? classNameWithExport
1015+
: tstl.createTableIndexExpression(classNameWithExport, tstl.createStringLiteral("prototype"));
1016+
9111017
const classGetters = tstl.createTableIndexExpression(
912-
classPrototype,
1018+
methodTable,
9131019
tstl.createStringLiteral("____getters")
9141020
);
9151021
const getter = tstl.createTableIndexExpression(
@@ -938,12 +1044,13 @@ export class LuaTransformer {
9381044
restParam
9391045
);
9401046

941-
const classPrototype = tstl.createTableIndexExpression(
942-
this.addExportToIdentifier(tstl.cloneIdentifier(className)),
943-
tstl.createStringLiteral("prototype")
944-
);
1047+
const classNameWithExport = this.addExportToIdentifier(tstl.cloneIdentifier(className));
1048+
const methodTable = tsHelper.isStatic(setAccessor)
1049+
? classNameWithExport
1050+
: tstl.createTableIndexExpression(classNameWithExport, tstl.createStringLiteral("prototype"));
1051+
9451052
const classSetters = tstl.createTableIndexExpression(
946-
classPrototype,
1053+
methodTable,
9471054
tstl.createStringLiteral("____setters")
9481055
);
9491056
const setter = tstl.createTableIndexExpression(
@@ -957,7 +1064,7 @@ export class LuaTransformer {
9571064
public transformMethodDeclaration(
9581065
node: ts.MethodDeclaration,
9591066
className: tstl.Identifier,
960-
usePrototype: boolean
1067+
noPrototype: boolean
9611068
): tstl.AssignmentStatement
9621069
{
9631070
// Don't transform methods without body (overload declarations)
@@ -984,9 +1091,8 @@ export class LuaTransformer {
9841091
restParamName
9851092
);
9861093

987-
const isStatic = node.modifiers && node.modifiers.some(m => m.kind === ts.SyntaxKind.StaticKeyword);
9881094
const classNameWithExport = this.addExportToIdentifier(tstl.cloneIdentifier(className));
989-
const methodTable = isStatic || usePrototype
1095+
const methodTable = tsHelper.isStatic(node) || noPrototype
9901096
? classNameWithExport
9911097
: tstl.createTableIndexExpression(classNameWithExport, tstl.createStringLiteral("prototype"));
9921098

src/TSHelper.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,10 @@ export class TSHelper {
131131
return false;
132132
}
133133

134+
public static isStatic(node: ts.Node): boolean {
135+
return node.modifiers !== undefined && node.modifiers.some(m => m.kind === ts.SyntaxKind.StaticKeyword);
136+
}
137+
134138
public static isStringType(type: ts.Type): boolean {
135139
return (type.flags & ts.TypeFlags.String) !== 0 || (type.flags & ts.TypeFlags.StringLike) !== 0 ||
136140
(type.flags & ts.TypeFlags.StringLiteral) !== 0;
@@ -375,24 +379,26 @@ export class TSHelper {
375379

376380
public static hasSetAccessorInClassOrAncestor(
377381
classDeclaration: ts.ClassLikeDeclarationBase,
382+
isStatic: boolean,
378383
checker: ts.TypeChecker
379384
): boolean
380385
{
381386
return TSHelper.findInClassOrAncestor(
382387
classDeclaration,
383-
c => c.members.some(ts.isSetAccessor),
388+
c => c.members.some(m => ts.isSetAccessor(m) && TSHelper.isStatic(m) === isStatic),
384389
checker
385390
) !== undefined;
386391
}
387392

388393
public static hasGetAccessorInClassOrAncestor(
389394
classDeclaration: ts.ClassLikeDeclarationBase,
395+
isStatic: boolean,
390396
checker: ts.TypeChecker
391397
): boolean
392398
{
393399
return TSHelper.findInClassOrAncestor(
394400
classDeclaration,
395-
c => c.members.some(ts.isGetAccessor),
401+
c => c.members.some(m => ts.isGetAccessor(m) && TSHelper.isStatic(m) === isStatic),
396402
checker
397403
) !== undefined;
398404
}
@@ -417,7 +423,7 @@ export class TSHelper {
417423
checker: ts.TypeChecker
418424
): element is ts.GetAccessorDeclaration
419425
{
420-
if (!ts.isGetAccessor(element)) {
426+
if (!ts.isGetAccessor(element) || TSHelper.isStatic(element)) {
421427
return false;
422428
}
423429

src/lualib/ClassIndex.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
interface LuaClass {
2+
____super?: LuaClass;
3+
____getters?: { [key: string]: (self: LuaClass) => any };
4+
}
5+
6+
declare function rawget<T, K extends keyof T>(obj: T, key: K): T[K];
7+
8+
function __TS__ClassIndex(classTable: LuaClass, key: keyof LuaClass): any {
9+
while (true) {
10+
const getters = rawget(classTable, "____getters");
11+
if (getters) {
12+
const getter = getters[key];
13+
if (getter) {
14+
return getter(classTable);
15+
}
16+
}
17+
18+
classTable = rawget(classTable, "____super");
19+
if (!classTable) {
20+
break;
21+
}
22+
23+
const val = rawget(classTable, key);
24+
if (val !== null) {
25+
return val;
26+
}
27+
}
28+
}

src/lualib/ClassNewIndex.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
interface LuaClass {
2+
____super?: LuaClass;
3+
____setters?: { [key: string]: (self: LuaClass, val: any) => void };
4+
}
5+
6+
declare function rawget<T, K extends keyof T>(obj: T, key: K): T[K];
7+
declare function rawset<T, K extends keyof T>(obj: T, key: K, val: T[K]): void;
8+
9+
function __TS__ClassNewIndex(classTable: LuaClass, key: keyof LuaClass, val: any): void {
10+
let tbl = classTable;
11+
do {
12+
const setters = rawget(tbl, "____setters");
13+
if (setters) {
14+
const setter = setters[key];
15+
if (setter) {
16+
setter(tbl, val);
17+
return;
18+
}
19+
}
20+
21+
tbl = rawget(tbl, "____super");
22+
}
23+
while (tbl);
24+
25+
rawset(classTable, key, val);
26+
}

0 commit comments

Comments
 (0)