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
After #685 closed the static params = params.slice() lowering bug in Schema.ts__init, the Effect end-to-end DoD repro from #321 makes it further into Schema.ts on perry 0.5.862 — but trips on a new uncaught throw:
TypeError: pipe is not a function
at <anonymous>
Strict improvement (got ~132 more bytes into Schema.ts's init body) but still blocks the DoD.
Repro
mkdir effect-321 &&cd effect-321
cat > package.json <<'JSON'{ "name": "effect-321", "version": "0.0.0", "type": "module", "perry": { "compilePackages": ["effect"] } }JSON
bun add effect@3.21.2
cat > test_bare.ts <<'TS'console.log("[1] before");import {} from "effect";console.log("[2] after");TS
perry compile test_bare.ts -o /tmp/out
/tmp/out
# stdout: (nothing)# stderr: TypeError: pipe is not a function# at <anonymous># exit: 1
Same module as #685, slightly further in. Frame #6 is +12144 (vs +12012 pre-#685, +132 bytes / 33 instructions of additional init body covered).
Throw-site analysis
Message format <prop> is not a function (no (kind). prefix) traces uniquely to crates/perry-runtime/src/object.rs:6521-6531 — the "real-object receiver, method-not-found anywhere in dispatch chain" catch-all (added by #648):
So during Schema.ts__init, codegen emitted js_native_call_method(<real-object>, "pipe", args) but the dispatch chain didn't find pipe on the receiver's class vtable, prototype, or field-stored closures.
Almost certainly: top-level class X extends Y.pipe(...)
Schema.ts is full of top-level class declarations that eagerly evaluate String$.pipe(...) (or Lowercased.pipe(...), etc.) as the base class. The first one is at line 4970:
exportclassLowercasedextendsString$.pipe(lowercased({identifier: "Lowercased"})){}exportclassUppercasedextendsString$.pipe(...){}// line 5003exportclassCapitalizedextendsString$.pipe(...){}// line 5036exportclassUncapitalizedextendsString$.pipe(...){}// line 5069exportclassCharextendsString$.pipe(length(1, ...)){}// line 5079exportclassTrimmedextendsString$.pipe(...){}// line 5162exportclassNonEmptyTrimmedStringextendsTrimmed.pipe(...){}// line 5182exportclassNonEmptyStringextendsString$.pipe(...){}// line 5324// ... ~30 more `class X extends Y.pipe(...)` patterns
String$ is itself defined as class String$ extends make<string>(AST.stringKeyword) {} (line 1230) — a class extending the result of a factory function call. In the Effect runtime, make<...>(...) returns a class whose prototype chain reaches Pipeable, so String$.pipe(...) resolves to the universal pipeArguments-based implementation.
In Perry, that lookup fails. Likely culprits:
Class-method dispatch doesn't walk the extends factory() prototype chain. When the base is a function call rather than a named class, perry's class-id propagation may not register the inheritance link, so String$'s vtable doesn't include pipe from the factory-produced base.
Cross-module pipeArguments doesn't get registered as a method on the result of make().pipeArguments is imported from ./Pipeable.js and assigned into the prototype of base schemas, but if the assignment path goes through a shape Perry can't track, the method-table entry is missing.
Class-id collision — same name (String, possibly aliased to String$ only in Schema.ts) resolves to a different class registration that doesn't have pipe.
What I tried for a standalone repro
A minimal mimic — class Base { pipe(...) {} }; class Derived extends make() {}; const X = Derived.pipe(fn); class Final extends X {} — runs clean. So the trigger needs more state than the surface pattern; consistent with #611 / #671 / #685 (each required the full Effect init prefix to repro).
* Same module as v0.5.809 but reaching deeper into the init body — Schema.ts itself is large enough that there's room for several sequential throw sites.
The class X extends Y.pipe(...) shape is the strongest hypothesis. Counter-test: comment out everything after Schema.ts line 4970 (or replace the String$.pipe(...) bases with Object or a no-pipe class) and see if the throw moves or goes away.
Effect's Pipeable protocol is implemented in effect/src/Pipeable.ts — pipeArguments(self, args) walks varargs as a fold. Any class produced by make() (Schema.ts:1230's class String$ extends make<string>(AST.stringKeyword) {}) should have pipe installed via the standard Pipeable inheritance chain.
Summary
After #685 closed the
static params = params.slice()lowering bug inSchema.ts__init, the Effect end-to-end DoD repro from #321 makes it further into Schema.ts on perry 0.5.862 — but trips on a new uncaught throw:Strict improvement (got ~132 more bytes into Schema.ts's init body) but still blocks the DoD.
Repro
Bisection
lldbbacktrace viab _exit / run / bt:_main + 1248→ unlinked offset0x290 + 0x4E0 = 0x770→ relocation:Same module as #685, slightly further in. Frame #6 is
+12144(vs+12012pre-#685, +132 bytes / 33 instructions of additional init body covered).Throw-site analysis
Message format
<prop> is not a function(no(kind).prefix) traces uniquely tocrates/perry-runtime/src/object.rs:6521-6531— the "real-object receiver, method-not-found anywhere in dispatch chain" catch-all (added by #648):So during Schema.ts__init, codegen emitted
js_native_call_method(<real-object>, "pipe", args)but the dispatch chain didn't findpipeon the receiver's class vtable, prototype, or field-stored closures.Almost certainly: top-level
class X extends Y.pipe(...)Schema.ts is full of top-level class declarations that eagerly evaluate
String$.pipe(...)(orLowercased.pipe(...), etc.) as the base class. The first one is at line 4970:String$is itself defined asclass String$ extends make<string>(AST.stringKeyword) {}(line 1230) — a class extending the result of a factory function call. In the Effect runtime,make<...>(...)returns a class whose prototype chain reachesPipeable, soString$.pipe(...)resolves to the universalpipeArguments-based implementation.In Perry, that lookup fails. Likely culprits:
extends factory()prototype chain. When the base is a function call rather than a named class, perry's class-id propagation may not register the inheritance link, soString$'s vtable doesn't includepipefrom the factory-produced base.pipeArgumentsdoesn't get registered as a method on the result ofmake().pipeArgumentsis imported from./Pipeable.jsand assigned into the prototype of base schemas, but if the assignment path goes through a shape Perry can't track, the method-table entry is missing.String, possibly aliased toString$only in Schema.ts) resolves to a different class registration that doesn't havepipe.What I tried for a standalone repro
A minimal mimic —
class Base { pipe(...) {} }; class Derived extends make() {}; const X = Derived.pipe(fn); class Final extends X {}— runs clean. So the trigger needs more state than the surface pattern; consistent with #611 / #671 / #685 (each required the full Effect init prefix to repro).Status snapshot for #321
result: 42* Same module as v0.5.809 but reaching deeper into the init body — Schema.ts itself is large enough that there's room for several sequential throw sites.
Notes for whoever picks this up
object.rs:6521(real-object dispatch catch-all from Callingundefinedas a function silently no-ops instead of throwing TypeError #648). Addingeprintln!of the receiver's class_id + the dispatch path tried before the throw would identify exactly where the inheritance lookup falls off.class X extends Y.pipe(...)shape is the strongest hypothesis. Counter-test: comment out everything after Schema.ts line 4970 (or replace theString$.pipe(...)bases withObjector a no-pipe class) and see if the throw moves or goes away.Pipeableprotocol is implemented ineffect/src/Pipeable.ts—pipeArguments(self, args)walks varargs as a fold. Any class produced bymake()(Schema.ts:1230'sclass String$ extends make<string>(AST.stringKeyword) {}) should havepipeinstalled via the standard Pipeable inheritance chain.Refs #321, #685.