Repository navigation
Diagnostics: runtime errors have no source location (at <anonymous>) — add file:line to throws #5247
Description
Context: debugging native npm-dep compilation. Several real packages fail at runtime with errors of the form "X is not a function" on perry's numeric 0-sentinel — ajv ((number).test), yargs (Bind must be called on a function), nanoid/yup (value is not a function). They're impossible to localize because perry's runtime errors carry no source location: crates/perry-runtime/src/error.rs::make_stack hardcodes "<name>: <message>\n at <anonymous>" for every error, and there's no call-stack / source-position tracking.
Goal
Give runtime errors a real source location — replace at <anonymous> with at <file>:<line>, at minimum for the call-dispatch throws (X is not a function, X is not a constructor, property-on-non-object), which is the class blocking native-compile debugging.
Suggested approach (bounded first increment)
- Carry a source position on the relevant throwing HIR nodes (call / method-call / member-access / new). The SWC AST has spans; AST→HIR lowering currently drops them (AOT: defer non-resolvable dynamic import() to a runtime error (shared notice + strict), like #5206 #5230 already added a
byte_offsetfield toExpr::DynamicImportfor this exact reason — follow that precedent). Setbyte_offsetfromspan.loin lowering. - Codegen passes that
byte_offset(and the module id) into the dispatch/throw path so the "not a function/constructor" throw helpers (throw_type_error,js_throw_type_error_not_a_function, etc. in perry-runtime) receive it. - Runtime resolves
byte_offset → file:linevia a per-module byte→line table built at compile time (embed a compact line-start index per compiled module), andmake_stackemitsat <file>:<line>when a location is available (fall back to<anonymous>). - Gate the codegen location-emission behind a flag (
--trace-locations, or reuse--debug-symbols) so release-build perf is unaffected; default off keeps current behavior.
Verify
const f: any = 5; f.test();compiled with the flag → the thrownTypeError(and.stack) reportsat <file>:<line>of thef.test()call, not<anonymous>.- Payoff: compile a tiny
ajvconsumer (compilePackages: ["*"]) with the flag and report thefile:linethe(number).testthrow now points at — that localizes the sentinel bug for a follow-up fix.
Scope
This issue is the throw-site source location. Full V8-style multi-frame stack traces (a live call-stack with function names per frame) are a larger follow-up. Deliver the bounded increment; if even that splits, ship the runtime+error.rs location-rendering + one throw path and report what's deferred.
Impact
Unblocks debugging the entire native-compile "sentinel-0" class (ajv/yargs/nanoid/yup) and every future native-compile runtime error — turns guess-and-check into normal debugging.
Activity
feat(error): source location on dynamic call-dispatch TypeErrors (#5247…
feat(error): source location on construct + ReferenceError throws (#5253
feat(error): source location on construct + ReferenceError throws (#5253
fix(parser): #5498 — classify minified esbuild ESM bundles (top-level…
test(#5247): assert compiled fixture exits successfully in run_fixture
feat(error): #5247 — source location on bare value-call "value is not…
test(PerryTS#5247): cargo-test-visible unit test for property-read so…
feat(error): #5247 — source location on property-read-on-nullish Type…
proggeramlug commentedon Jul 30, 2026
Closing as completed by the merged increments in #5250, #5274, #5573, and #6597. The requested bounded
--debug-symbolspath now reportsfile:linefor dynamic method-call and bare value-call TypeErrors, not-a-constructor throws, unresolved-identifier ReferenceErrors, and nullish property reads. I re-ran the four focused integration suites on current main: 11/11 tests pass, including the default-build<anonymous>fallback checks. Full multi-frame stacks remain outside this issue’s stated scope; property writes/index reads can be tracked separately if still needed.