Skip to content

(4) class objects: new / instanceof / typeof on a class-object value #1789

Closed

Description

@proggeramlug

Part of #1785. Design: #1772. Depends on #1786.

(4) new / instanceof / typeof on a class-object value

Scope

Make the instance-side operations work when the class is a class-object value (POINTER) rather than INT32(class_id):

  • new (classObjectValue)(args) → read class_id from the class object, instantiate via the existing class_id machinery.
  • x instanceof classObjectValue → resolve the class_id from the value and walk the class chain.
  • typeof classObjectValue === "function" (classes are callable in JS).
  • extends: js_register_class_parent_dynamic already reads class_id from a POINTER — confirm it stays correct with the class-object representation.

Acceptance criteria

  • const C = make(x); const c = new C(); c instanceof C === true.
  • typeof make(x) === "function".
  • effect's class X extends make(...) instances (where used) behave correctly.
  • No regression to new/instanceof on top-level class declarations.

Blocks

(6).

Activity

  1. proggeramlug commented on May 25, 2026

    @proggeramlug
    ContributorAuthor

    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), 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.

  2. proggeramlug commented on May 27, 2026

    @proggeramlug
    ContributorAuthor

    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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions