X Tutup
Skip to content

Commit 515e5de

Browse files
tomblindPerryvw
authored andcommitted
New functions (#283)
* passing nil instead of _G as context for global functions when in ES strict mode * fixed logic for determining strict mode * replaced hack-around when passing nil as a function context with a null keyword * testing viability of wrapping context/no-context calls on assignment * working on more function assignment situations * fixed getting constructor signature and refactored things a bit * checking resolved signature when comparing function types passed as arguments * working on assignment checks for methods vs functions * handling context in calls and decls * refactoring and handling tuple destructuring * generalized tuple assignment checking * overloads with function and method signatures default to functions now * preventing non-methods from being passed to bind/call/apply * removed uneccessary helpers * using proper exceptions for function conversion errors * removed context arg from custom constructors and added check for assigning to untyped vars * updated tests * removing leftover NoContext decorators
1 parent bc64913 commit 515e5de

Some content is hidden

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

47 files changed

+178
-127
lines changed

src/Decorator.ts

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

src/Errors.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,10 @@ export class TSTLErrors {
6868

6969
public static UnsupportedObjectLiteralElement = (elementKind: ts.SyntaxKind, node: ts.Node) =>
7070
new TranspileError(`Unsupported object literal element: ${elementKind}.`, node)
71+
72+
public static UnsupportedFunctionConversion = (node: ts.Node) =>
73+
new TranspileError(`Unsupported conversion from method to function.`, node)
74+
75+
public static UnsupportedMethodConversion = (node: ts.Node) =>
76+
new TranspileError(`Unsupported conversion from function to method.`, node)
7177
}

src/TSHelper.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,4 +248,38 @@ export class TSHelper {
248248
}
249249
return [false, null, null];
250250
}
251+
252+
public static isDeclarationWithContext(sigDecl: ts.SignatureDeclaration, checker: ts.TypeChecker): boolean {
253+
const thisArg = sigDecl.parameters.find(p => ts.isIdentifier(p.name)
254+
&& p.name.originalKeywordKind === ts.SyntaxKind.ThisKeyword);
255+
if (thisArg) {
256+
// Explicit 'this'
257+
return !thisArg.type || thisArg.type.kind !== ts.SyntaxKind.VoidKeyword;
258+
}
259+
if ((ts.isMethodDeclaration(sigDecl) || ts.isMethodSignature(sigDecl))
260+
&& !(ts.getCombinedModifierFlags(sigDecl) & ts.ModifierFlags.Static)) {
261+
// Non-static method
262+
return true;
263+
}
264+
if ((ts.isPropertySignature(sigDecl.parent) || ts.isPropertyDeclaration(sigDecl.parent))
265+
&& !(ts.getCombinedModifierFlags(sigDecl.parent) & ts.ModifierFlags.Static)) {
266+
// Non-static lambda property
267+
return true;
268+
}
269+
if (ts.isBinaryExpression(sigDecl.parent)
270+
&& this.isFunctionWithContext(checker.getTypeAtLocation(sigDecl.parent.left), checker)) {
271+
// Function expression: check type being assigned to
272+
return true;
273+
}
274+
return false;
275+
}
276+
277+
public static isFunctionWithContext(type: ts.Type, checker: ts.TypeChecker): boolean {
278+
const sigs = checker.getSignaturesOfType(type, ts.SignatureKind.Call);
279+
if (sigs.length === 0) {
280+
return false;
281+
}
282+
const sigDecls = sigs.map(s => s.getDeclaration());
283+
return sigDecls.every(s => this.isDeclarationWithContext(s, checker));
284+
}
251285
}

src/Transpiler.ts

Lines changed: 85 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -942,8 +942,12 @@ export abstract class LuaTranspiler {
942942
return this.transpileSetAccessor(node.left as ts.PropertyAccessExpression, rhs);
943943
}
944944

945+
// Validate assignment
946+
const rightType = this.checker.getTypeAtLocation(node.right);
947+
const leftType = this.checker.getTypeAtLocation(node.left);
948+
this.validateAssignment(node.right, rightType, leftType);
949+
945950
if (ts.isArrayLiteralExpression(node.left)) {
946-
// Destructing assignment
947951
const vars = node.left.elements.map(e => this.transpileExpression(e)).join(",");
948952
const vals = tsHelper.isTupleReturnCall(node.right, this.checker)
949953
? rhs : this.transpileDestructingAssignmentValue(node.right);
@@ -1144,7 +1148,8 @@ export abstract class LuaTranspiler {
11441148

11451149
public transpileNewExpression(node: ts.NewExpression): string {
11461150
const name = this.transpileExpression(node.expression);
1147-
let params = node.arguments ? this.transpileArguments(node.arguments, ts.createTrue()) : "true";
1151+
const sig = this.checker.getResolvedSignature(node);
1152+
const params = node.arguments ? this.transpileArguments(node.arguments, sig, ts.createTrue()) : "true";
11481153
const type = this.checker.getTypeAtLocation(node);
11491154
const classDecorators = tsHelper.getCustomDecorators(type, this.checker);
11501155

@@ -1159,15 +1164,7 @@ export abstract class LuaTranspiler {
11591164
if (!customDecorator.args[0]) {
11601165
throw TSTLErrors.InvalidDecoratorArgumentNumber("!CustomConstructor", 0, 1, node);
11611166
}
1162-
if (!ts.isPropertyAccessExpression(node.expression)
1163-
&& !ts.isElementAccessExpression(node.expression)
1164-
&& !tsHelper.getCustomDecorators(type, this.checker).has(DecoratorKind.NoContext)) {
1165-
const context = this.isStrict ? ts.createNull() : ts.createIdentifier("_G");
1166-
params = this.transpileArguments(node.arguments, context);
1167-
} else {
1168-
params = this.transpileArguments(node.arguments);
1169-
}
1170-
return `${customDecorator.args[0]}(${params})`;
1167+
return `${customDecorator.args[0]}(${this.transpileArguments(node.arguments)})`;
11711168
}
11721169

11731170
return `${name}.new(${params})`;
@@ -1190,22 +1187,25 @@ export abstract class LuaTranspiler {
11901187
? `({ ${result} })` : result;
11911188
}
11921189

1190+
const sig = this.checker.getResolvedSignature(node);
1191+
11931192
// Handle super calls properly
11941193
if (node.expression.kind === ts.SyntaxKind.SuperKeyword) {
1195-
params = this.transpileArguments(node.arguments, ts.createNode(ts.SyntaxKind.ThisKeyword) as ts.Expression);
1194+
params = this.transpileArguments(node.arguments, sig,
1195+
ts.createNode(ts.SyntaxKind.ThisKeyword) as ts.Expression);
11961196
const className = this.classStack[this.classStack.length - 1];
11971197
return `${className}.__base.constructor(${params})`;
11981198
}
11991199

1200-
callPath = this.transpileExpression(node.expression);
12011200
const type = this.checker.getTypeAtLocation(node.expression);
1202-
if (!ts.isPropertyAccessExpression(node.expression)
1203-
&& !ts.isElementAccessExpression(node.expression)
1204-
&& !tsHelper.getCustomDecorators(type, this.checker).has(DecoratorKind.NoContext)) {
1201+
callPath = this.transpileExpression(node.expression);
1202+
if (tsHelper.isFunctionWithContext(type, this.checker)
1203+
&& !ts.isPropertyAccessExpression(node.expression)
1204+
&& !ts.isElementAccessExpression(node.expression)) {
12051205
const context = this.isStrict ? ts.createNull() : ts.createIdentifier("_G");
1206-
params = this.transpileArguments(node.arguments, context);
1206+
params = this.transpileArguments(node.arguments, sig, context);
12071207
} else {
1208-
params = this.transpileArguments(node.arguments);
1208+
params = this.transpileArguments(node.arguments, sig);
12091209
}
12101210
return isTupleReturn && !isTupleReturnForward && !isInDestructingAssignment && returnValueIsUsed
12111211
? `({ ${callPath}(${params}) })` : `${callPath}(${params})`;
@@ -1248,10 +1248,13 @@ export abstract class LuaTranspiler {
12481248
return this.transpileFunctionCallExpression(node);
12491249
}
12501250

1251+
const sig = this.checker.getResolvedSignature(node);
1252+
12511253
// Get the type of the function
12521254
if (node.expression.expression.kind === ts.SyntaxKind.SuperKeyword) {
12531255
// Super calls take the format of super.call(self,...)
1254-
params = this.transpileArguments(node.arguments, ts.createNode(ts.SyntaxKind.ThisKeyword) as ts.Expression);
1256+
params = this.transpileArguments(node.arguments, sig,
1257+
ts.createNode(ts.SyntaxKind.ThisKeyword) as ts.Expression);
12551258
return `${this.transpileExpression(node.expression)}(${params})`;
12561259
} else {
12571260
// Replace last . with : here
@@ -1260,13 +1263,13 @@ export abstract class LuaTranspiler {
12601263
return `tostring(${this.transpileExpression(node.expression.expression)})`;
12611264
} else if (name === "hasOwnProperty") {
12621265
const expr = this.transpileExpression(node.expression.expression);
1263-
params = this.transpileArguments(node.arguments);
1266+
params = this.transpileArguments(node.arguments, sig);
12641267
return `(rawget(${expr}, ${params} )~=nil)`;
12651268
} else {
12661269
const type = this.checker.getTypeAtLocation(node.expression);
1267-
const op = tsHelper.getCustomDecorators(type, this.checker).has(DecoratorKind.NoContext) ? "." : ":";
1270+
const op = tsHelper.isFunctionWithContext(type, this.checker) ? ":" : ".";
12681271
callPath = `${this.transpileExpression(node.expression.expression)}${op}${name}`;
1269-
params = this.transpileArguments(node.arguments);
1272+
params = this.transpileArguments(node.arguments, sig);
12701273
return `${callPath}(${params})`;
12711274
}
12721275
}
@@ -1387,6 +1390,10 @@ export abstract class LuaTranspiler {
13871390

13881391
public transpileFunctionCallExpression(node: ts.CallExpression): string {
13891392
const expression = node.expression as ts.PropertyAccessExpression;
1393+
const callerType = this.checker.getTypeAtLocation(expression.expression);
1394+
if (!tsHelper.isFunctionWithContext(callerType, this.checker)) {
1395+
throw TSTLErrors.UnsupportedMethodConversion(node);
1396+
}
13901397
const params = this.transpileArguments(node.arguments);
13911398
const caller = this.transpileExpression(expression.expression);
13921399
const expressionName = this.transpileIdentifier(expression.name);
@@ -1402,17 +1409,51 @@ export abstract class LuaTranspiler {
14021409
}
14031410
}
14041411

1405-
public transpileArguments(params: ts.NodeArray<ts.Expression>, context?: ts.Expression): string {
1412+
public validateAssignment(node: ts.Node, fromType: ts.Type, toType: ts.Type): void {
1413+
if ((toType.flags & ts.TypeFlags.Any) !== 0) {
1414+
// Assigning to un-typed variable
1415+
return;
1416+
} else if ((fromType as ts.TypeReference).typeArguments && (toType as ts.TypeReference).typeArguments) {
1417+
// Recurse into tuples/arrays
1418+
(toType as ts.TypeReference).typeArguments.forEach((t, i) => {
1419+
this.validateAssignment(node, (fromType as ts.TypeReference).typeArguments[i], t);
1420+
});
1421+
} else {
1422+
// Check function assignments
1423+
const fromHasContext = tsHelper.isFunctionWithContext(fromType, this.checker);
1424+
const toHasContext = tsHelper.isFunctionWithContext(toType, this.checker);
1425+
if (fromHasContext !== toHasContext) {
1426+
if (fromHasContext) {
1427+
throw TSTLErrors.UnsupportedFunctionConversion(node);
1428+
} else {
1429+
throw TSTLErrors.UnsupportedMethodConversion(node);
1430+
}
1431+
}
1432+
}
1433+
}
1434+
1435+
public transpileArguments(params: ts.NodeArray<ts.Expression>, sig?: ts.Signature,
1436+
context?: ts.Expression): string {
14061437
const parameters: string[] = [];
14071438

14081439
// Add context as first param if present
14091440
if (context) {
14101441
parameters.push(this.transpileExpression(context));
14111442
}
14121443

1413-
params.forEach(param => {
1414-
parameters.push(this.transpileExpression(param));
1415-
});
1444+
if (sig && sig.parameters.length >= params.length) {
1445+
for (let i = 0; i < params.length; ++i) {
1446+
const param = params[i];
1447+
const paramType = this.checker.getTypeAtLocation(param);
1448+
const sigType = this.checker.getTypeAtLocation(sig.parameters[i].valueDeclaration);
1449+
this.validateAssignment(param, paramType, sigType);
1450+
parameters.push(this.transpileExpression(param));
1451+
}
1452+
} else {
1453+
params.forEach(param => {
1454+
parameters.push(this.transpileExpression(param));
1455+
});
1456+
}
14161457

14171458
return parameters.join(",");
14181459
}
@@ -1592,6 +1633,13 @@ export abstract class LuaTranspiler {
15921633
}
15931634

15941635
public transpileVariableDeclaration(node: ts.VariableDeclaration): string {
1636+
if (node.initializer) {
1637+
// Validate assignment
1638+
const initializerType = this.checker.getTypeAtLocation(node.initializer);
1639+
const varType = this.checker.getTypeFromTypeNode(node.type);
1640+
this.validateAssignment(node.initializer, initializerType, varType);
1641+
}
1642+
15951643
if (ts.isIdentifier(node.name)) {
15961644
// Find variable identifier
15971645
const identifierName = this.transpileIdentifier(node.name);
@@ -1635,7 +1683,7 @@ export abstract class LuaTranspiler {
16351683
const methodName = this.transpileIdentifier(node.name);
16361684

16371685
const type = this.checker.getTypeAtLocation(node);
1638-
const context = tsHelper.getCustomDecorators(type, this.checker).has(DecoratorKind.NoContext) ? null : "self";
1686+
const context = tsHelper.isFunctionWithContext(type, this.checker) ? "self" : null;
16391687
const [paramNames, spreadIdentifier] = this.transpileParameters(node.parameters, context);
16401688

16411689
let prefix = this.accessPrefix(node);
@@ -1672,6 +1720,9 @@ export abstract class LuaTranspiler {
16721720

16731721
// Only push parameter name to paramName array if it isn't a spread parameter
16741722
for (const param of parameters) {
1723+
if (ts.isIdentifier(param.name) && param.name.originalKeywordKind === ts.SyntaxKind.ThisKeyword) {
1724+
continue;
1725+
}
16751726
const paramName = this.transpileIdentifier(param.name as ts.Identifier);
16761727

16771728
// This parameter is a spread parameter (...param)
@@ -1718,7 +1769,7 @@ export abstract class LuaTranspiler {
17181769
}
17191770

17201771
const type = this.checker.getTypeAtLocation(node);
1721-
const context = tsHelper.getCustomDecorators(type, this.checker).has(DecoratorKind.NoContext) ? null : "self";
1772+
const context = tsHelper.isFunctionWithContext(type, this.checker) ? "self" : null;
17221773
const [paramNames, spreadIdentifier] = this.transpileParameters(node.parameters, context);
17231774

17241775
// Build function header
@@ -1918,6 +1969,9 @@ export abstract class LuaTranspiler {
19181969

19191970
public transpileConstructor(node: ts.ConstructorDeclaration,
19201971
className: string): string {
1972+
// Don't transpile methods without body (overload declarations)
1973+
if (!node.body) { return ""; }
1974+
19211975
const extraInstanceFields = [];
19221976

19231977
const parameters = ["self"];
@@ -1987,8 +2041,10 @@ export abstract class LuaTranspiler {
19872041
}
19882042

19892043
public transpileFunctionExpression(node: ts.FunctionLikeDeclaration, context: string | null): string {
2044+
const type = this.checker.getTypeAtLocation(node);
2045+
const hasContext = tsHelper.isFunctionWithContext(type, this.checker);
19902046
// Build parameter string
1991-
const [paramNames, spreadIdentifier] = this.transpileParameters(node.parameters, context);
2047+
const [paramNames, spreadIdentifier] = this.transpileParameters(node.parameters, hasContext ? context : null);
19922048
let result = `function(${paramNames.join(",")})\n`;
19932049
this.pushIndent();
19942050
const body = ts.isBlock(node.body) ? node.body : ts.createBlock([ts.createReturn(node.body)]);

src/lualib/ArrayConcat.ts

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

6-
/** !NoContext */
74
function __TS__ArrayConcat(arr1: any[], ...args: any[]): any[] {
85
const out: any[] = [];
96
for (const val of arr1) {

src/lualib/ArrayEvery.ts

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

src/lualib/ArrayFilter.ts

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

src/lualib/ArrayForEach.ts

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

src/lualib/ArrayIndexOf.ts

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

src/lualib/ArrayMap.ts

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

0 commit comments

Comments
 (0)
X Tutup