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
Dynamic-parent (mixin) class does not inherit the base's instance field initializers #6356
A class whose parent is wired at runtime (RegisterClassParentDynamic — i.e. any
mixin/factory class that does NOT take the const M = Mixin(Base) fast path)
inherits the base's prototype methods but not its instance field
initializers. The field reads undefined.
classBase{value="base value";hello(){return"base hello";}}functionViaConst(B: any){constK=classextendsB{greet(){return"const";}};returnK;// returns a LocalGet, not a bare class expr}constC=ViaConst(Base);console.log(newC().hello());// node: "base hello" perry: "base hello" ✅ methods workconsole.log((newC()asany).value);// node: "base value" perry: undefined ❌console.log(newC()instanceofBase);// true on both ✅
Same for the other shapes that decline the fast path:
functionGreetable(B: any){returnclassextendsB{greet(){return"hi";}};}functionSerializable(B: any){returnclassextendsB{ser(){return"ser";}};}// composed in one expression — the arg is a Call, not an IdentconstComp=Serializable(Greetable(Base)asany);(newComp()asany).value// node: "base value" perry: undefined ❌// mixin over a mixin resultconstMid=Greetable(Base);constTop=Serializable(Midasany);(newTop()asany).value// node: "base value" perry: undefined ❌(newTop()asany).greet()// node: "hi" perry: TypeError: greet is not a function ❌newTop()instanceofMid// node: true perry: false ❌
Why the fast-path shape is fine
const M = Mixin(Base) over the canonical function Mixin(B) { return class extends B {…} } is intercepted in HIR lowering
(crates/perry-hir/src/lower/stmt.rs), which synthesizes a real class with a staticextends Base. Static parents get the field-init inheritance
(apply_field_initializers_recursive), so that shape reports "base value"
correctly — see the matrix in #6355. It's specifically the dynamic parent edge
that doesn't replay the parent's field initializers.
Expected
Instance field initializers of the base should run for a class whose parent edge is
registered dynamically, and the grandparent's methods should resolve through a
two-level dynamic chain.
Notes
Found while fixing #5952 (mixin-factory binding). It is a separate, pre-existing
mechanism — present identically before and after #6355, which deliberately does not
touch it. The #5952 fixture
(test-files/test_gap_5952_mixin_factory_binding.ts) asserts only the cells that
match node today and leaves these out.
Verified on main @ be5ed2bfc and on fix/5952-mixin-factory-binding, byte for
byte identical.
Activity
fix(transform): hoist static parent through a Sequence parent_expr (P…
fix(transform): specialized mixin clones keep parent edge + a unique …