diff --git a/src/TSHelper.ts b/src/TSHelper.ts index 203181681..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 @@ -74,11 +93,21 @@ export class TSHelper { && (typeNode as ts.UnionOrIntersectionTypeNode).types.some(this.isArrayTypeNode)); } - public static isArrayType(type: ts.Type, checker: ts.TypeChecker): boolean { + 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); @@ -254,4 +283,9 @@ export class TSHelper { } return [false, null, null]; } + + public static isDefaultArrayCallMethodName(methodName: string): boolean { + return defaultArrayCallMethodNames.has(methodName); + } + } diff --git a/src/Transpiler.ts b/src/Transpiler.ts index 7ca71b507..232d1c7fc 100644 --- a/src/Transpiler.ts +++ b/src/Transpiler.ts @@ -1203,7 +1203,14 @@ export abstract class LuaTranspiler { } - if (tsHelper.isArrayType(ownerType, this.checker)) { + // if ownerType is a array, use only supported functions + if (tsHelper.isExplicitArrayType(ownerType, this.checker)) { + return this.transpileArrayCallExpression(node); + } + + // if ownerType inherits from an array, use array calls where appropriate + if (tsHelper.isArrayType(ownerType, this.checker) + && tsHelper.isDefaultArrayCallMethodName(this.transpileIdentifier(node.expression.name))) { return this.transpileArrayCallExpression(node); } 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); + } }