Summary
Array.prototype.concat should accept any number of arguments, return a new array, leave the receiver unchanged, and follow Symbol.isConcatSpreadable / array-like spreadability rules. Perry currently handles only the single-argument static path, returns the receiver unchanged for multi-arg static calls, and dynamic dispatch calls the internal mutating concat helper.
Node parity probe
Run with Node v25.9.0:
for (const [label, fn] of [
["concat one array", () => [1].concat([2, 3])],
["concat multiple", () => [1].concat([2], [3, 4], 5)],
["concat preserves original", () => { const a = [1]; const out = a.concat([2]); return [out, a, out === a]; }],
["concat spreadable false", () => { const x = [2, 3]; x[Symbol.isConcatSpreadable] = false; return [1].concat(x); }],
["concat array-like spreadable true", () => { const x = {0: "a", 1: "b", length: 2, [Symbol.isConcatSpreadable]: true}; return [1].concat(x); }],
]) {
try {
console.log(label + ": " + JSON.stringify(fn()));
} catch (e) {
console.log(label + ": throws " + e.name + ": " + e.message);
}
}
Output:
concat one array: [1,2,3]
concat multiple: [1,2,3,4,5]
concat preserves original: [[1,2],[1],false]
concat spreadable false: [1,[2,3]]
concat array-like spreadable true: [1,"a","b"]
Current Perry behavior in source
The current static path is intentionally single-argument only:
crates/perry-codegen/src/lower_array_method.rs:106-132 routes one-arg concat to js_array_concat_new, but the comment says "For simplicity we only handle single-argument concat." and args.len() != 1 returns recv_box unchanged.
crates/perry-runtime/src/array/concat_reverse.rs:94-124 implements js_array_concat_new(arr, other) for exactly one other array.
Dynamic dispatch uses the mutating helper:
crates/perry-runtime/src/object/native_call_method.rs:1255-1260 handles dynamic "concat" by reading only the first argument and calling crate::array::js_array_concat(arr, other_ptr).
crates/perry-runtime/src/array/concat_reverse.rs:5-92 documents js_array_concat as an append-into-destination helper used by spread/desugaring, while js_array_concat_new is the JS-semantic non-mutating helper.
The fixture comments still call out multi-arg concat as a known divergence:
test-files/test_compat_arrays_iterators.ts:5-8 excludes multi-arg concat from parity coverage, while line 40 covers only the single-argument case.
Expected fix shape
Implement full JS concat behavior for static and dynamic paths:
- Accept zero or more concat arguments.
- Always return a new array and leave the receiver unchanged.
- Append each argument in order.
- Spread arrays by default.
- Respect
Symbol.isConcatSpreadable === false for arrays and === true for array-like objects.
- Preserve non-array/non-spreadable values as single elements.
Please add regression coverage for zero args, one arg, multiple args, primitive/non-array arguments, receiver immutability, Symbol.isConcatSpreadable false on arrays, and spreadable array-like objects.
Summary
Array.prototype.concatshould accept any number of arguments, return a new array, leave the receiver unchanged, and followSymbol.isConcatSpreadable/ array-like spreadability rules. Perry currently handles only the single-argument static path, returns the receiver unchanged for multi-arg static calls, and dynamic dispatch calls the internal mutating concat helper.Node parity probe
Run with Node v25.9.0:
Output:
Current Perry behavior in source
The current static path is intentionally single-argument only:
crates/perry-codegen/src/lower_array_method.rs:106-132routes one-argconcattojs_array_concat_new, but the comment says "For simplicity we only handle single-argument concat." andargs.len() != 1returnsrecv_boxunchanged.crates/perry-runtime/src/array/concat_reverse.rs:94-124implementsjs_array_concat_new(arr, other)for exactly oneotherarray.Dynamic dispatch uses the mutating helper:
crates/perry-runtime/src/object/native_call_method.rs:1255-1260handles dynamic"concat"by reading only the first argument and callingcrate::array::js_array_concat(arr, other_ptr).crates/perry-runtime/src/array/concat_reverse.rs:5-92documentsjs_array_concatas an append-into-destination helper used by spread/desugaring, whilejs_array_concat_newis the JS-semantic non-mutating helper.The fixture comments still call out multi-arg concat as a known divergence:
test-files/test_compat_arrays_iterators.ts:5-8excludesmulti-arg concatfrom parity coverage, while line 40 covers only the single-argument case.Expected fix shape
Implement full JS concat behavior for static and dynamic paths:
Symbol.isConcatSpreadable === falsefor arrays and=== truefor array-like objects.Please add regression coverage for zero args, one arg, multiple args, primitive/non-array arguments, receiver immutability,
Symbol.isConcatSpreadablefalse on arrays, and spreadable array-like objects.