Skip to content

Commit c8cb103

Browse files
committed
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.
1 parent 5176fcd commit c8cb103

3 files changed

Lines changed: 20 additions & 0 deletions

File tree

src/lualib/ObjectAssign.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
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+
if (type(source) !== "table") continue;
56
for (const key in source) {
67
target[key] = source[key];
78
}

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)