You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
runtime: new Blob(x) where x is an Array subclass stringifies the constructor instead of iterating elements #6232
new Blob(x) where x is an instance of a subclass of Array (e.g. class MyArray extends Array {}) does not iterate the elements — it treats the instance as a single non-array part and stringifies its constructor. A plain array of the same values works. Wrong result, no error.
The BlobPart-array detection appears to check for the exact Array and misses Array subclasses. It should use the iterable/Symbol.iterator path (the constructor takes any iterable of parts).
perry release binary dated 2026-07-05, PERRY_NO_AUTO_OPTIMIZE=1, with libperry_stdlib.a. Found via secret-tests/bun-suite; from Bun's web/fetch/blob-array-fast-path.test.ts ("DerivedArray").
Update after digging in: this is not a Blob-part-detection bug — it's that subclassing Array is broadly unsupported, and the Blob path just surfaced it. On the current build:
classMyArrayextendsArray{}MyArray.from(["x","y","z"])// → typeof "function", not an array (returns garbage, not an instance)consta=newMyArray();a.push("p")// → TypeError: value is not a function
So MyArray.from(...) never produces an array instance for new Blob(...) to iterate; the Blob code then ToStrings whatever it got. Fixing the Blob branch would not help until class extends Array (subclassing the builtin — cf. #6105) produces real array-backed instances with working inherited statics (from) and methods (push). Recommend tracking this under the subclass-builtin work rather than as a Blob issue.
Confirmed this is a feature gap, not a targeted bug — closing the loop on scope for whoever picks it up.
class extends Array is essentially unimplemented. Even the explicit-super() path is broken:
classAextendsArray{constructor(){super();}}consta=newA();a.push(1);// TypeError: value is not a functionArray.isArray(a)// false
Root cause: js_array_subclass_init (perry-runtime/.../node_stream_constructors/builders.rs) only sets a length field and installs a single fill method on a plain GC_TYPE_OBJECT; the instance is never array-backed and gets none of push/pop/map/index behavior, and Array.isArray doesn't recognize it. Inherited statics are also broken (A.from(...) returns a non-array).
A real fix needs exotic-array-object semantics for subclass instances (array-backed storage or full method-inheritance + length exotic behavior) plus inherited-static construction — i.e. proper builtin-subclassing, adjacent to #6105. Recommend tracking as a feature rather than a patch. (The Blob symptom that filed this is downstream and resolves for free once instances are real arrays.)
Investigated with a build (perry @ current main). The diagnosis in this issue is wrong — the Blob part-walker is fine; the real bug is upstream in Array.from/Array.of static dispatch on a subclass.
Probe:
classMyArrayextendsArray{}constarr=MyArray.from(["hello"," ","derived"]);typeofarr// perry: "function" node: "object"Array.isArray(arr)// perry: false node: true(arrasany).length// perry: undefined node: 3[...arr]// perry: throws "value is not iterable"typeofMyArray.from// perry: "undefined" node: "function"typeofMyArray.of// perry: "undefined" node: "function"Array.from.call(MyArray,["m","n"])// perry: object len 2 (elements ARE built)
So MyArray.from(...) returns the constructor function, not an array (Blob then stringifies that function — hence the "36 / function MyArray()" output). Root cause: static methods are not inherited from a builtin Array parent — MyArray.from/MyArray.of resolve to undefined, and the MyArray.from(...)call falls back to yielding the class ref. Array.from.call(MyArray, …) shows the underlying array_from_full does honor a subclass this (it built len 2), so the missing piece is wiring the builtin constructor's statics onto subclasses of Array (see install_builtin_constructor_statics in object/global_this/install_static.rs, and the no-own-ctor/builtin_parent_runtime path in lower_call/new.rs), not the Blob walker.
(Also independent: new MyArray(3) returns a plain object with isArray:false, length:0 — the documented "subclass modeled as a plain object" limitation — so even a fixed .from would produce an array-like object, not a genuine exotic Array. Full class X extends Array exotic-array behavior is a larger effort; wiring inherited statics from/of is the scoped fix that resolves this repro.)
Retitling/repurposing this toward "Array subclass does not inherit static from/of from the builtin Array" would be accurate. Not shipping a Blob patch since the Blob code is correct.
Data point from the pi bring-up (wall #7 stretch assessment, see #6658 / PR #6659): the class extends Set family is further along than the class extends Array case this issue tracks, and the remaining gap is precisely scoped to dynamic construction.
classAextendsSet{}newA().add("x");// WORKS (static new, decl)constC: any=classextendsSet{};newC().add("x");// WORKS (static new, class expr)constobj: any={};obj.A=A;newobj.A().add("x");// FAILS: "add is not a function"// the gaxios/webpack-CJS shape (same failure):exportsObj.GaxiosInterceptorManager=classextendsSet{};newexportsObj.GaxiosInterceptorManager().add(...);
Root cause of the failing shapes: the Map/Set backing init (js_map_set_subclass_init, which allocates the real SetHeader/MapHeader and stashes it on the instance) is emitted by codegen at static new sites only — lower_call/new_helpers.rs::emit_native_instance_base_init. A new through a runtime value (property read, laundered local, cross-module export object) takes the dynamic-construct path, which never installs the backing for ANY native instance base (Set, Map, Event, CustomEvent, EventEmitter — the whole NativeInstanceBase enum), so the instance has no collection storage and every method read misses.
A general fix needs: a class-id → native-base registry populated at class registration (codegen knows the base statically), the dynamic-new runtime path consulting it and running the same base init with forwarded ctor args (seeding semantics: new Seeded([["k",9]])), for the full native-base family — i.e. this issue's feature, not a bounded Set-only patch. Method inheritance itself is NOT the problem: once the backing exists, dispatch already serves the whole surface via subclass_backing_of (methods, for…of, .size).
Practical impact: gaxios instantiates class extends Set interceptor managers per Gaxios instance through exactly the failing shape — this is the projected pi bring-up wall #8 once execution goes past --help/--version (those two are clean as of #6659, GATE 1 reached).
Activity
proggeramlug commentedon Jul 10, 2026
Update after digging in: this is not a Blob-part-detection bug — it's that subclassing
Arrayis broadly unsupported, and the Blob path just surfaced it. On the current build:So
MyArray.from(...)never produces an array instance fornew Blob(...)to iterate; the Blob code then ToStrings whatever it got. Fixing the Blob branch would not help untilclass extends Array(subclassing the builtin — cf. #6105) produces real array-backed instances with working inherited statics (from) and methods (push). Recommend tracking this under the subclass-builtin work rather than as a Blob issue.proggeramlug commentedon Jul 10, 2026
Confirmed this is a feature gap, not a targeted bug — closing the loop on scope for whoever picks it up.
class extends Arrayis essentially unimplemented. Even the explicit-super()path is broken:Root cause:
js_array_subclass_init(perry-runtime/.../node_stream_constructors/builders.rs) only sets alengthfield and installs a singlefillmethod on a plainGC_TYPE_OBJECT; the instance is never array-backed and gets none ofpush/pop/map/index behavior, andArray.isArraydoesn't recognize it. Inherited statics are also broken (A.from(...)returns a non-array).A real fix needs exotic-array-object semantics for subclass instances (array-backed storage or full method-inheritance + length exotic behavior) plus inherited-static construction — i.e. proper builtin-subclassing, adjacent to #6105. Recommend tracking as a feature rather than a patch. (The Blob symptom that filed this is downstream and resolves for free once instances are real arrays.)
proggeramlug commentedon Jul 10, 2026
Investigated with a build (
perry@ current main). The diagnosis in this issue is wrong — the Blob part-walker is fine; the real bug is upstream inArray.from/Array.ofstatic dispatch on a subclass.Probe:
So
MyArray.from(...)returns the constructor function, not an array (Blob then stringifies that function — hence the "36 / function MyArray()" output). Root cause: static methods are not inherited from a builtinArrayparent —MyArray.from/MyArray.ofresolve toundefined, and theMyArray.from(...)call falls back to yielding the class ref.Array.from.call(MyArray, …)shows the underlyingarray_from_fulldoes honor a subclassthis(it builtlen 2), so the missing piece is wiring the builtin constructor's statics onto subclasses of Array (seeinstall_builtin_constructor_staticsinobject/global_this/install_static.rs, and the no-own-ctor/builtin_parent_runtimepath inlower_call/new.rs), not the Blob walker.(Also independent:
new MyArray(3)returns a plain object withisArray:false, length:0— the documented "subclass modeled as a plain object" limitation — so even a fixed.fromwould produce an array-like object, not a genuine exotic Array. Fullclass X extends Arrayexotic-array behavior is a larger effort; wiring inherited staticsfrom/ofis the scoped fix that resolves this repro.)Retitling/repurposing this toward "Array subclass does not inherit static
from/offrom the builtin Array" would be accurate. Not shipping a Blob patch since the Blob code is correct.feat(class): `class X extends Array` — instance methods + iteration (P…
feat(class): `class X extends Array` — instance methods + iteration (P…
feat(class): `class X extends Array` — instance methods + iteration (#…
proggeramlug commentedon Jul 19, 2026
Data point from the pi bring-up (wall #7 stretch assessment, see #6658 / PR #6659): the
class extends Setfamily is further along than theclass extends Arraycase this issue tracks, and the remaining gap is precisely scoped to dynamic construction.Probed on current main + #6659 (node v26 parity):
Root cause of the failing shapes: the Map/Set backing init (
js_map_set_subclass_init, which allocates the realSetHeader/MapHeaderand stashes it on the instance) is emitted by codegen at staticnewsites only —lower_call/new_helpers.rs::emit_native_instance_base_init. Anewthrough a runtime value (property read, laundered local, cross-module export object) takes the dynamic-construct path, which never installs the backing for ANY native instance base (Set, Map, Event, CustomEvent, EventEmitter — the wholeNativeInstanceBaseenum), so the instance has no collection storage and every method read misses.A general fix needs: a class-id → native-base registry populated at class registration (codegen knows the base statically), the dynamic-new runtime path consulting it and running the same base init with forwarded ctor args (seeding semantics:
new Seeded([["k",9]])), for the full native-base family — i.e. this issue's feature, not a bounded Set-only patch. Method inheritance itself is NOT the problem: once the backing exists, dispatch already serves the whole surface viasubclass_backing_of(methods,for…of,.size).Practical impact: gaxios instantiates
class extends Setinterceptor managers per Gaxios instance through exactly the failing shape — this is the projected pi bring-up wall #8 once execution goes past--help/--version(those two are clean as of #6659, GATE 1 reached).