Repository navigation
Description
Summary
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: 1Crashes before [1] before prints — still pre-main module-init territory.
Bisection
lldb -o "b _exit" -o "run" -o "bt" -o "quit" --batch backtrace:
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint on __exit
* frame #0: libsystem_kernel.dylib`__exit
frame #1: libsystem_c.dylib`exit + 68
frame #2: out_b`<unnamed> + 12 ← _Exit panic-exit
frame #3: out_b`<unnamed> + 28 ← throw bubbles
frame #4: out_b`<unnamed> + 440 ← throw machinery
frame #5: out_b`<unnamed> + 504
frame #6: out_b`<unnamed> + 12012 ← inside Schema.ts__init body
frame #7: out_b`<unnamed> + 5024 ← deeper init helper
frame #8: out_b`_main + 1252 ← `bl @ _main + 1248`
frame #9: dyld`start
Mapping _main + 1252 (return addr) → bl @ _main + 1248 → unlinked offset 0x290 + 0x4E0 = 0x770 in the entry .o. Looking up the relocation:
0x770 ARM64_RELOC_BRANCH26 _node_modules_effect_src_Schema_ts__init
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<&'static str> = if jsval.is_any_string() {
Some("string")
} else if jsval.is_int32() || jsval.is_number() {
Some("number")
} else if jsval.is_bool() {
Some("boolean")
} else if jsval.is_bigint() {
Some("bigint")
} else {
None
};
if let Some(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. aSymbol.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(...)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:
return class TemplateLiteralParserClass extends transformOrFail(from, to, {
strict: false,
decode: (i, _, ast) => {
const match = re.exec(i)
return match
? ParseResult.succeed(match.slice(1, params.length + 1)) // line 955
: ParseResult.fail(...)
},
encode: (tuple) => ParseResult.succeed(tuple.join(""))
}) {
static params = 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.
Status snapshot for #321
| Phase | v0.5.706 | v0.5.795 | v0.5.809 |
|---|---|---|---|
| Modules collected | 362 ✅ | 362 ✅ | 362 ✅ |
| Codegen failures | 0 ✅ | 0 ✅ | 0 ✅ |
| Missing-symbol refs | 0 ✅ | 0 ✅ | 0 ✅ |
| Link | clean ✅ | clean ✅ | clean ✅ |
| Boots into main | ❌ SIGSEGV | ✅ | ✅ |
| Inits cleared before crash | — | 25/362 (HashMap) | 308/362 (Schema) |
result: 42 |
— | — | — |
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).
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.- 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: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", ...). - Schema.ts has a single top-level
.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.
Activity
fix(codegen): closes PerryTS#685 — nested-class static-field init no …
test(parity): catch up known_failures.json with 47 expected failures …
fix: #797 — restructure known_failures.json with audit metadata (#819)
fix(runtime,codegen): inherit static methods through `extends` of a c…
feat(runtime,codegen): Object.getOwnPropertyDescriptors (#1791/#1758) (…
fix(codegen): nested class static blocks must not re-run at module in…
fix(codegen): #6263 follow-up — find inline static-block invocations …
fix(codegen): class-expression statics — run static blocks per evalua…
fix(hir): extend PerryTS#6037/PerryTS#6052 class-capture refresh to c…
fix(hir): extend PerryTS#6037/PerryTS#6052 class-capture refresh to c…
fix(hir): extend PerryTS#6037/PerryTS#6052 class-capture refresh to c…
fix(hir): extend #6037/#6052 class-capture refresh to class expressio…
runtime: createRequire node:-prefix normalization + diagnostics_chann…
fix(codegen): globalize module-init lets nested in destructure Try sc…