Summary
test_issue_462_nullish_property_access.ts and test_issue_510_primitive_method_typeerror.ts both diverge from Node's parity output not because Perry's behavior is wrong, but because Perry's uncaught-exception format doesn't match Node's. Both tests trigger a real TypeError and Perry correctly throws it; the test's UNREACHABLE line correctly never prints. The remaining failure is purely the framing.
This regressed in v0.5.702 (#596 — TypeError throw helpers route through js_throw). The print_uncaught function in crates/perry-runtime/src/exception.rs:159+ was modified at that point to emit a single Uncaught exception: <Name>: <message> line plus the stack string.
Diff (test_issue_462)
Node:
about to throw — last line of stdout
file:///Users/.../test_issue_462_nullish_property_access.ts:33
console.log(undef.foo);
^
TypeError: Cannot read properties of undefined (reading 'foo')
at file:///.../test_issue_462_nullish_property_access.ts:33:19
at ModuleJob.run (node:internal/modules/esm/module_job:430:25)
...
Perry:
about to throw — last line of stdout
Uncaught exception: TypeError: Cannot read properties of undefined (reading 'foo')
TypeError: Cannot read properties of undefined (reading 'foo')
at <anonymous>
Three concrete differences:
- Perry has the
Uncaught exception: prefix; Node does not.
- Perry omits the file:line "header" block (file URL, source-line, caret, blank).
- Stack frames differ — Perry's
at <anonymous> vs Node's at <file>:<line>:<col> + module-job frames.
Symptom shared by
test_issue_462_nullish_property_access
test_issue_510_primitive_method_typeerror
(any future test that intentionally throws + lets it escape will hit the same format divergence)
Reproducer
const x: any = undefined;
console.log(x.foo); // throws TypeError
node --experimental-strip-types repro.ts vs perry repro.ts && ./out produces the diff above.
Suggested approach
If matching Node byte-for-byte is the goal: extend print_uncaught in exception.rs to emit a Node-shaped header. Source-line + caret requires reading the original source text + columns, which the runtime doesn't currently retain at throw time — would need an HIR-side annotation that flows the source-text snippet for the throwing site to the runtime.
If matching Node exactly is unrealistic (Perry's stack frames have a different shape — no module-job, no async-job machinery), an alternative: keep the current Perry format but add it to the parity harness's known-divergence allowlist, so these tests don't show up as failures every sweep.
Environment
Summary
test_issue_462_nullish_property_access.tsandtest_issue_510_primitive_method_typeerror.tsboth diverge from Node's parity output not because Perry's behavior is wrong, but because Perry's uncaught-exception format doesn't match Node's. Both tests trigger a realTypeErrorand Perry correctly throws it; the test'sUNREACHABLEline correctly never prints. The remaining failure is purely the framing.This regressed in v0.5.702 (#596 — TypeError throw helpers route through
js_throw). Theprint_uncaughtfunction incrates/perry-runtime/src/exception.rs:159+was modified at that point to emit a singleUncaught exception: <Name>: <message>line plus the stack string.Diff (test_issue_462)
Three concrete differences:
Uncaught exception:prefix; Node does not.at <anonymous>vs Node'sat <file>:<line>:<col>+ module-job frames.Symptom shared by
test_issue_462_nullish_property_accesstest_issue_510_primitive_method_typeerror(any future test that intentionally throws + lets it escape will hit the same format divergence)
Reproducer
node --experimental-strip-types repro.tsvsperry repro.ts && ./outproduces the diff above.Suggested approach
If matching Node byte-for-byte is the goal: extend
print_uncaughtinexception.rsto emit a Node-shaped header. Source-line + caret requires reading the original source text + columns, which the runtime doesn't currently retain at throw time — would need an HIR-side annotation that flows the source-text snippet for the throwing site to the runtime.If matching Node exactly is unrealistic (Perry's stack frames have a different shape — no module-job, no async-job machinery), an alternative: keep the current Perry format but add it to the parity harness's known-divergence allowlist, so these tests don't show up as failures every sweep.
Environment
0.5.712scripts/release_sweep.shtier 2; regression point: v0.5.702 (Sync throw inside async fn body escapes outer try/catch around await #596)