From b77d06bd9840a9edab806f5a3d6b8d1261e47a46 Mon Sep 17 00:00:00 2001 From: ark120202 Date: Thu, 2 May 2019 14:03:19 +0500 Subject: [PATCH] Add Symbol.hasInstance support --- build_lualib.ts | 4 +++ src/LuaLib.ts | 2 ++ src/LuaTransformer.ts | 17 +++++------ src/lualib/InstanceOf.ts | 9 ++++++ src/lualib/InstanceOfObject.ts | 4 +++ src/lualib/Symbol.ts | 1 + src/lualib/declarations/global.d.ts | 4 +++ test/unit/typechecking.spec.ts | 46 +++++++++++++++++++++++++---- 8 files changed, 73 insertions(+), 14 deletions(-) create mode 100644 src/lualib/InstanceOfObject.ts create mode 100644 src/lualib/declarations/global.d.ts diff --git a/build_lualib.ts b/build_lualib.ts index 18fb24bf7..193e6251e 100644 --- a/build_lualib.ts +++ b/build_lualib.ts @@ -9,6 +9,10 @@ compile([ "--skipLibCheck", "--types", "node", + "--target", + "esnext", + "--lib", + "esnext", "--luaLibImport", "none", "--luaTarget", diff --git a/src/LuaLib.ts b/src/LuaLib.ts index 2d1c5c134..17b910b52 100644 --- a/src/LuaLib.ts +++ b/src/LuaLib.ts @@ -27,6 +27,7 @@ export enum LuaLibFeature { FunctionCall = "FunctionCall", Index = "Index", InstanceOf = "InstanceOf", + InstanceOfObject = "InstanceOfObject", Iterator = "Iterator", Map = "Map", NewIndex = "NewIndex", @@ -49,6 +50,7 @@ export enum LuaLibFeature { const luaLibDependencies: {[lib in LuaLibFeature]?: LuaLibFeature[]} = { ArrayFlat: [LuaLibFeature.ArrayConcat], ArrayFlatMap: [LuaLibFeature.ArrayConcat], + InstanceOf: [LuaLibFeature.Symbol], Iterator: [LuaLibFeature.Symbol], ObjectFromEntries: [LuaLibFeature.Iterator, LuaLibFeature.Symbol], Map: [LuaLibFeature.InstanceOf, LuaLibFeature.Iterator, LuaLibFeature.Symbol], diff --git a/src/LuaTransformer.ts b/src/LuaTransformer.ts index 59a91edbb..d26022061 100644 --- a/src/LuaTransformer.ts +++ b/src/LuaTransformer.ts @@ -2660,14 +2660,18 @@ export class LuaTransformer { ); case ts.SyntaxKind.InstanceOfKeyword: - const decorators = tsHelper.getCustomDecorators( - this.checker.getTypeAtLocation(expression.right), - this.checker - ); + const rhsType = this.checker.getTypeAtLocation(expression.right); + const decorators = tsHelper.getCustomDecorators(rhsType, this.checker); + if (decorators.has(DecoratorKind.Extension) || decorators.has(DecoratorKind.MetaExtension)) { // Cannot use instanceof on extension classes throw TSTLErrors.InvalidInstanceOfExtension(expression); } + + if (tsHelper.isStandardLibraryType(rhsType, "ObjectConstructor", this.program)) { + return this.transformLuaLibFunction(LuaLibFeature.InstanceOfObject, expression, lhs); + } + return this.transformLuaLibFunction(LuaLibFeature.InstanceOf, expression, lhs, rhs); case ts.SyntaxKind.CommaToken: @@ -4531,11 +4535,6 @@ export class LuaTransformer { } private importLuaLibFeature(feature: LuaLibFeature): void { - // Add additional lib requirements - if (feature === LuaLibFeature.Map || feature === LuaLibFeature.Set) { - this.luaLibFeatureSet.add(LuaLibFeature.InstanceOf); - } - this.luaLibFeatureSet.add(feature); } diff --git a/src/lualib/InstanceOf.ts b/src/lualib/InstanceOf.ts index b59674943..6f6dd1c3c 100644 --- a/src/lualib/InstanceOf.ts +++ b/src/lualib/InstanceOf.ts @@ -7,6 +7,15 @@ interface LuaObject { } function __TS__InstanceOf(this: void, obj: LuaObject, classTbl: LuaClass): boolean { + if (typeof classTbl !== "object") { + // tslint:disable-next-line: no-string-throw + throw "Right-hand side of 'instanceof' is not an object"; + } + + if (classTbl[Symbol.hasInstance] !== undefined) { + return !!classTbl[Symbol.hasInstance](obj); + } + if (obj !== undefined) { let luaClass = obj.constructor; while (luaClass !== undefined) { diff --git a/src/lualib/InstanceOfObject.ts b/src/lualib/InstanceOfObject.ts new file mode 100644 index 000000000..681e243c5 --- /dev/null +++ b/src/lualib/InstanceOfObject.ts @@ -0,0 +1,4 @@ +function __TS__InstanceOfObject(this: void, value: unknown): boolean { + const valueType = type(value); + return valueType === "table" || valueType === "function"; +} diff --git a/src/lualib/Symbol.ts b/src/lualib/Symbol.ts index 5c7bf0a7c..ef110af61 100644 --- a/src/lualib/Symbol.ts +++ b/src/lualib/Symbol.ts @@ -17,4 +17,5 @@ function __TS__Symbol(description?: string | number): symbol { Symbol = { iterator: __TS__Symbol('Symbol.iterator'), + hasInstance: __TS__Symbol('Symbol.hasInstance'), } as any; diff --git a/src/lualib/declarations/global.d.ts b/src/lualib/declarations/global.d.ts new file mode 100644 index 000000000..6d88460ed --- /dev/null +++ b/src/lualib/declarations/global.d.ts @@ -0,0 +1,4 @@ +declare function type( + this: void, + value: any +): "nil" | "number" | "string" | "boolean" | "table" | "function" | "thread" | "userdata"; diff --git a/test/unit/typechecking.spec.ts b/test/unit/typechecking.spec.ts index e52337405..8088994b0 100644 --- a/test/unit/typechecking.spec.ts +++ b/test/unit/typechecking.spec.ts @@ -50,7 +50,7 @@ test("instanceof", () => { "class myClass {} let inst = new myClass(); return inst instanceof myClass;", ); - expect(result).toBeTruthy(); + expect(result).toBe(true); }); test("instanceof inheritance", () => { @@ -60,7 +60,7 @@ test("instanceof inheritance", () => { let inst = new childClass(); return inst instanceof myClass; `); - expect(result).toBeTruthy(); + expect(result).toBe(true); }); test("instanceof inheritance false", () => { @@ -73,15 +73,33 @@ test("instanceof inheritance false", () => { expect(result).toBe(false); }); +test("{} instanceof Object", () => { + const result = util.transpileAndExecute("return {} instanceof Object;"); + + expect(result).toBe(true); +}); + +test("function instanceof Object", () => { + const result = util.transpileAndExecute("return (() => {}) instanceof Object;"); + + expect(result).toBe(true); +}); + test("null instanceof Object", () => { - const result = util.transpileAndExecute("return (null) instanceof Object;"); + const result = util.transpileAndExecute("return (null as any) instanceof Object;"); expect(result).toBe(false); }); +test("instanceof undefined", () => { + expect(() => { + util.transpileAndExecute("return {} instanceof (undefined as any);"); + }).toThrow("Right-hand side of 'instanceof' is not an object"); +}); + test("null instanceof Class", () => { const result = util.transpileAndExecute( - "class myClass {} return (null) instanceof myClass;", + "class myClass {} return (null as any) instanceof myClass;", ); expect(result).toBe(false); @@ -108,5 +126,23 @@ test("instanceof export", () => { "result", ); - expect(result).toBeTruthy(); + expect(result).toBe(true); +}); + +test("instanceof Symbol.hasInstance", () => { + const result = util.transpileAndExecute(` + class myClass { + static [Symbol.hasInstance]() { + return false; + } + } + + const inst = new myClass(); + const isInstanceOld = inst instanceof myClass; + myClass[Symbol.hasInstance] = () => true; + const isInstanceNew = inst instanceof myClass; + return isInstanceOld !== isInstanceNew; + `); + + expect(result).toBe(true); });