Epic: class expressions as first-class heap objects (per-evaluation class identity)
Problem
Perry models a class as a compile-time integer (class_id) plus process-global side tables; the class value is INT32(class_id). That's only valid when there's one class per class keyword — true for top-level class Foo {}, false for a class expression in a factory (make(ast) => class { static ast = ast }), where each evaluation must yield a distinct class. Today make(a) === make(b) and they share one (clobbered/undefined) static slot. Root blocker for effect's Schema (#1758) and thus Effect end-to-end (#321). Full root-cause + design: #1772.
Target architecture
A class expression evaluates to a real heap class object: a regular object stamped with the compile-time template's class_id (static methods / new / instanceof keep dispatching through the fast existing class_id machinery) carrying its per-evaluation static fields as own properties, with its superclass class object as prototype (static inheritance = one prototype-chain walk). Top-level class declarations keep INT32(class_id) unchanged. GC-native: a normal traced heap object → collectible, no leak; aligns with #1090 / #1096 / #1095.
Status
The entire class-object dispatch surface is shipped + merged (2026-05-25), all byte-identical to node with zero regressions: PR #1792 (foundation), #1793 (this-binding + extends field-inheritance), #1795 (new/instanceof/typeof + the OBJECT_TYPE_CLASS discriminator), #1797 (inherited static methods), #1800 (rest-param inherited static methods). The effect Schema DoD was then reached via a follow-up chain (#1804/#1809/#1810/#1811/#1812/#1815): the effect barrel runs Effect.runSync(Effect.succeed(42)) → 42 and effect/Schema Schema.Number decode works. Remaining: #1790 (GC layout descriptor) is the one open technical piece; richer effect features (#321) continue beyond this epic via #1816 (cross-package arguments) and the #1982–#2032 series.
Subtickets (dependency-ordered)
Dependency DAG:
#1786 (foundation)
/ | | \
#1787 #1788 #1789 #1790
\ | | /
#1791 (integration → closes #1772)
DoD
import { Effect } from "effect" and import * as S from "effect/Schema" initialize and run; effect/Effect deep import stays green; full class suite + GC-stress green; no leak. Closes #1772; unblocks #1758 / #321.
Refs #1772, #1758, #321, #1090, #1095, #1096.
Epic: class expressions as first-class heap objects (per-evaluation class identity)
Problem
Perry models a class as a compile-time integer (
class_id) plus process-global side tables; the class value isINT32(class_id). That's only valid when there's one class perclasskeyword — true for top-levelclass Foo {}, false for a class expression in a factory (make(ast) => class { static ast = ast }), where each evaluation must yield a distinct class. Todaymake(a) === make(b)and they share one (clobbered/undefined) static slot. Root blocker for effect's Schema (#1758) and thus Effect end-to-end (#321). Full root-cause + design: #1772.Target architecture
A class expression evaluates to a real heap class object: a regular object stamped with the compile-time template's
class_id(static methods /new/ instanceof keep dispatching through the fast existing class_id machinery) carrying its per-evaluation static fields as own properties, with its superclass class object as prototype (static inheritance = one prototype-chain walk). Top-level class declarations keepINT32(class_id)unchanged. GC-native: a normal traced heap object → collectible, no leak; aligns with #1090 / #1096 / #1095.Status
The entire class-object dispatch surface is shipped + merged (2026-05-25), all byte-identical to node with zero regressions: PR #1792 (foundation), #1793 (this-binding + extends field-inheritance), #1795 (new/instanceof/typeof + the
OBJECT_TYPE_CLASSdiscriminator), #1797 (inherited static methods), #1800 (rest-param inherited static methods). The effect Schema DoD was then reached via a follow-up chain (#1804/#1809/#1810/#1811/#1812/#1815): theeffectbarrel runsEffect.runSync(Effect.succeed(42)) → 42andeffect/SchemaSchema.Numberdecode works. Remaining: #1790 (GC layout descriptor) is the one open technical piece; richer effect features (#321) continue beyond this epic via #1816 (cross-packagearguments) and the #1982–#2032 series.Subtickets (dependency-ordered)
thisto the class-object value on static-method calls #1787 — (2) Static-methodthis-binding on class-object values (PR fix: class-object value dispatch — static-method this-binding (#1787) + extends static-field inheritance (#1788) #1793, merged)extends#1788 — (3)extends-inheritance of per-evaluation own static fields (PR fix: class-object value dispatch — static-method this-binding (#1787) + extends static-field inheritance (#1788) #1793 fields + fix(runtime,codegen): inherit static methods through extends of a class-object value (#1788 follow-up) #1797 methods + fix(runtime,codegen): bundle rest args for inherited rest-param static methods (#1788) #1800 rest-param, merged)new/instanceof/typeofon a class-object value #1789 — (4)new/instanceof/typeofon a class-object value (PR fix(runtime,codegen): new / instanceof / typeof on a class-object value (#1789) #1795, merged)Dependency DAG:
DoD
import { Effect } from "effect"andimport * as S from "effect/Schema"initialize and run;effect/Effectdeep import stays green; full class suite + GC-stress green; no leak. Closes #1772; unblocks #1758 / #321.Refs #1772, #1758, #321, #1090, #1095, #1096.