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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions