Skip to content

Commit c3b3a8b

Browse files
authored
Fix/object assign non table source (#1642) (#1710)
* fix Object.assign crashing when source is a non-table value `{ ...(cond && obj) }` short-circuits to `false` when `cond` is falsy. JS treats this as a no-op (Object.assign coerces primitives to wrapper objects with no own enumerable properties), but `__TS__ObjectAssign` was iterating every source unconditionally, so `pairs(false)` errored at runtime. Skip non-table sources, matching the spec for null/undefined/primitive args. * refactor __TS__ObjectAssign to emit cleaner Lua Invert the type guard so it nests the spread loop instead of using `continue`, which lowered to a `repeat ... until true` + `break` dance. Same behavior, the bundled helper now reads like the hand-written original.
1 parent 5176fcd commit c3b3a8b

3 files changed

Lines changed: 23 additions & 2 deletions

File tree

src/lualib/ObjectAssign.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
export function __TS__ObjectAssign<T extends object>(this: void, target: T, ...sources: T[]): T {
33
for (const i of $range(1, sources.length)) {
44
const source = sources[i - 1];
5-
for (const key in source) {
6-
target[key] = source[key];
5+
if (type(source) === "table") {
6+
for (const key in source) {
7+
target[key] = source[key];
8+
}
79
}
810
}
911

test/unit/builtins/object.spec.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@ test.each([
1010
util.testExpression`Object.assign(${util.formatCode(initial)}, ${argsString})`.expectToMatchJsResult();
1111
});
1212

13+
test.each([
14+
"Object.assign({}, false)",
15+
"Object.assign({}, null)",
16+
"Object.assign({}, undefined)",
17+
"Object.assign({}, null, undefined)",
18+
"Object.assign({ a: 1 }, false, { b: 2 })",
19+
])("Object.assign skips non-object sources (%p)", expression => {
20+
util.testExpression(expression).expectToMatchJsResult();
21+
});
22+
1323
test.each([{}, { abc: 3 }, { abc: 3, def: "xyz" }])("Object.entries (%p)", obj => {
1424
const testBuilder = util.testExpressionTemplate`Object.entries(${obj})`;
1525
// Need custom matcher because order is not guaranteed in neither JS nor Lua

test/unit/spread.spec.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,15 @@ describe("in object literal", () => {
119119
util.testExpression(expression).expectToMatchJsResult();
120120
});
121121

122+
test.each([
123+
"{ ...((false && { a: 1 }) as any) }",
124+
"{ ...((true && { a: 1 }) as any) }",
125+
"{ a: 1, ...((false && { b: 2 }) as any) }",
126+
"{ ...(null as any), ...(undefined as any) }",
127+
])("of short-circuited operand (%p)", expression => {
128+
util.testExpression(expression).expectToMatchJsResult();
129+
});
130+
122131
test("of object reference", () => {
123132
util.testFunction`
124133
const object = { x: 0, y: 1 };

0 commit comments

Comments
 (0)