Skip to content

methods inherited via Object.setPrototypeOf(obj, proto) run with this=undefined — effect Pipeable/Tag statics return the wrong pipe stage (blocks web.ts, 'Not a valid effect: undefined') #6475

Description

@proggeramlug

Summary

The last standing failure of effect-compiled-experiment web.ts (after #6449 / #6459 / #6460 / #6463+#6466 / #6465+#6470 / #6469+#6474): startup dies with

Error: Not a valid effect: undefined      (fiberRuntime.ts:1422)

Root of the poisoned value, isolated by three probes:

probe43 — the poisoned binding

Walking web.ts's layer graph binding-by-binding: everything matches node except

HealthLive    node: object (a Layer)   perry: function

HealthLive = HttpApiBuilder.group(Api, "health", ...) — and group returns Router.use((router) => ...) where Router is class Router extends HttpRouter.Tag("@effect/platform/HttpApiBuilder/Router")<Router>() {}.

probe44 — Tag statics misbehave

Recreating the Tag shape directly (class R2 extends HttpRouter.Tag("probe44/R2")<R2>() {}):

check node perry
pipe(1, f, g) / 4 fns / method-style .pipe ✓ (all fine)
R2.key
R2.Live Layer Layer ✓
R2.use(f) Layer function (isLayer false)
R2.unwrap(f) Layer object function

use is an own-prop ARROW on the factory's TagClass (TagClass_.use = (f) => TagClass_.pipe(Effect.flatMap(f), Layer.scopedDiscard, Layer.provide(TagClass_.Live))), inherited by R2 through the parent function object. The returned function is consistent with the pipe chain's LAST stage (Layer.provide(live), a data-last curried fn) never being applied — i.e. TagClass_.pipe(...) misfired on the receiver/arguments. TagClass's prototype is Object.getPrototypeOf(Context.GenericTag(id)) (a two-hop chain to Pipeable's pipe() { return pipeArguments(this, arguments) }).

probe45 — a minimal this-drop divergence in the same family

const Proto: any = { collect(this: any, ...args: any[]) { return [this?.tag, args.length, ...args] } }
function F() {}; Object.setPrototypeOf(F, Proto); (F as any).tag = "fn-recv"
const O: any = { tag: "obj-recv" }; Object.setPrototypeOf(O, Proto)

(F as any).collect(10, 20, 30)   // node ["fn-recv",3,10,20,30] | perry ["fn-recv",3,10,20,30] ✓
O.collect(10, 20, 30)            // node ["obj-recv",3,10,20,30] | perry ["null",3,10,20,30]  ✗
O.fixed(1, 2)                    // same: this dropped (null) for the OBJECT receiver

A method inherited through Object.setPrototypeOf(plainObject, proto) runs with this = undefined under perry (function-object receivers, own-prop closures, and arguments-based variadics all work). This is the same resolution family pipeArguments(this, arguments) sits in — with this undefined mid-chain, effect's Pipeable composition returns the wrong stage.

Impact

HttpApiBuilder.group(...) returns a function instead of a Layer, and the poison propagates lazily until the fiber run loop rejects an undefined — blocking the web.ts app (the 4th of the effect repro; the other three are byte-identical to node).

Repro files: probes 43/44/45 as above (no server needed for 44/45).

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