Skip to content
Open
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
11 changes: 11 additions & 0 deletions src/lualib/SetDescriptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,17 @@ export function __TS__SetDescriptor(
setmetatable(target, metatable);
}

// When setting a descriptor on an instance (not a prototype), ensure it has
// its own metatable so descriptors are not shared across instances.
// See: https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1625
if (!isPrototype && !rawget(metatable, "_isOwnDescriptorMetatable")) {
const instanceMetatable: any = {};
instanceMetatable._isOwnDescriptorMetatable = true;
setmetatable(instanceMetatable, metatable);
setmetatable(target, instanceMetatable);
metatable = instanceMetatable;
}

const value = rawget(target, key);
if (value !== undefined) rawset(target, key, undefined);

Expand Down
75 changes: 75 additions & 0 deletions test/unit/builtins/object.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,81 @@ describe("Object.defineProperty", () => {
return { prop: foo.bar, err };
`.expectToMatchJsResult();
});

// https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1625
test("instance isolation", () => {
util.testFunction`
class Test {
declare obj: object;
constructor() {
Object.defineProperty(this, "obj", { value: {}, writable: true, configurable: true });
}
}
const t1 = new Test();
const t2 = new Test();
return t1.obj === t2.obj;
`.expectToMatchJsResult();
});

test("instance isolation with three instances", () => {
util.testFunction`
class Test {
declare obj: object;
constructor() {
Object.defineProperty(this, "obj", { value: {}, writable: true, configurable: true });
}
}
const t1 = new Test();
const t2 = new Test();
const t3 = new Test();
return [t1.obj === t2.obj, t1.obj === t3.obj, t2.obj === t3.obj];
`.expectToMatchJsResult();
});

test("instance isolation with mutation", () => {
util.testFunction`
class Test {
declare value: number;
constructor(v: number) {
Object.defineProperty(this, "value", { value: v, writable: true, configurable: true });
}
}
const t1 = new Test(1);
const t2 = new Test(2);
return [t1.value, t2.value];
`.expectToMatchJsResult();
});

test("instance isolation with multiple properties", () => {
util.testFunction`
class Test {
declare a: string;
declare b: string;
constructor(a: string, b: string) {
Object.defineProperty(this, "a", { value: a, writable: true, configurable: true });
Object.defineProperty(this, "b", { value: b, writable: true, configurable: true });
}
}
const t1 = new Test("x", "y");
const t2 = new Test("p", "q");
return [t1.a, t1.b, t2.a, t2.b];
`.expectToMatchJsResult();
});

test("instance isolation preserves prototype methods", () => {
util.testFunction`
class Test {
declare val: number;
constructor(v: number) {
Object.defineProperty(this, "val", { value: v, writable: true, configurable: true });
}
getVal() { return this.val; }
}
const t1 = new Test(10);
const t2 = new Test(20);
return [t1.getVal(), t2.getVal()];
`.expectToMatchJsResult();
});
});

describe("Object.getOwnPropertyDescriptor", () => {
Expand Down
Loading