Skip to content

Commit 42b3568

Browse files
authored
Fix class .toString inheritance (#882)
* Fix class .toString inheritance * Move tests to builtins/object and add few more toString tests
1 parent 1e4a8bb commit 42b3568

File tree

5 files changed

+97
-36
lines changed

5 files changed

+97
-36
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525

2626
- Fixed iteration over generators stopping at first yielded `nil` value
2727

28+
- Fixed extending a class not keeping `toString` implementation from a super class
29+
2830
## 0.33.0
2931

3032
- Added support for nullish coalescing `A ?? B`.

src/lualib/ClassExtends.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ function __TS__ClassExtends(this: void, target: LuaClass, base: LuaClass): void
1616
// Re-add metatable events defined by accessors with `__TS__SetDescriptor`
1717
if (typeof base.prototype.__index === "function") target.prototype.__index = base.prototype.__index;
1818
if (typeof base.prototype.__newindex === "function") target.prototype.__newindex = base.prototype.__newindex;
19+
if (typeof base.prototype.__tostring === "function") target.prototype.__tostring = base.prototype.__tostring;
1920
}

src/lualib/declarations/tstl.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ interface Metatable {
1010
_descriptors?: Record<string, PropertyDescriptor>;
1111
__index?: any;
1212
__newindex?: any;
13+
__tostring?: any;
1314
}
1415

1516
interface LuaClass extends Metatable {

test/unit/builtins/object.spec.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,96 @@ test.each(["[]", '[["a", 1], ["b", 2]]', '[["a", 1], ["a", 2]]', 'new Map([["foo
2828
util.testExpression`Object.fromEntries(${entries})`.expectToMatchJsResult();
2929
}
3030
);
31+
32+
describe(".toString()", () => {
33+
const toStringDeclaration = `
34+
function toString(value: object) {
35+
const result = value.toString();
36+
return result === "[object Object]" || result.startsWith("table: ") ? "table" : result;
37+
}
38+
`;
39+
40+
test("class override", () => {
41+
util.testFunction`
42+
${toStringDeclaration}
43+
class A {
44+
public toString() {
45+
return "A";
46+
}
47+
}
48+
49+
return toString(new A());
50+
`.expectToMatchJsResult();
51+
});
52+
53+
test("inherited class override", () => {
54+
util.testFunction`
55+
${toStringDeclaration}
56+
class A {
57+
public toString() {
58+
return "A";
59+
}
60+
}
61+
62+
class B extends A {}
63+
64+
return { A: toString(new A()), B: toString(new B()) };
65+
`.expectToMatchJsResult();
66+
});
67+
68+
test("don't affect inherited class", () => {
69+
util.testFunction`
70+
${toStringDeclaration}
71+
class A {}
72+
73+
class B extends A {
74+
public toString() {
75+
return "B";
76+
}
77+
}
78+
79+
return { A: toString(new A()), B: toString(new B()) };
80+
`.expectToMatchJsResult();
81+
});
82+
83+
test("override inherited class override", () => {
84+
util.testFunction`
85+
${toStringDeclaration}
86+
class A {
87+
public toString() {
88+
return "A";
89+
}
90+
}
91+
92+
class B extends A {
93+
public toString() {
94+
return "B";
95+
}
96+
}
97+
98+
return { A: toString(new A()), B: toString(new B()) };
99+
`.expectToMatchJsResult();
100+
});
101+
});
102+
103+
describe(".hasOwnProperty()", () => {
104+
test("class field", () => {
105+
util.testFunction`
106+
class A {
107+
public field = true;
108+
}
109+
110+
return new A().hasOwnProperty("field");
111+
`.expectToMatchJsResult();
112+
});
113+
114+
test("class method", () => {
115+
util.testFunction`
116+
class A {
117+
public method() {}
118+
}
119+
120+
return new A().hasOwnProperty("method");
121+
`.expectToMatchJsResult();
122+
});
123+
});

test/unit/classes/classes.spec.ts

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -352,42 +352,6 @@ test("ClassComputedMethodCall", () => {
352352
`.expectToMatchJsResult();
353353
});
354354

355-
test("ClassToString", () => {
356-
util.testFunction`
357-
class a {
358-
public toString(): string {
359-
return "instance of a";
360-
}
361-
}
362-
let inst = new a();
363-
return inst.toString();
364-
`.expectToMatchJsResult();
365-
});
366-
367-
test("HasOwnProperty true", () => {
368-
util.testFunction`
369-
class a {
370-
public test(): void {
371-
}
372-
}
373-
let inst = new a();
374-
inst["prop"] = 17;
375-
return inst.hasOwnProperty("prop");
376-
`.expectToMatchJsResult();
377-
});
378-
379-
test("HasOwnProperty false", () => {
380-
util.testFunction`
381-
class a {
382-
public test(): void {
383-
}
384-
}
385-
let inst = new a();
386-
inst["prop"] = 17;
387-
return inst.hasOwnProperty("test");
388-
`.expectToMatchJsResult();
389-
});
390-
391355
test("CastClassMethodCall", () => {
392356
util.testFunction`
393357
interface result

0 commit comments

Comments
 (0)