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
Static getter on a class extending a call expression: this.of(x) throws "of is not a function" (this = holder, call path differs from read path) — OpenCode v1.18.30 bootstrap wall #10210
Inside a static getter of a class whose extends clause is a call expression (a plain function whose [[Prototype]] was swapped with Object.setPrototypeOf), a method call on this (this.of(x)) throws TypeError: of is not a function under perry, although the plain property read this.of on the same receiver yields the function. It happens as soon as the class is either declared inside a function or subclassed. Static methods with the identical body work.
This is the current bootstrap wall for OpenCode v1.18.30 (opencode models, opencode run, opencode serve): effect v4's Context.Service<...>()(id) returns function KeyClass(){} with Object.setPrototypeOf(KeyClass, ServiceProto) (ServiceProto.of lives on the swapped prototype), and OpenCode's src/effect/config-service.ts wraps it as
exportconstService=<Self>()=><constIdextendsstring,constFieldsextendsConfigMap>(id: Id,fields: Fields)=>{classConfigTagextendsContext.Service<Self,Shape<Fields>>()(id){staticgetlayer(){consttag=thisreturnLayer.effect(tag,Effect.gen(function*(){constconfig=yield*Config.all(fields)returntag.of(configasShape<Fields>)// <-- "of is not a function"}))}}returnConfigTag}// src/effect/runtime-flags.tsexportclassServiceextendsConfigService.Service<Service>()("@opencode/RuntimeFlags",{ ... }){}exportconstnode=LayerNode.make({service: Service,layer: Service.layer.pipe(Layer.orDie),deps: []})
Service.layer is built while AppRuntime constructs its layers, i.e. before the first log line, so every command that needs the runtime dies with Error: Unexpected error / of is not a function (oracle: bun runs normally). Tracker: #10107.
Repro (no dependencies)
// static-getter-this.tsconstProto: any={of(self: any){returnself}}constKey=function(){functionK(){}Object.setPrototypeOf(K,Proto)returnfunction(key: string){(Kasany).key=key;returnK}}constt=(name: string,f: ()=>any)=>{try{console.log(name,JSON.stringify(f()))}catch(e: any){console.log(name,"THROW",e.message)}}// N1 nested class + subclass, static getter calls this.of(x)constmk1=(id: string)=>{classTextends(Keyasany)()(id){staticgetg(){return(thisasany).of({n: 1})}};returnTasany}classS1extendsmk1("S1"){}t("N1 nested+sub getter this.of(x)",()=>S1.g)// N2 same receiver, read first then callconstmk2=(id: string)=>{classTextends(Keyasany)()(id){staticgetg(){constf=(thisasany).of;return[typeoff,f.call(this,{n: 2})]}};returnTasany}classS2extendsmk2("S2"){}t("N2 nested+sub getter f=this.of; f.call",()=>S2.g)// N5 nested, no subclasst("N5 nested no-sub getter this.of(x)",()=>mk1("T5").g)// N6 top-level + subclassclassT6extends(Keyasany)()("T6"){staticgetg(){return(thisasany).of({n: 6})}}classS6extendsT6{}t("N6 top-level+sub getter this.of(x)",()=>S6.g)// N7 top-level, no subclass (works)t("N7 top-level no-sub getter this.of(x)",()=>T6.g)// N8 who is `this` inside the getter?constmk8=(id: string)=>{classTextends(Keyasany)()(id){staticgetg(){return{th: thisasany, T }}};returnTasany}classS8extendsmk8("S8"){}t("N8 identity",()=>{const{ th, T }=S8.g;return{thisIsSub: th===S8,thisIsT: th===T,thisIsSubProto: th===Object.getPrototypeOf(S8),subProtoIsT: Object.getPrototypeOf(S8)===T,typeofThisOf: typeofth.of}})// N10 static METHOD with the same body (works)constmk10=(id: string)=>{classTextends(Keyasany)()(id){staticm(){return(thisasany).of({n: 10})}};returnTasany}classS10extendsmk10("S10"){}t("N10 nested+sub method this.of(x)",()=>S10.m())// M1 calling the function a static getter returns, on a class that extends a call expressionconstmk11=(id: string)=>{classTextends(Keyasany)()(id){staticgetg(){return()=>11}};returnTasany}t("M1 nestedClass.getter()",()=>mk11("T11").g())
perry 0.5.1544 (perry compile static-getter-this.ts), Linux x86_64:
N1 nested+sub getter this.of(x) THROW of is not a function
N2 nested+sub getter f=this.of; f.call ["function",{"n":2}]
N5 nested no-sub getter this.of(x) THROW of is not a function
N6 top-level+sub getter this.of(x) THROW of is not a function
N7 top-level no-sub getter this.of(x) {"n":6}
N8 identity {"thisIsSub":false,"thisIsT":false,"thisIsSubProto":true,"subProtoIsT":false,"typeofThisOf":"function"}
N10 nested+sub method this.of(x) {"n":10}
M1 nestedClass.getter() THROW g is not a function
Observations
Call path vs read path differ on the same receiver. N2 reads this.of (a function) and .calls it fine; N1 lowers this.of(x) as a method call and does not find of. Static methods (N10) lower the identical call correctly, so the divergence is specific to static accessors.
Calling the value returned by a static getter (mk11("T11").g(), M1) throws g is not a function for classes extending a call expression, while the same shape on a class without such a heritage works (class Q { static get g() { return () => 9 } }; Q.g() is fine).
Top-level, non-subclassed classes (N7) work, so the compile-time static resolution for known classes is fine; the failure needs an unknown superclass (call expression) plus either nesting in a function or a subclass.
Fix: PR #10213 (runtime only, miss path only). Verified on perrymaster: the N1–N7/N10 matrix above prints the node output, the ConfigService.Service mirror passes on all lines, new test crates/perry/tests/issue_10210_static_getter_call.rs passes, and the sibling class/static tests (issue_4908_subclass_native_member_base, capture_rereg_renamed_class, instanceof_classexpr_rhs_captured, aliased_native_class_import) still pass.
Left open by that PR, tracked here:
Identity (observation 2): this inside the static getter is still the holder object, and a nested capturing class is a ClassExprFresh pointer while the name inside its own body lowers to an INT32 ClassRef (perry-hir/src/lower_decl/body_stmt.rs:281 lowers the body before :362 defines the local), so this === Sub / Object.getPrototypeOf(Sub) === T stay false. Dispatch is correct; only identity diverges.
KnownTopLevelClass.staticGetter() (observation 3, top-level receiver variant): the compile-time static tower (lower_call/property_get/static_dispatch.rs) routes an unknown static name on a known class ref to js_class_static_method_call (class_registry/parent_static.rs), whose miss path walks CLASS_DYNAMIC_PROPS and the parent chain but not CLASS_STATIC_ACCESSORS — same shape as the fixed class-object arm, one helper over. The dynamic-receiver variant (mk().getter()) is fixed.
Summary
Inside a static getter of a class whose
extendsclause is a call expression (a plain function whose[[Prototype]]was swapped withObject.setPrototypeOf), a method call onthis(this.of(x)) throwsTypeError: of is not a functionunder perry, although the plain property readthis.ofon the same receiver yields the function. It happens as soon as the class is either declared inside a function or subclassed. Static methods with the identical body work.This is the current bootstrap wall for OpenCode v1.18.30 (
opencode models,opencode run,opencode serve): effect v4'sContext.Service<...>()(id)returnsfunction KeyClass(){}withObject.setPrototypeOf(KeyClass, ServiceProto)(ServiceProto.oflives on the swapped prototype), and OpenCode'ssrc/effect/config-service.tswraps it asService.layeris built whileAppRuntimeconstructs its layers, i.e. before the first log line, so every command that needs the runtime dies withError: Unexpected error / of is not a function(oracle:bunruns normally). Tracker: #10107.Repro (no dependencies)
node (
node --experimental-strip-types) / bun:perry 0.5.1544 (
perry compile static-getter-this.ts), Linux x86_64:Observations
this.of(a function) and.calls it fine; N1 lowersthis.of(x)as a method call and does not findof. Static methods (N10) lower the identical call correctly, so the divergence is specific to static accessors.thisinside the static getter is the holder, not the receiver. N8: under perrythis === Object.getPrototypeOf(S8)and is neither the subclassS8nor the class bindingT; in nodethis === S8.Object.getPrototypeOf(S8) === Tis also false under perry, so the subclass's static side points at an internal class object distinct from theTbinding (this is likely the same identity gap noted on modules:export * as X from "./self"is undefined on a dynamic import() namespace (key present, value undefined) — blocks every OpenCode command at the effect-cmd bootstrap #10160).mk11("T11").g(), M1) throwsg is not a functionfor classes extending a call expression, while the same shape on a class without such a heritage works (class Q { static get g() { return () => 9 } }; Q.g()is fine).Expected: node/bun output above.