Skip to content

fix(runtime,codegen): new / instanceof / typeof on a class-object value (#1789) - #1795

Merged
proggeramlug merged 1 commit into
mainfrom
worktree-class-obj-1789
May 25, 2026
Merged

fix(runtime,codegen): new / instanceof / typeof on a class-object value (#1789)#1795
proggeramlug merged 1 commit into
mainfrom
worktree-class-obj-1789

Conversation

@proggeramlug

Copy link
Copy Markdown
Contributor

#1789new / instanceof / typeof on a class-object value

Part of epic #1785 (design #1772). Builds on the merged #1786 foundation + #1787/#1788 (#1792, #1793). Validated byte-for-byte against node, zero regressions.

What

Make the instance-side operations work when the class is a class-expression value (a heap class object) rather than INT32(class_id). Introduces the per-object discriminator OBJECT_TYPE_CLASS so a class object is distinguished from its instances (which share the template's class_id):

  • stamp: ClassExprFresh codegen marks the allocation via js_object_mark_classobject_type = OBJECT_TYPE_CLASS.
  • typeof aClassObject"function" (classes are callable), matching the existing INT32-ClassRef case.
  • new (classObjectValue)(args): js_new_function_construct reads the object's class_id and allocates an instance of it → instance methods dispatch, instanceof matches.
  • x instanceof classObjectValue: js_instanceof_dynamic now resolves the class_id from a POINTER class object (previously INT32-tag only) and walks the chain.
  • shared is_class_object_ptr / is_class_object_value validate the GcHeader before reading object_type.

Why it's safe

Own-field get/set are gated on gc_type/class_id, not on object_type, so the foundation's own-field reads (#1786/#1787/#1788) are unaffected — confirmed by the unchanged gap tests + 17/17 class spot-check. The new tag also keeps class objects out of the instance-shaped PIC (which is keyed by the shared class_id), which is correct.

Coordination with #1790

The OBJECT_TYPE_CLASS discriminator is exactly the marker #1790's #1096 layout descriptor needs to scan a class object precisely (pointer-bearing static-field slots + the prototype link). It now exists — #1790 should reuse it rather than add its own.

Notes / scope

  • A class expression only becomes a heap class object when it has static fields/symbols; a static-less class { m(){} } remains an INT32 ClassRef (unchanged path).
  • Per-evaluation class objects share the template's class_id, so cross-evaluation instanceof (one make() result vs a different make() result) isn't distinguished — inherent to the shared-class_id model.
  • Constructor-body / field-initializer execution via dynamic new on a class-object value is a tracked refinement (static new ClassName() is unchanged).

Gap test test_gap_class_expr_new_instanceof.ts byte-identical to node.

Refs #1789, #1785, #1772.

…ue (#1789)

Make the instance-side operations work when the class is a class-expression
VALUE (a heap class object from #1786) rather than `INT32(class_id)`. Adds a
per-object discriminator `OBJECT_TYPE_CLASS` so a class object is told apart
from its instances (which share the template's class_id):

- ClassExprFresh codegen stamps the allocation via `js_object_mark_class`
  (object_type = OBJECT_TYPE_CLASS). Own-field get/set are gated on
  `gc_type`/`class_id`, not on this tag, so #1786/#1787/#1788 reads are
  unaffected; the tag also keeps class objects out of the instance-shaped
  PIC (which is keyed by the shared class_id).
- `typeof aClassObject` → "function" (classes are callable), matching the
  existing INT32-ClassRef case.
- `new (classObjectValue)(args)` → `js_new_function_construct` reads the
  object's class_id and allocates an instance of it, so instance methods
  dispatch and `instanceof` matches. (Constructor-body/field-init via dynamic
  new is a tracked refinement; static `new ClassName()` is unchanged.)
- `x instanceof classObjectValue` → `js_instanceof_dynamic` resolves the
  class_id from the POINTER (not just the INT32 tag) and walks the chain.

Shared helpers `is_class_object_ptr` / `is_class_object_value` validate the
GcHeader before reading `object_type`.

Gap test test_gap_class_expr_new_instanceof.ts byte-identical to node
(typeof/own-static/new+instanceof/method dispatch). Zero regressions (class
suite + #1786/#1787/#1788 gap tests unchanged, 17/17 spot-check).

Note: per-evaluation class objects share the template's class_id, so
cross-evaluation `instanceof` (one make() result vs a different make()
result) isn't distinguished — inherent to the shared-class_id model.

Refs #1789, #1785, #1772.
@proggeramlug
proggeramlug merged commit e73963d into main May 25, 2026
18 of 19 checks passed
@proggeramlug
proggeramlug deleted the worktree-class-obj-1789 branch May 25, 2026 13:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant