[...arr] on a class X extends Array instance throws TypeError: value is not iterable where node spreads it normally.
class MyArr extends Array {}
const sub = MyArr.from([1, 2, 3]);
console.log([...sub]); // node: [ 1, 2, 3 ] perry: TypeError: value is not iterable
console.log(Array.isArray([...sub])); // node: true perry: TypeError
Where
array_from_spread_value (crates/perry-runtime/src/array/iterator.rs) has a branch for exactly this:
if crate::array::is_array_subclass_instance(value())
&& !crate::array::array_subclass_has_iterator_override(value())
{
let snap = crate::array::array_subclass_dense_snapshot(value());
return crate::value::js_nanbox_get_pointer(snap) as *mut ArrayHeader;
}
so one of is_array_subclass_instance or array_subclass_has_iterator_override is answering wrong for an instance produced by the inherited Array.from (MyArr.from(...)), and the value then falls all the way through to throw_not_iterable. Worth checking whether a directly-constructed new MyArr(...) behaves differently from an inherited-static-produced one — that split is the known-weak "native base-class subclassing" family (CLAUDE.md).
Provenance
Found while building a 32-case [...arr] semantics matrix for #7533. Pre-existing: reproduces byte-identically at f06270d06 (before the #7495/#7516/#7527 rooting stack) and is unchanged by #7540's dense fast path, which deliberately excludes subclass instances — they are GC_TYPE_OBJECT, not GC_TYPE_ARRAY, so they never reach the copy.
Every other case in that matrix matches node, including holes, sparse arrays, past-capacity indices, frozen arrays, index accessors, own @@iterator, strings, Set, Map, generators, arr.values()/arr.entries(), multi-element and call spreads.
[...arr]on aclass X extends Arrayinstance throwsTypeError: value is not iterablewhere node spreads it normally.Where
array_from_spread_value(crates/perry-runtime/src/array/iterator.rs) has a branch for exactly this:so one of
is_array_subclass_instanceorarray_subclass_has_iterator_overrideis answering wrong for an instance produced by the inheritedArray.from(MyArr.from(...)), and the value then falls all the way through tothrow_not_iterable. Worth checking whether a directly-constructednew MyArr(...)behaves differently from an inherited-static-produced one — that split is the known-weak "native base-class subclassing" family (CLAUDE.md).Provenance
Found while building a 32-case
[...arr]semantics matrix for #7533. Pre-existing: reproduces byte-identically atf06270d06(before the #7495/#7516/#7527 rooting stack) and is unchanged by #7540's dense fast path, which deliberately excludes subclass instances — they areGC_TYPE_OBJECT, notGC_TYPE_ARRAY, so they never reach the copy.Every other case in that matrix matches node, including holes, sparse arrays, past-capacity indices, frozen arrays, index accessors, own
@@iterator, strings,Set,Map, generators,arr.values()/arr.entries(), multi-element and call spreads.