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
perry-runtime: Effect framework throws TypeError: (number).slice is not a function during Schema.ts module init (Effect end-to-end blocker, post-#671) #685
After #671 closed the v0.5.795 TypeError: value is not a function during HashMap.ts__init, the Effect end-to-end DoD repro from #321 advances much further at perry 0.5.809 — at least 308 module inits run cleanly before the runtime trips on a new uncaught throw during effect/src/Schema.ts__init:
TypeError: (number).slice is not a function
at <anonymous>
Strict improvement over #671 (HashMap.ts was the 26th init; Schema.ts is around the 309th of 362), but still blocks the DoD repro.
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: (number).slice is not a function# at <anonymous># exit: 1
Crashes before [1] before prints — still pre-main module-init territory.
So the crash is during effect/src/Schema.ts__init. Note the very deep +12012 offset on frame #6 — Schema.ts is huge (11,443 lines), its init function is enormous, and the throw fires deep inside it.
Throw-site analysis
(number).<method> is not a function traces to crates/perry-runtime/src/object.rs:6488-6506 — js_native_call_method's primitive-receiver catch-all:
let primitive_kind:Option<&'staticstr> = if jsval.is_any_string(){Some("string")}elseif jsval.is_int32() || jsval.is_number(){Some("number")}elseif jsval.is_bool(){Some("boolean")}elseif jsval.is_bigint(){Some("bigint")}else{None};ifletSome(kind) = primitive_kind {crate::error::js_throw_type_error_not_a_function(
kind.as_ptr(), kind.len(),
method_name.as_ptr(), method_name.len(),);}
So during Schema.ts__init, codegen emitted a dynamic dispatch js_native_call_method(value, "slice", args) where value carried a NaN-box int32/double tag rather than a string or array pointer. Two plausible upstream causes:
Wrong value flowing into a .slice() call — somewhere a binding that should hold a string/array was bound to a number (e.g. a Symbol.for(...) mis-resolved, or a cross-module property read returned a placeholder int).
Codegen mis-typed the receiver — a top-level const X = someFactory(...); X.slice(...) where X is typed as any because the factory's return type couldn't be inferred, so codegen falls through to runtime tag dispatch; if the factory ran in a way that produced a numeric NaN-box (e.g. the codegen-side scalar-replacement perry/ui: state.text() and ForEach(stateCount,...) don't propagate on macOS after .set() #599 / sentinel-int patterns), the dispatch hits the number arm.
Schema.ts:945-960 has the only top-level .slice(...) calls I can find:
returnclassTemplateLiteralParserClassextendstransformOrFail(from,to,{strict: false,decode: (i,_,ast)=>{constmatch=re.exec(i)returnmatch
? ParseResult.succeed(match.slice(1,params.length+1))// line 955
: ParseResult.fail(...)},encode: (tuple)=>ParseResult.succeed(tuple.join(""))}){staticparams=params.slice()// line 960}
The match.slice(1, params.length + 1) (line 955) is inside a closure body, but static params = params.slice() (line 960) is a static class field initializer, evaluated when the class is declared, which itself is inside the TemplateLiteralParser factory body — also not directly at module-init time.
A grep for top-level .slice( in Schema.ts only finds those three sites. The init-time .slice() may be coming from a different shape that codegen lowers to slice — e.g. spread-rest into an array allocator, or destructuring into rest patterns.
Each landed fix has unblocked more of the init chain. The remaining surface is now ~54 modules of Effect (Schema.ts + everything topologically downstream of it).
Schema.ts is 11,443 lines and probably the highest-yield place in the whole Effect tree to find more issues — if this throw covers a generic "dynamic .slice() against a numeric receiver" codegen bug, the same root cause likely affects other heavy-generics packages.
Easiest next step: instrument the throw helper in error.rs:293 to also dump the calling code's address (std::backtrace::Backtrace::capture()) before throwing — would identify the exact lowering shape that emitted js_native_call_method(<numeric>, "slice", ...).
Schema.ts has a single top-level .slice() that isn't inside a closure: static params = params.slice() at line 960, inside the TemplateLiteralParser factory's returned class. If codegen's static-class-field init for declared-inside-factory classes evaluates eagerly at module init when it shouldn't, that's the lead.
Summary
After #671 closed the v0.5.795
TypeError: value is not a functionduringHashMap.ts__init, the Effect end-to-end DoD repro from #321 advances much further at perry 0.5.809 — at least 308 module inits run cleanly before the runtime trips on a new uncaught throw duringeffect/src/Schema.ts__init:Strict improvement over #671 (HashMap.ts was the 26th init; Schema.ts is around the 309th of 362), but still blocks the DoD repro.
Repro
Crashes before
[1] beforeprints — still pre-mainmodule-init territory.Bisection
lldb -o "b _exit" -o "run" -o "bt" -o "quit" --batchbacktrace:Mapping
_main + 1252(return addr) →bl @ _main + 1248→ unlinked offset0x290 + 0x4E0 = 0x770in the entry.o. Looking up the relocation:So the crash is during
effect/src/Schema.ts__init. Note the very deep+12012offset on frame #6 — Schema.ts is huge (11,443 lines), its init function is enormous, and the throw fires deep inside it.Throw-site analysis
(number).<method> is not a functiontraces tocrates/perry-runtime/src/object.rs:6488-6506—js_native_call_method's primitive-receiver catch-all:So during Schema.ts__init, codegen emitted a dynamic dispatch
js_native_call_method(value, "slice", args)wherevaluecarried a NaN-box int32/double tag rather than a string or array pointer. Two plausible upstream causes:.slice()call — somewhere a binding that should hold a string/array was bound to a number (e.g. aSymbol.for(...)mis-resolved, or a cross-module property read returned a placeholder int).const X = someFactory(...); X.slice(...)whereXis typed asanybecause the factory's return type couldn't be inferred, so codegen falls through to runtime tag dispatch; if the factory ran in a way that produced a numeric NaN-box (e.g. the codegen-side scalar-replacement perry/ui: state.text() and ForEach(stateCount,...) don't propagate on macOS after .set() #599 / sentinel-int patterns), the dispatch hits the number arm.Schema.ts:945-960 has the only top-level
.slice(...)calls I can find:The
match.slice(1, params.length + 1)(line 955) is inside a closure body, butstatic params = params.slice()(line 960) is a static class field initializer, evaluated when the class is declared, which itself is inside theTemplateLiteralParserfactory body — also not directly at module-init time.A grep for top-level
.slice(in Schema.ts only finds those three sites. The init-time.slice()may be coming from a different shape that codegen lowers toslice— e.g. spread-rest into an array allocator, or destructuring into rest patterns.Status snapshot for #321
result: 42Each landed fix has unblocked more of the init chain. The remaining surface is now ~54 modules of Effect (Schema.ts + everything topologically downstream of it).
Notes for whoever picks this up
--keep-intermediateskeepstest_bare_ts.o;objdump -r test_bare_ts.o | grep "_node_modules_effect_src.*__init$" | sortgives init order,lldb -o "b _exit" -o "run" -o "bt" -o "quit" --batch <bin>captures the backtrace..slice()against a numeric receiver" codegen bug, the same root cause likely affects other heavy-generics packages.error.rs:293to also dump the calling code's address (std::backtrace::Backtrace::capture()) before throwing — would identify the exact lowering shape that emittedjs_native_call_method(<numeric>, "slice", ...)..slice()that isn't inside a closure:static params = params.slice()at line 960, inside theTemplateLiteralParserfactory's returned class. If codegen's static-class-field init for declared-inside-factory classes evaluates eagerly at module init when it shouldn't, that's the lead.Refs #321, #671.