Skip to content

Commit 39c0c51

Browse files
tomblindPerryvw
authored andcommitted
New functions (TypeScriptToLua#264)
* initial re-work of function transpiling, including NoContext decorator * added lib function for bind() * decorated lib functions with NoContext * added apply and call lib functions * checking type aliases for custom decorators to fix tslint issues with new lib functions
1 parent e310368 commit 39c0c51

49 files changed

Lines changed: 286 additions & 120 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/Decorator.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@ export enum DecoratorKind {
2121
Phantom = "Phantom",
2222
TupleReturn = "TupleReturn",
2323
NoClassOr = "NoClassOr",
24+
NoContext = "NoContext",
2425
}

src/TSHelper.ts

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ export class TSHelper {
7979
return typeNode && this.isArrayTypeNode(typeNode);
8080
}
8181

82+
public static isFunctionType(type: ts.Type, checker: ts.TypeChecker): boolean {
83+
const typeNode = checker.typeToTypeNode(type, undefined, ts.NodeBuilderFlags.InTypeAlias);
84+
return typeNode && ts.isFunctionTypeNode(typeNode);
85+
}
86+
8287
public static isTupleReturnCall(node: ts.Node, checker: ts.TypeChecker): boolean {
8388
if (ts.isCallExpression(node)) {
8489
const type = checker.getTypeAtLocation(node.expression);
@@ -104,22 +109,29 @@ export class TSHelper {
104109
}
105110
}
106111

112+
public static collectCustomDecorators(symbol: ts.Symbol, checker: ts.TypeChecker,
113+
decMap: Map<DecoratorKind, Decorator>): void {
114+
const comments = symbol.getDocumentationComment(checker);
115+
const decorators =
116+
comments.filter(comment => comment.kind === "text")
117+
.map(comment => comment.text.trim().split("\n"))
118+
.reduce((a, b) => a.concat(b), [])
119+
.filter(comment => comment[0] === "!");
120+
decorators.forEach(decStr => {
121+
const dec = new Decorator(decStr);
122+
decMap.set(dec.kind, dec);
123+
});
124+
}
125+
107126
public static getCustomDecorators(type: ts.Type, checker: ts.TypeChecker): Map<DecoratorKind, Decorator> {
127+
const decMap = new Map<DecoratorKind, Decorator>();
108128
if (type.symbol) {
109-
const comments = type.symbol.getDocumentationComment(checker);
110-
const decorators =
111-
comments.filter(comment => comment.kind === "text")
112-
.map(comment => comment.text.trim().split("\n"))
113-
.reduce((a, b) => a.concat(b), [])
114-
.filter(comment => comment[0] === "!");
115-
const decMap = new Map<DecoratorKind, Decorator>();
116-
decorators.forEach(decStr => {
117-
const dec = new Decorator(decStr);
118-
decMap.set(dec.kind, dec);
119-
});
120-
return decMap;
129+
this.collectCustomDecorators(type.symbol, checker, decMap);
130+
}
131+
if (type.aliasSymbol) {
132+
this.collectCustomDecorators(type.aliasSymbol, checker, decMap);
121133
}
122-
return new Map<DecoratorKind, Decorator>();
134+
return decMap;
123135
}
124136

125137
// Search up until finding a node satisfying the callback

src/Transpiler.ts

Lines changed: 68 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ export enum LuaLibFeature {
3333
ArraySlice = "ArraySlice",
3434
ArraySome = "ArraySome",
3535
ArraySplice = "ArraySplice",
36+
FunctionApply = "FunctionApply",
37+
FunctionBind = "FunctionBind",
38+
FunctionCall = "FunctionCall",
3639
InstanceOf = "InstanceOf",
3740
Map = "Map",
3841
Set = "Set",
@@ -784,8 +787,9 @@ export abstract class LuaTranspiler {
784787
case ts.SyntaxKind.DeleteExpression:
785788
return this.transpileExpression((node as ts.DeleteExpression).expression) + "=nil";
786789
case ts.SyntaxKind.FunctionExpression:
790+
return this.transpileFunctionExpression(node as ts.ArrowFunction, "self");
787791
case ts.SyntaxKind.ArrowFunction:
788-
return this.transpileFunctionExpression(node as ts.ArrowFunction);
792+
return this.transpileFunctionExpression(node as ts.ArrowFunction, "_");
789793
case ts.SyntaxKind.NewExpression:
790794
return this.transpileNewExpression(node as ts.NewExpression);
791795
case ts.SyntaxKind.ComputedPropertyName:
@@ -1136,7 +1140,7 @@ export abstract class LuaTranspiler {
11361140

11371141
public transpileNewExpression(node: ts.NewExpression): string {
11381142
const name = this.transpileExpression(node.expression);
1139-
const params = node.arguments ? this.transpileArguments(node.arguments, ts.createTrue()) : "true";
1143+
let params = node.arguments ? this.transpileArguments(node.arguments, ts.createTrue()) : "true";
11401144
const type = this.checker.getTypeAtLocation(node);
11411145
const classDecorators = tsHelper.getCustomDecorators(type, this.checker);
11421146

@@ -1151,7 +1155,14 @@ export abstract class LuaTranspiler {
11511155
if (!customDecorator.args[0]) {
11521156
throw TSTLErrors.InvalidDecoratorArgumentNumber("!CustomConstructor", 0, 1, node);
11531157
}
1154-
return `${customDecorator.args[0]}(${this.transpileArguments(node.arguments)})`;
1158+
if (!ts.isPropertyAccessExpression(node.expression)
1159+
&& !ts.isElementAccessExpression(node.expression)
1160+
&& !tsHelper.getCustomDecorators(type, this.checker).has(DecoratorKind.NoContext)) {
1161+
params = this.transpileArguments(node.arguments, ts.createIdentifier("_G"));
1162+
} else {
1163+
params = this.transpileArguments(node.arguments);
1164+
}
1165+
return `${customDecorator.args[0]}(${params})`;
11551166
}
11561167

11571168
return `${name}.new(${params})`;
@@ -1182,7 +1193,14 @@ export abstract class LuaTranspiler {
11821193
}
11831194

11841195
callPath = this.transpileExpression(node.expression);
1185-
params = this.transpileArguments(node.arguments);
1196+
const type = this.checker.getTypeAtLocation(node.expression);
1197+
if (!ts.isPropertyAccessExpression(node.expression)
1198+
&& !ts.isElementAccessExpression(node.expression)
1199+
&& !tsHelper.getCustomDecorators(type, this.checker).has(DecoratorKind.NoContext)) {
1200+
params = this.transpileArguments(node.arguments, ts.createIdentifier("_G"));
1201+
} else {
1202+
params = this.transpileArguments(node.arguments);
1203+
}
11861204
return isTupleReturn && !isTupleReturnForward && !isInDestructingAssignment && returnValueIsUsed
11871205
? `({ ${callPath}(${params}) })` : `${callPath}(${params})`;
11881206
}
@@ -1220,16 +1238,12 @@ export abstract class LuaTranspiler {
12201238
return this.transpileArrayCallExpression(node);
12211239
}
12221240

1241+
if (tsHelper.isFunctionType(ownerType, this.checker)) {
1242+
return this.transpileFunctionCallExpression(node);
1243+
}
1244+
12231245
// Get the type of the function
1224-
const functionType = this.checker.getTypeAtLocation(node.expression);
1225-
// Don't replace . with : for namespaces or functions defined as properties with lambdas
1226-
if ((functionType.symbol && !(functionType.symbol.flags & ts.SymbolFlags.Method))
1227-
// Check explicitly for method calls on 'this', since they don't have the Method flag set
1228-
|| (node.expression.expression.kind === ts.SyntaxKind.ThisType)) {
1229-
callPath = this.transpileExpression(node.expression);
1230-
params = this.transpileArguments(node.arguments);
1231-
return `${callPath}(${params})`;
1232-
} else if (node.expression.expression.kind === ts.SyntaxKind.SuperKeyword) {
1246+
if (node.expression.expression.kind === ts.SyntaxKind.SuperKeyword) {
12331247
// Super calls take the format of super.call(self,...)
12341248
params = this.transpileArguments(node.arguments, ts.createNode(ts.SyntaxKind.ThisKeyword) as ts.Expression);
12351249
return `${this.transpileExpression(node.expression)}(${params})`;
@@ -1243,8 +1257,9 @@ export abstract class LuaTranspiler {
12431257
params = this.transpileArguments(node.arguments);
12441258
return `(rawget(${expr}, ${params} )~=nil)`;
12451259
} else {
1246-
callPath =
1247-
`${this.transpileExpression(node.expression.expression)}:${name}`;
1260+
const type = this.checker.getTypeAtLocation(node.expression);
1261+
const op = tsHelper.getCustomDecorators(type, this.checker).has(DecoratorKind.NoContext) ? "." : ":";
1262+
callPath = `${this.transpileExpression(node.expression.expression)}${op}${name}`;
12481263
params = this.transpileArguments(node.arguments);
12491264
return `${callPath}(${params})`;
12501265
}
@@ -1364,6 +1379,23 @@ export abstract class LuaTranspiler {
13641379
}
13651380
}
13661381

1382+
public transpileFunctionCallExpression(node: ts.CallExpression): string {
1383+
const expression = node.expression as ts.PropertyAccessExpression;
1384+
const params = this.transpileArguments(node.arguments);
1385+
const caller = this.transpileExpression(expression.expression);
1386+
const expressionName = this.transpileIdentifier(expression.name);
1387+
switch (expressionName) {
1388+
case "apply":
1389+
return this.transpileLuaLibFunction(LuaLibFeature.FunctionApply, caller, params);
1390+
case "bind":
1391+
return this.transpileLuaLibFunction(LuaLibFeature.FunctionBind, caller, params);
1392+
case "call":
1393+
return this.transpileLuaLibFunction(LuaLibFeature.FunctionCall, caller, params);
1394+
default:
1395+
throw TSTLErrors.UnsupportedProperty("function", expressionName, node);
1396+
}
1397+
}
1398+
13671399
public transpileArguments(params: ts.NodeArray<ts.Expression>, context?: ts.Expression): string {
13681400
const parameters: string[] = [];
13691401

@@ -1596,7 +1628,9 @@ export abstract class LuaTranspiler {
15961628
let result = "";
15971629
const methodName = this.transpileIdentifier(node.name);
15981630

1599-
const [paramNames, spreadIdentifier] = this.transpileParameters(node.parameters);
1631+
const type = this.checker.getTypeAtLocation(node);
1632+
const context = tsHelper.getCustomDecorators(type, this.checker).has(DecoratorKind.NoContext) ? null : "self";
1633+
const [paramNames, spreadIdentifier] = this.transpileParameters(node.parameters, context);
16001634

16011635
let prefix = this.accessPrefix(node);
16021636

@@ -1620,9 +1654,13 @@ export abstract class LuaTranspiler {
16201654
}
16211655

16221656
// Transpile a list of parameters, returns a list of transpiled parameters and an optional spread identifier
1623-
public transpileParameters(parameters: ts.NodeArray<ts.ParameterDeclaration>): [string[], string] {
1657+
public transpileParameters(parameters: ts.NodeArray<ts.ParameterDeclaration>, context: string | null)
1658+
: [string[], string] {
16241659
// Build parameter string
16251660
const paramNames: string[] = [];
1661+
if (context) {
1662+
paramNames.push(context);
1663+
}
16261664

16271665
let spreadIdentifier = "";
16281666

@@ -1673,12 +1711,12 @@ export abstract class LuaTranspiler {
16731711
methodName = "__tostring";
16741712
}
16751713

1676-
const [paramNames, spreadIdentifier] = this.transpileParameters(node.parameters);
1677-
1678-
const selfParamNames = ["self"].concat(paramNames);
1714+
const type = this.checker.getTypeAtLocation(node);
1715+
const context = tsHelper.getCustomDecorators(type, this.checker).has(DecoratorKind.NoContext) ? null : "self";
1716+
const [paramNames, spreadIdentifier] = this.transpileParameters(node.parameters, context);
16791717

16801718
// Build function header
1681-
result += this.indent + `function ${callPath}${methodName}(${selfParamNames.join(",")})\n`;
1719+
result += this.indent + `function ${callPath}${methodName}(${paramNames.join(",")})\n`;
16821720

16831721
this.pushIndent();
16841722
result += this.transpileFunctionBody(node.parameters, node.body, spreadIdentifier);
@@ -1932,7 +1970,7 @@ export abstract class LuaTranspiler {
19321970
} else if (ts.isShorthandPropertyAssignment(element)) {
19331971
properties.push(`${name} = ${name}`);
19341972
} else if (ts.isMethodDeclaration(element)) {
1935-
const expression = this.transpileFunctionExpression(element);
1973+
const expression = this.transpileFunctionExpression(element, "self");
19361974
properties.push(`${name} = ${expression}`);
19371975
} else {
19381976
throw TSTLErrors.UnsupportedKind("object literal element", element.kind, node);
@@ -1942,30 +1980,15 @@ export abstract class LuaTranspiler {
19421980
return "{" + properties.join(",") + "}";
19431981
}
19441982

1945-
public transpileFunctionExpression(node: ts.FunctionLikeDeclaration): string {
1983+
public transpileFunctionExpression(node: ts.FunctionLikeDeclaration, context: string | null): string {
19461984
// Build parameter string
1947-
const paramNames: string[] = [];
1948-
if (ts.isMethodDeclaration(node)) {
1949-
paramNames.push("self");
1950-
}
1951-
node.parameters.forEach(param => {
1952-
paramNames.push(this.transpileIdentifier(param.name as ts.Identifier));
1953-
});
1954-
1955-
const defaultValueParams = node.parameters.filter(declaration => declaration.initializer !== undefined);
1956-
1957-
if (ts.isBlock(node.body) || defaultValueParams.length > 0) {
1958-
let result = `function(${paramNames.join(",")})\n`;
1959-
this.pushIndent();
1960-
result += this.transpileParameterDefaultValues(defaultValueParams);
1961-
result += this.transpileBlock(node.body as ts.Block);
1962-
this.popIndent();
1963-
return result + this.indent + "end\n";
1964-
} else {
1965-
// Transpile as return value
1966-
const returnVal = this.transpileReturn(ts.createReturn(node.body));
1967-
return `function(${paramNames.join(",")}) ${returnVal} end`;
1968-
}
1985+
const [paramNames, spreadIdentifier] = this.transpileParameters(node.parameters, context);
1986+
let result = `function(${paramNames.join(",")})\n`;
1987+
this.pushIndent();
1988+
const body = ts.isBlock(node.body) ? node.body : ts.createBlock([ts.createReturn(node.body)]);
1989+
result += this.transpileFunctionBody(node.parameters, body, spreadIdentifier);
1990+
this.popIndent();
1991+
return result + this.indent + "end";
19691992
}
19701993

19711994
public transpileParameterDefaultValues(params: ts.ParameterDeclaration[]): string {

src/lualib/ArrayConcat.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
/** !NoContext */
12
declare function pcall(func: () => any): any;
3+
/** !NoContext */
24
declare function type(val: any): string;
35

6+
/** !NoContext */
47
function __TS__ArrayConcat(arr1: any[], ...args: any[]): any[] {
58
const out: any[] = [];
69
for (const val of arr1) {

src/lualib/ArrayEvery.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/** !NoContext */
12
function __TS__ArrayEvery<T>(arr: T[], callbackfn: (value: T, index?: number, array?: any[]) => boolean): boolean {
23
for (let i = 0; i < arr.length; i++) {
34
if (!callbackfn(arr[i], i, arr)) {

src/lualib/ArrayFilter.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/** !NoContext */
12
function __TS__ArrayFilter<T>(arr: T[], callbackfn: (value: T, index?: number, array?: any[]) => boolean): T[] {
23
const result: T[] = [];
34
for (let i = 0; i < arr.length; i++) {

src/lualib/ArrayForEach.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/** !NoContext */
12
function __TS__ArrayForEach<T>(arr: T[], callbackFn: (value: T, index?: number, array?: any[]) => any): void {
23
for (let i = 0; i < arr.length; i++) {
34
callbackFn(arr[i], i, arr);

src/lualib/ArrayIndexOf.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/** !NoContext */
12
function __TS__ArrayIndexOf<T>(arr: T[], searchElement: T, fromIndex?: number): number {
23
const len = arr.length;
34
if (len === 0) {

src/lualib/ArrayMap.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/** !NoContext */
12
function __TS__ArrayMap<T, U>(arr: T[], callbackfn: (value: T, index?: number, array?: T[]) => U): U[] {
23
const newArray: U[] = [];
34
for (let i = 0; i < arr.length; i++) {

src/lualib/ArrayPush.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/** !NoContext */
12
function __TS__ArrayPush<T>(arr: T[], ...items: T[]): number {
23
for (const item of items) {
34
arr[arr.length] = item;

0 commit comments

Comments
 (0)