Skip to content

Commit 1d2cd13

Browse files
ark120202Perryvw
authored andcommitted
Add Symbol.hasInstance support (#550)
1 parent bd2d81b commit 1d2cd13

File tree

8 files changed

+75
-16
lines changed

8 files changed

+75
-16
lines changed

build_lualib.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,15 @@ import { LuaLib } from "./src/LuaLib";
88
const options: tstl.CompilerOptions = {
99
skipLibCheck: true,
1010
types: [],
11+
target: ts.ScriptTarget.ESNext,
12+
lib: ["lib.esnext.d.ts"],
13+
14+
outDir: path.join(__dirname, "./dist/lualib"),
15+
rootDir: path.join(__dirname, "./src/lualib"),
16+
1117
luaLibImport: tstl.LuaLibImportKind.None,
1218
luaTarget: tstl.LuaTarget.Lua51,
1319
noHeader: true,
14-
outDir: path.join(__dirname, "./dist/lualib"),
15-
rootDir: path.join(__dirname, "./src/lualib"),
1620
};
1721

1822
// TODO: Check diagnostics

src/LuaLib.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export enum LuaLibFeature {
2727
FunctionCall = "FunctionCall",
2828
Index = "Index",
2929
InstanceOf = "InstanceOf",
30+
InstanceOfObject = "InstanceOfObject",
3031
Iterator = "Iterator",
3132
Map = "Map",
3233
NewIndex = "NewIndex",
@@ -51,6 +52,7 @@ export enum LuaLibFeature {
5152
const luaLibDependencies: {[lib in LuaLibFeature]?: LuaLibFeature[]} = {
5253
ArrayFlat: [LuaLibFeature.ArrayConcat],
5354
ArrayFlatMap: [LuaLibFeature.ArrayConcat],
55+
InstanceOf: [LuaLibFeature.Symbol],
5456
Iterator: [LuaLibFeature.Symbol],
5557
ObjectFromEntries: [LuaLibFeature.Iterator, LuaLibFeature.Symbol],
5658
Map: [LuaLibFeature.InstanceOf, LuaLibFeature.Iterator, LuaLibFeature.Symbol],

src/LuaTransformer.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2658,14 +2658,18 @@ export class LuaTransformer {
26582658
);
26592659

26602660
case ts.SyntaxKind.InstanceOfKeyword:
2661-
const decorators = tsHelper.getCustomDecorators(
2662-
this.checker.getTypeAtLocation(expression.right),
2663-
this.checker
2664-
);
2661+
const rhsType = this.checker.getTypeAtLocation(expression.right);
2662+
const decorators = tsHelper.getCustomDecorators(rhsType, this.checker);
2663+
26652664
if (decorators.has(DecoratorKind.Extension) || decorators.has(DecoratorKind.MetaExtension)) {
26662665
// Cannot use instanceof on extension classes
26672666
throw TSTLErrors.InvalidInstanceOfExtension(expression);
26682667
}
2668+
2669+
if (tsHelper.isStandardLibraryType(rhsType, "ObjectConstructor", this.program)) {
2670+
return this.transformLuaLibFunction(LuaLibFeature.InstanceOfObject, expression, lhs);
2671+
}
2672+
26692673
return this.transformLuaLibFunction(LuaLibFeature.InstanceOf, expression, lhs, rhs);
26702674

26712675
case ts.SyntaxKind.CommaToken:
@@ -4533,11 +4537,6 @@ export class LuaTransformer {
45334537
}
45344538

45354539
private importLuaLibFeature(feature: LuaLibFeature): void {
4536-
// Add additional lib requirements
4537-
if (feature === LuaLibFeature.Map || feature === LuaLibFeature.Set) {
4538-
this.luaLibFeatureSet.add(LuaLibFeature.InstanceOf);
4539-
}
4540-
45414540
this.luaLibFeatureSet.add(feature);
45424541
}
45434542

src/lualib/InstanceOf.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,15 @@ interface LuaObject {
77
}
88

99
function __TS__InstanceOf(this: void, obj: LuaObject, classTbl: LuaClass): boolean {
10+
if (typeof classTbl !== "object") {
11+
// tslint:disable-next-line: no-string-throw
12+
throw "Right-hand side of 'instanceof' is not an object";
13+
}
14+
15+
if (classTbl[Symbol.hasInstance] !== undefined) {
16+
return !!classTbl[Symbol.hasInstance](obj);
17+
}
18+
1019
if (obj !== undefined) {
1120
let luaClass = obj.constructor;
1221
while (luaClass !== undefined) {

src/lualib/InstanceOfObject.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
function __TS__InstanceOfObject(this: void, value: unknown): boolean {
2+
const valueType = type(value);
3+
return valueType === "table" || valueType === "function";
4+
}

src/lualib/Symbol.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ function __TS__Symbol(description?: string | number): symbol {
1717

1818
Symbol = {
1919
iterator: __TS__Symbol('Symbol.iterator'),
20+
hasInstance: __TS__Symbol('Symbol.hasInstance'),
2021
} as any;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
declare function type(
2+
this: void,
3+
value: any
4+
): "nil" | "number" | "string" | "boolean" | "table" | "function" | "thread" | "userdata";

test/unit/typechecking.spec.ts

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ test("instanceof", () => {
5050
"class myClass {} let inst = new myClass(); return inst instanceof myClass;",
5151
);
5252

53-
expect(result).toBeTruthy();
53+
expect(result).toBe(true);
5454
});
5555

5656
test("instanceof inheritance", () => {
@@ -60,7 +60,7 @@ test("instanceof inheritance", () => {
6060
let inst = new childClass(); return inst instanceof myClass;
6161
`);
6262

63-
expect(result).toBeTruthy();
63+
expect(result).toBe(true);
6464
});
6565

6666
test("instanceof inheritance false", () => {
@@ -73,15 +73,33 @@ test("instanceof inheritance false", () => {
7373
expect(result).toBe(false);
7474
});
7575

76+
test("{} instanceof Object", () => {
77+
const result = util.transpileAndExecute("return {} instanceof Object;");
78+
79+
expect(result).toBe(true);
80+
});
81+
82+
test("function instanceof Object", () => {
83+
const result = util.transpileAndExecute("return (() => {}) instanceof Object;");
84+
85+
expect(result).toBe(true);
86+
});
87+
7688
test("null instanceof Object", () => {
77-
const result = util.transpileAndExecute("return (<any>null) instanceof Object;");
89+
const result = util.transpileAndExecute("return (null as any) instanceof Object;");
7890

7991
expect(result).toBe(false);
8092
});
8193

94+
test("instanceof undefined", () => {
95+
expect(() => {
96+
util.transpileAndExecute("return {} instanceof (undefined as any);");
97+
}).toThrow("Right-hand side of 'instanceof' is not an object");
98+
});
99+
82100
test("null instanceof Class", () => {
83101
const result = util.transpileAndExecute(
84-
"class myClass {} return (<any>null) instanceof myClass;",
102+
"class myClass {} return (null as any) instanceof myClass;",
85103
);
86104

87105
expect(result).toBe(false);
@@ -108,5 +126,23 @@ test("instanceof export", () => {
108126
"result",
109127
);
110128

111-
expect(result).toBeTruthy();
129+
expect(result).toBe(true);
130+
});
131+
132+
test("instanceof Symbol.hasInstance", () => {
133+
const result = util.transpileAndExecute(`
134+
class myClass {
135+
static [Symbol.hasInstance]() {
136+
return false;
137+
}
138+
}
139+
140+
const inst = new myClass();
141+
const isInstanceOld = inst instanceof myClass;
142+
myClass[Symbol.hasInstance] = () => true;
143+
const isInstanceNew = inst instanceof myClass;
144+
return isInstanceOld !== isInstanceNew;
145+
`);
146+
147+
expect(result).toBe(true);
112148
});

0 commit comments

Comments
 (0)