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
(4) class objects: new / instanceof / typeof on a class-object value #1789
Current state on a class-object VALUE: typeof C → "object" (need "function"), new C() → no-op, c instanceof C → false.
Keystone: a per-object class discriminator. A class object and its instances SHARE class_id (= template), so class_id alone can't distinguish them — typeof must return "function" for the class object but "object" for an instance. Add OBJECT_TYPE_CLASS (next to OBJECT_TYPE_REGULAR=1, OBJECT_TYPE_ERROR=2 in crates/perry-runtime/src/error.rs) and stamp it on the ClassExprFresh allocation (codegen expr/static_field_meta.rs, after js_object_alloc).
Blast radius is bounded (~8 object_type != REGULAR gates): own-field get/set are gated behind gc_type == GC_TYPE_OBJECT / class_id != 0 checks the class object already satisfies, so flipping object_type is safe for the foundation's own-field reads (verified while building #1787). Two caching/typed-feedback gates (field_get_set.rs:2093, typed_feedback/guards.rs) should treat CLASS like REGULAR.
Then:
typeof: js_value_typeof → if POINTER object with object_type == OBJECT_TYPE_CLASS → "function".
new (classObjValue)(): read class_id from the POINTER's ObjectHeader and route through the existing class_id instantiation machinery.
instanceof: resolve class_id from the POINTER and walk the class chain (js_register_class_parent_dynamic already reads class_id from a POINTER — confirm the instanceof path does too).
Coordinate with #1790: the same OBJECT_TYPE_CLASS discriminator + the static-field/prototype slots are what the #1096 layout descriptor must mark as pointer-bearing. Recommend landing the discriminator once, shared between this and #1790, rather than twice.
Done — new / instanceof / typeof on a class-object value is shipped and merged in PR #1795, byte-identical to node with zero regressions:
Added the per-object OBJECT_TYPE_CLASS discriminator (stamped on the ClassExprFresh allocation via js_object_mark_class) so a class object is told apart from its instances, which share the template's class_id.
typeof aClassObject === "function"; new (classObjectValue)() reads class_id from the POINTER and instantiates (methods dispatch); x instanceof classObjectValue resolves class_id from the POINTER and walks the chain.
Gap test: test_gap_class_expr_new_instanceof.ts. Note: per-evaluation class objects share the template's class_id, so cross-evaluation instanceof (one make() result vs a different one) isn't distinguished — inherent to the shared-class_id model. The OBJECT_TYPE_CLASS discriminator is also what #1790's GC layout descriptor should reuse. Closing as complete (part of epic #1785).
Activity
fix(hir,codegen): class expressions → heap class objects with per-eva…
proggeramlug commentedon May 25, 2026
Implementation blueprint (after #1787/#1788 in PR #1793)
Current state on a class-object VALUE:
typeof C→"object"(need"function"),new C()→ no-op,c instanceof C→ false.Keystone: a per-object class discriminator. A class object and its instances SHARE
class_id(= template), soclass_idalone can't distinguish them —typeofmust return"function"for the class object but"object"for an instance. AddOBJECT_TYPE_CLASS(next toOBJECT_TYPE_REGULAR=1,OBJECT_TYPE_ERROR=2incrates/perry-runtime/src/error.rs) and stamp it on theClassExprFreshallocation (codegenexpr/static_field_meta.rs, afterjs_object_alloc).Blast radius is bounded (~8
object_type != REGULARgates): own-field get/set are gated behindgc_type == GC_TYPE_OBJECT/class_id != 0checks the class object already satisfies, so flippingobject_typeis safe for the foundation's own-field reads (verified while building #1787). Two caching/typed-feedback gates (field_get_set.rs:2093,typed_feedback/guards.rs) should treat CLASS like REGULAR.Then:
typeof:js_value_typeof→ if POINTER object withobject_type == OBJECT_TYPE_CLASS→"function".new (classObjValue)(): readclass_idfrom the POINTER'sObjectHeaderand route through the existing class_id instantiation machinery.instanceof: resolveclass_idfrom the POINTER and walk the class chain (js_register_class_parent_dynamicalready reads class_id from a POINTER — confirm the instanceof path does too).Coordinate with #1790: the same
OBJECT_TYPE_CLASSdiscriminator + the static-field/prototype slots are what the #1096 layout descriptor must mark as pointer-bearing. Recommend landing the discriminator once, shared between this and #1790, rather than twice.fix(runtime,codegen): new / instanceof / typeof on a class-object val…
fix(runtime): construct instances from class-ref values held as first…
proggeramlug commentedon May 27, 2026
Done —
new/instanceof/typeofon a class-object value is shipped and merged in PR #1795, byte-identical to node with zero regressions:OBJECT_TYPE_CLASSdiscriminator (stamped on theClassExprFreshallocation viajs_object_mark_class) so a class object is told apart from its instances, which share the template's class_id.typeof aClassObject === "function";new (classObjectValue)()reads class_id from the POINTER and instantiates (methods dispatch);x instanceof classObjectValueresolves class_id from the POINTER and walks the chain.Gap test:
test_gap_class_expr_new_instanceof.ts. Note: per-evaluation class objects share the template's class_id, so cross-evaluationinstanceof(onemake()result vs a different one) isn't distinguished — inherent to the shared-class_id model. TheOBJECT_TYPE_CLASSdiscriminator is also what #1790's GC layout descriptor should reuse. Closing as complete (part of epic #1785).