From 0db2d732db58572fbfa3d75f82bb7b4aca141991 Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Thu, 29 Nov 2018 22:19:46 +0100 Subject: [PATCH 01/10] check the base types to determine if a type inherits from Array --- src/TSHelper.ts | 13 ++++++++++++- src/Transpiler.ts | 12 +++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/TSHelper.ts b/src/TSHelper.ts index d35973f32..c4d628f9a 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -74,7 +74,18 @@ export class TSHelper { && (typeNode as ts.UnionOrIntersectionTypeNode).types.some(this.isArrayTypeNode)); } - public static isArrayType(type: ts.Type, checker: ts.TypeChecker): boolean { + public static isArrayType(type: ts.Type, checker: ts.TypeChecker, dontCheckBases?: boolean): boolean { + if (! dontCheckBases) { + // Check if type inherits from Array + const baseTypes = type.getBaseTypes(); + if (baseTypes) { + for (const baseType of baseTypes) { + const baseTypeNode = checker.typeToTypeNode(baseType, undefined, ts.NodeBuilderFlags.InTypeAlias); + if (baseTypeNode && this.isArrayTypeNode(baseTypeNode)) { return true; } + } + } + } + const typeNode = checker.typeToTypeNode(type, undefined, ts.NodeBuilderFlags.InTypeAlias); return typeNode && this.isArrayTypeNode(typeNode); } diff --git a/src/Transpiler.ts b/src/Transpiler.ts index 7ca71b507..bddb1e05b 100644 --- a/src/Transpiler.ts +++ b/src/Transpiler.ts @@ -1203,10 +1203,20 @@ export abstract class LuaTranspiler { } - if (tsHelper.isArrayType(ownerType, this.checker)) { + // if ownerType is a array, use only supported functions + if (tsHelper.isArrayType(ownerType, this.checker, true)) { return this.transpileArrayCallExpression(node); } + // if ownerType inherits from an array, use array calls where appropriate + if (tsHelper.isArrayType(ownerType, this.checker)) { + try { + return this.transpileArrayCallExpression(node); + } catch (exception) { + // non-array method + } + } + // Get the type of the function const functionType = this.checker.getTypeAtLocation(node.expression); // Don't replace . with : for namespaces or functions defined as properties with lambdas From bd43ab033addd100fa2775df52dd39c4f3e5aa27 Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Fri, 30 Nov 2018 01:31:27 +0100 Subject: [PATCH 02/10] dont block exceptions --- src/Transpiler.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Transpiler.ts b/src/Transpiler.ts index bddb1e05b..5e10cce3e 100644 --- a/src/Transpiler.ts +++ b/src/Transpiler.ts @@ -1210,9 +1210,10 @@ export abstract class LuaTranspiler { // if ownerType inherits from an array, use array calls where appropriate if (tsHelper.isArrayType(ownerType, this.checker)) { - try { - return this.transpileArrayCallExpression(node); - } catch (exception) { + const transpiledExpression = this.transpileArrayCallExpression(node, true); + if (transpiledExpression !== "") { + return transpiledExpression; + } else { // non-array method } } @@ -1313,7 +1314,7 @@ export abstract class LuaTranspiler { } } - public transpileArrayCallExpression(node: ts.CallExpression): string { + public transpileArrayCallExpression(node: ts.CallExpression, noException?: boolean ): string { const expression = node.expression as ts.PropertyAccessExpression; const params = this.transpileArguments(node.arguments); const caller = this.transpileExpression(expression.expression); @@ -1357,6 +1358,7 @@ export abstract class LuaTranspiler { return `table.concat(${caller}, ${params})`; } default: + if (noException) { return ""; } throw TSTLErrors.UnsupportedProperty("array", expressionName, node); } } From f6e2112f2a41219cf90588c8a294e69ec14775fb Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Fri, 30 Nov 2018 11:11:28 +0100 Subject: [PATCH 03/10] remove empty else clause --- src/Transpiler.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Transpiler.ts b/src/Transpiler.ts index 5e10cce3e..28bb72fc1 100644 --- a/src/Transpiler.ts +++ b/src/Transpiler.ts @@ -1213,8 +1213,6 @@ export abstract class LuaTranspiler { const transpiledExpression = this.transpileArrayCallExpression(node, true); if (transpiledExpression !== "") { return transpiledExpression; - } else { - // non-array method } } From 258d4cf374be7c04d3c853970f715b8d47c8ee06 Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Fri, 30 Nov 2018 11:12:50 +0100 Subject: [PATCH 04/10] remove space --- src/TSHelper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TSHelper.ts b/src/TSHelper.ts index c4d628f9a..7132584ec 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -75,7 +75,7 @@ export class TSHelper { } public static isArrayType(type: ts.Type, checker: ts.TypeChecker, dontCheckBases?: boolean): boolean { - if (! dontCheckBases) { + if (!dontCheckBases) { // Check if type inherits from Array const baseTypes = type.getBaseTypes(); if (baseTypes) { From 0c497e1abf5b25dff7202728b4acb9708e972e5e Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Fri, 30 Nov 2018 11:54:33 +0100 Subject: [PATCH 05/10] added tsHelper.isDefaultArrayCallExpression() --- src/TSHelper.ts | 43 +++++++++++++++++++++++++++++++++++++++++++ src/Transpiler.ts | 11 ++++------- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/src/TSHelper.ts b/src/TSHelper.ts index 7132584ec..225989553 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -247,4 +247,47 @@ export class TSHelper { } return [false, null, null]; } + + public static isDefaultArrayCallExpression(node: ts.CallExpression, + transpileIdentifier: (identifier: ts.Identifier) => string ): boolean { + const expression = node.expression as ts.PropertyAccessExpression; + const expressionName = transpileIdentifier(expression.name); + switch (expressionName) { + case "concat": + return true; + case "push": + return true; + case "reverse": + return true; + case "shift": + return true; + case "unshift": + return true; + case "sort": + return true; + case "pop": + return true; + case "forEach": + return true; + case "indexOf": + return true; + case "map": + return true; + case "filter": + return true; + case "some": + return true; + case "every": + return true; + case "slice": + return true; + case "splice": + return true; + case "join": + return true; + default: + return false; + } + } + } diff --git a/src/Transpiler.ts b/src/Transpiler.ts index 28bb72fc1..2c3fb64e1 100644 --- a/src/Transpiler.ts +++ b/src/Transpiler.ts @@ -1209,11 +1209,9 @@ export abstract class LuaTranspiler { } // if ownerType inherits from an array, use array calls where appropriate - if (tsHelper.isArrayType(ownerType, this.checker)) { - const transpiledExpression = this.transpileArrayCallExpression(node, true); - if (transpiledExpression !== "") { - return transpiledExpression; - } + if (tsHelper.isArrayType(ownerType, this.checker) + && tsHelper.isDefaultArrayCallExpression(node, expression => this.transpileIdentifier(expression))) { + return this.transpileArrayCallExpression(node); } // Get the type of the function @@ -1312,7 +1310,7 @@ export abstract class LuaTranspiler { } } - public transpileArrayCallExpression(node: ts.CallExpression, noException?: boolean ): string { + public transpileArrayCallExpression(node: ts.CallExpression): string { const expression = node.expression as ts.PropertyAccessExpression; const params = this.transpileArguments(node.arguments); const caller = this.transpileExpression(expression.expression); @@ -1356,7 +1354,6 @@ export abstract class LuaTranspiler { return `table.concat(${caller}, ${params})`; } default: - if (noException) { return ""; } throw TSTLErrors.UnsupportedProperty("array", expressionName, node); } } From 4dd21698a22296177b8684bc67889ec7372a684d Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Fri, 30 Nov 2018 12:51:22 +0100 Subject: [PATCH 06/10] added derived array recognition test code --- test/translation/lua/assignments.lua | 3 +++ test/translation/ts/assignments.ts | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/test/translation/lua/assignments.lua b/test/translation/lua/assignments.lua index 6f4e5f9bd..4d6227750 100644 --- a/test/translation/lua/assignments.lua +++ b/test/translation/lua/assignments.lua @@ -1,10 +1,13 @@ x = y; x = obj.prop; x = arr[(0)+1]; +x = customArr[(0)+1]; x = ((function() local __TS_tmp = obj.prop; y = __TS_tmp; return __TS_tmp end)()); x = obj.prop; obj.prop = x; arr[(0)+1] = x; +customArr[(0)+1] = x; +customArr:reset(); obj.prop = arr[(0)+1]; obj.prop = ((function() arr[(0)+1] = x; return x end)()); xTup = getTup(); diff --git a/test/translation/ts/assignments.ts b/test/translation/ts/assignments.ts index a8e51e59a..0c9146413 100644 --- a/test/translation/ts/assignments.ts +++ b/test/translation/ts/assignments.ts @@ -5,6 +5,8 @@ declare let obj: {prop: number, arr: number[]}; declare function getObj(): typeof obj; declare let arr: number[]; declare let arr2: number[][]; +declare interface CustomArray extends Array{ reset():void }; +declare let customArr: CustomArray; declare function getArr(): typeof arr; declare function getIndex(): number; declare let xTup: [number, number]; @@ -15,10 +17,13 @@ declare function getTupRet(): [number, number]; x = y; x = obj.prop; x = arr[0]; +x = customArr[0]; x = y = obj.prop; x = obj.prop; obj.prop = x; arr[0] = x; +customArr[0] = x; +customArr.reset(); obj.prop = arr[0]; obj.prop = arr[0] = x; xTup = getTup(); From 2f4495b5c00160407579087c407767ccb945ab6c Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Fri, 30 Nov 2018 13:57:00 +0100 Subject: [PATCH 07/10] Added isExplicitArrayType() method --- src/TSHelper.ts | 23 +++++++++++------------ src/Transpiler.ts | 2 +- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/TSHelper.ts b/src/TSHelper.ts index 225989553..2c4f26591 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -74,22 +74,21 @@ export class TSHelper { && (typeNode as ts.UnionOrIntersectionTypeNode).types.some(this.isArrayTypeNode)); } - public static isArrayType(type: ts.Type, checker: ts.TypeChecker, dontCheckBases?: boolean): boolean { - if (!dontCheckBases) { - // Check if type inherits from Array - const baseTypes = type.getBaseTypes(); - if (baseTypes) { - for (const baseType of baseTypes) { - const baseTypeNode = checker.typeToTypeNode(baseType, undefined, ts.NodeBuilderFlags.InTypeAlias); - if (baseTypeNode && this.isArrayTypeNode(baseTypeNode)) { return true; } - } - } - } - + public static isExplicitArrayType(type: ts.Type, checker: ts.TypeChecker): boolean { const typeNode = checker.typeToTypeNode(type, undefined, ts.NodeBuilderFlags.InTypeAlias); return typeNode && this.isArrayTypeNode(typeNode); } + public static isArrayType(type: ts.Type, checker: ts.TypeChecker): boolean { + const baseTypes = type.getBaseTypes(); + if (baseTypes) { + for (const baseType of baseTypes) { + if (this.isExplicitArrayType(baseType, checker)) { return true; } + } + } + return this.isExplicitArrayType(type, checker); + } + public static isTupleReturnCall(node: ts.Node, checker: ts.TypeChecker): boolean { if (ts.isCallExpression(node)) { const type = checker.getTypeAtLocation(node.expression); diff --git a/src/Transpiler.ts b/src/Transpiler.ts index 2c3fb64e1..385706480 100644 --- a/src/Transpiler.ts +++ b/src/Transpiler.ts @@ -1204,7 +1204,7 @@ export abstract class LuaTranspiler { } // if ownerType is a array, use only supported functions - if (tsHelper.isArrayType(ownerType, this.checker, true)) { + if (tsHelper.isExplicitArrayType(ownerType, this.checker)) { return this.transpileArrayCallExpression(node); } From d21091d059fe211f82154b474a5560bd9d2e6833 Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Fri, 30 Nov 2018 14:25:56 +0100 Subject: [PATCH 08/10] added unit test --- test/unit/array.spec.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/unit/array.spec.ts b/test/unit/array.spec.ts index bca94494a..d1daa4268 100644 --- a/test/unit/array.spec.ts +++ b/test/unit/array.spec.ts @@ -38,4 +38,17 @@ export class ArrayTests { const result = util.executeLua(lua); Expect(result).toBe(5); } + + @Test("Derived array access") + public derivedArrayAccess(): void { + const lua = `local arr = {firstElement=function(self) return self[1]; end};` + + util.transpileString( + `interface CustomArray extends Array{ firstElement():number; }; + declare const arr: CustomArray; + arr[0] = 3; + return arr.firstElement();` + ); + const result = util.executeLua(lua); + Expect(result).toBe(3); + } } From 47c45e0dae4032e4b564b57a091f9fa38c492464 Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Sun, 2 Dec 2018 14:52:11 +0100 Subject: [PATCH 09/10] rewrite isDefaultArrayCall() -> isDefaultArrayCallMethodName() --- src/TSHelper.ts | 7 ++----- src/Transpiler.ts | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/TSHelper.ts b/src/TSHelper.ts index 3825bca7e..b4addb702 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -265,11 +265,8 @@ export class TSHelper { return [false, null, null]; } - public static isDefaultArrayCallExpression(node: ts.CallExpression, - transpileIdentifier: (identifier: ts.Identifier) => string ): boolean { - const expression = node.expression as ts.PropertyAccessExpression; - const expressionName = transpileIdentifier(expression.name); - switch (expressionName) { + public static isDefaultArrayCallMethodName(methodName: string): boolean { + switch (methodName) { case "concat": return true; case "push": diff --git a/src/Transpiler.ts b/src/Transpiler.ts index 385706480..232d1c7fc 100644 --- a/src/Transpiler.ts +++ b/src/Transpiler.ts @@ -1210,7 +1210,7 @@ export abstract class LuaTranspiler { // if ownerType inherits from an array, use array calls where appropriate if (tsHelper.isArrayType(ownerType, this.checker) - && tsHelper.isDefaultArrayCallExpression(node, expression => this.transpileIdentifier(expression))) { + && tsHelper.isDefaultArrayCallMethodName(this.transpileIdentifier(node.expression.name))) { return this.transpileArrayCallExpression(node); } From e0f272b96731249bbf482f3b5658ac9eea7dd577 Mon Sep 17 00:00:00 2001 From: Lars Melchior Date: Sun, 2 Dec 2018 15:18:57 +0100 Subject: [PATCH 10/10] change switch statement to Set lookup --- src/TSHelper.ts | 56 ++++++++++++++++++------------------------------- 1 file changed, 20 insertions(+), 36 deletions(-) diff --git a/src/TSHelper.ts b/src/TSHelper.ts index b4addb702..c3eac9460 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -1,6 +1,25 @@ import * as ts from "typescript"; import { Decorator, DecoratorKind } from "./Decorator"; +const defaultArrayCallMethodNames = new Set([ + "concat", + "push", + "reverse", + "shift", + "unshift", + "sort", + "pop", + "forEach", + "indexOf", + "map", + "filter", + "some", + "every", + "slice", + "splice", + "join", +]); + export class TSHelper { // Reverse lookup of enum key by value @@ -266,42 +285,7 @@ export class TSHelper { } public static isDefaultArrayCallMethodName(methodName: string): boolean { - switch (methodName) { - case "concat": - return true; - case "push": - return true; - case "reverse": - return true; - case "shift": - return true; - case "unshift": - return true; - case "sort": - return true; - case "pop": - return true; - case "forEach": - return true; - case "indexOf": - return true; - case "map": - return true; - case "filter": - return true; - case "some": - return true; - case "every": - return true; - case "slice": - return true; - case "splice": - return true; - case "join": - return true; - default: - return false; - } + return defaultArrayCallMethodNames.has(methodName); } }