Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions build_lualib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ import { LuaLib } from "./src/LuaLib";
const options: tstl.CompilerOptions = {
skipLibCheck: true,
types: [],
target: ts.ScriptTarget.ESNext,
lib: ["lib.esnext.d.ts"],

outDir: path.join(__dirname, "./dist/lualib"),
rootDir: path.join(__dirname, "./src/lualib"),

luaLibImport: tstl.LuaLibImportKind.None,
luaTarget: tstl.LuaTarget.Lua51,
noHeader: true,
outDir: path.join(__dirname, "./dist/lualib"),
rootDir: path.join(__dirname, "./src/lualib"),
};

// TODO: Check diagnostics
Expand Down
2 changes: 2 additions & 0 deletions src/LuaLib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export enum LuaLibFeature {
FunctionCall = "FunctionCall",
Index = "Index",
InstanceOf = "InstanceOf",
InstanceOfObject = "InstanceOfObject",
Iterator = "Iterator",
Map = "Map",
NewIndex = "NewIndex",
Expand All @@ -51,6 +52,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],
Expand Down
17 changes: 8 additions & 9 deletions src/LuaTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2658,14 +2658,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:
Expand Down Expand Up @@ -4533,11 +4537,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);
}

Expand Down
9 changes: 9 additions & 0 deletions src/lualib/InstanceOf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions src/lualib/InstanceOfObject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
function __TS__InstanceOfObject(this: void, value: unknown): boolean {
const valueType = type(value);
return valueType === "table" || valueType === "function";
}
1 change: 1 addition & 0 deletions src/lualib/Symbol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ function __TS__Symbol(description?: string | number): symbol {

Symbol = {
iterator: __TS__Symbol('Symbol.iterator'),
hasInstance: __TS__Symbol('Symbol.hasInstance'),
} as any;
4 changes: 4 additions & 0 deletions src/lualib/declarations/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
declare function type(
this: void,
value: any
): "nil" | "number" | "string" | "boolean" | "table" | "function" | "thread" | "userdata";
46 changes: 41 additions & 5 deletions test/unit/typechecking.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand All @@ -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", () => {
Expand All @@ -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 (<any>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 (<any>null) instanceof myClass;",
"class myClass {} return (null as any) instanceof myClass;",
);

expect(result).toBe(false);
Expand All @@ -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);
});