Skip to content

feat(error): source location on construct + ReferenceError throws (#5253)#5274

Open
proggeramlug wants to merge 1 commit into
mainfrom
feat/diag-construct-reference-locations2
Open

feat(error): source location on construct + ReferenceError throws (#5253)#5274
proggeramlug wants to merge 1 commit into
mainfrom
feat/diag-construct-reference-locations2

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #5247/#5250. Extends the --debug-symbols runtime source-location pipeline — which #5250 gave the dynamic call-dispatch "X is not a function" throw an at <file>:<line> frame — to two more throw classes. Default behavior is unchanged; only --debug-symbols builds gain the location.

Throw classes now covered

  1. new X() → "X is not a constructor" — localizes ajv's undefined is not a constructor.
  2. ReferenceError: X is not defined / module is not defined — localizes winston's module is not defined.

What changed

New/NewDynamic/NewDynamicSpread byte_offset

  • Added byte_offset: u32 to Expr::New, Expr::NewDynamic, Expr::NewDynamicSpread (crates/perry-hir/src/ir/expr.rs), set from new_expr.span.lo.0 in expr_new.rs::lower_new. Key nuance: const X: any = undefined; new X() lowers to Expr::NewDynamic (callee LocalGet), so the offset must live on that variant — which is exactly the ajv shape.
  • Excluded from the stable hash (stable_hash/expr.rs), mirroring Call.byte_offset (Diagnostics: runtime errors have no source location (at <anonymous>) — add file:line to throws #5247), so whitespace edits don't bust the object cache.
  • All other construction sites across hir/transform/codegen (synthesized news from transforms/intrinsics, CJS wrap, monomorph substitution preserves it) pass byte_offset: 0. Walkers/collectors/JS+WASM+ArkTS backends updated to ignore the new field.
  • Codegen (expr/new_dynamic.rs) calls emit_call_location_at right before each js_new_function_construct{,_apply} / js_throw_not_a_constructor dispatch (the New, NewDynamic primitive-literal + routed + fallback, NewDynamicSpread, and ternary-synthesized arms).

ReferenceError path

  • A bare unresolved identifier read already lowers to Call { callee: ExternFuncRef("js_global_get_or_throw_unresolved"), args: [name] }. That Call now carries ident.span.lo.0 as byte_offset (lower_expr.rs), and lower_call (lower_call/mod.rs) emits js_set_call_location from the pending offset before that specific extern call.

Runtime

  • No runtime change needed. Both throw paths already route through alloc_errormake_stack, which reads CURRENT_CALL_LOCATION. They light up once codegen sets the location.

Line-cap housekeeping

  • The field additions pushed expr_new.rs past the 2000-line cap; extracted the leaf helpers module_constructor_name / global_member_constructor_name into a new expr_new_builtins.rs (pure mechanical move). expr_new.rs is now 1968 lines.

Test evidence

New crates/perry/tests/issue_5253_construct_reference_source_location.rs (4 cases): not-a-constructor and ReferenceError, each WITH and WITHOUT --debug-symbols. All pass; #5247/#5250 tests stay green.

Manual .ts fixtures (WITH --debug-symbols):

new X():    TypeError: undefined is not a constructor   at nc.ts:3   (without: at <anonymous>)
ref read:   ReferenceError: notDefinedAnywhere is not defined   at re.ts:2   (without: at <anonymous>)

ajv / winston localization (payoff)

Compiled from secret-tests/corpus with compilePackages:["*"] + --debug-symbols:

  • winstonReferenceError: module is not defined at node_modules/@colors/colors/lib/custom/trap.js:1localized.
  • ajvTypeError: undefined is not a constructor at <anonymous>.

Deferred / known limitation

ajv's not-a-constructor throw is inside a CJS-wrapped module. The byte offset on the new node is in wrapped-source coordinates (verified via --print-hir: NewDynamic { byte_offset: 4849 }) while module_source (consulted by call_location_for) holds the original unwrapped file, so the offset is out of range → <anonymous>. This is a pre-existing limitation shared with #5250 — the call-dispatch path shows <anonymous> for .js/CJS modules too (confirmed). Mapping CJS-wrap coordinates back to original source is a separate follow-up; this PR's instrumentation is correct and complete and works for .ts/ESM modules (winston's module throw happens to be in an un-wrapped module, hence it localizes).

Version / CHANGELOG

Per the contributor guidance, this PR does not touch [workspace.package] version, the Current Version line, CHANGELOG.md, or Cargo.lock — maintainer folds those in at merge.

Summary by CodeRabbit

Release Notes

  • Bug Fixes
    • Improved error diagnostics for "not a constructor" TypeErrors and unresolved identifier ReferenceErrors by including precise source file and line information when --debug-symbols is enabled, eliminating anonymous stack frames for better debugging experience.

)

Follow-up to #5247/#5250. Extends the `--debug-symbols` runtime source-location
pipeline (which #5250 gave the dynamic call-dispatch "X is not a function"
throw) to two more throw classes:

  1. `new X()` "X is not a constructor" — adds `byte_offset` to `Expr::New`,
     `Expr::NewDynamic`, and `Expr::NewDynamicSpread`, set from the AST
     `new_expr.span.lo.0` at lowering. Codegen calls `emit_call_location_at`
     before the `js_new_function_construct{,_apply}` / `js_throw_not_a_constructor`
     dispatch. `const X: any = undefined; new X()` lowers to `NewDynamic` (callee
     `LocalGet`), so the offset lands on that variant — localizing ajv's
     `undefined is not a constructor`.

  2. `ReferenceError: X is not defined` — the bare unresolved-identifier read
     already lowers to a `Call` into `js_global_get_or_throw_unresolved`; that
     Call now carries `ident.span.lo.0` as its `byte_offset`, and `lower_call`
     emits `js_set_call_location` before the helper call. Localizes winston's
     `module is not defined`.

The runtime needs no change: both throw paths already route through
`alloc_error` → `make_stack`, which reads `CURRENT_CALL_LOCATION`. Default
behavior is unchanged (no debug-location ctx installed → no-op; offset 0 →
`<anonymous>`).

`byte_offset` is excluded from the stable hash (like `Call.byte_offset`) so
whitespace edits don't bust the object cache. All other New construction sites
(transforms, intrinsics, synthesized news) pass `byte_offset: 0`.

Extracted `module_constructor_name` / `global_member_constructor_name` from
`expr_new.rs` into a new `expr_new_builtins.rs` to keep the file under the
2000-line cap after the field additions.

Tests: new `issue_5253_construct_reference_source_location.rs` (4 cases:
not-a-constructor + ReferenceError, each WITH/WITHOUT `--debug-symbols`).
#5247/#5250 tests stay green.

No version/CHANGELOG/Cargo.lock edits (maintainer folds in at merge).
@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a byte_offset: u32 field to Expr::New, Expr::NewDynamic, and Expr::NewDynamicSpread HIR variants to carry AST source positions. The offset is threaded through all new-expression and unresolved-identifier lowering paths, then consumed in codegen to emit emit_call_location_at metadata before "not a constructor" TypeError and "is not defined" ReferenceError throw sites when --debug-symbols is active. All existing match arms across the codebase are updated to accept the new field via ...

Changes

Issue #5253 — Constructor and Reference Error Source Locations

Layer / File(s) Summary
HIR Expr::New* schema and stable-hash exclusion
crates/perry-hir/src/ir/expr.rs, crates/perry-hir/src/stable_hash/expr.rs
Expr::New, Expr::NewDynamic, and Expr::NewDynamicSpread each gain a byte_offset: u32 field. Stable-hash match arms updated to ignore byte_offset with .. and inline comments.
AST→HIR lowering: capture and propagate byte_offset
crates/perry-hir/src/lower/mod.rs, crates/perry-hir/src/lower/expr_new_builtins.rs, crates/perry-hir/src/lower/expr_new.rs, crates/perry-hir/src/lower/lower_expr.rs, crates/perry-hir/src/lower/expr_call/..., crates/perry-hir/src/lower/expr_object.rs, crates/perry-hir/src/lower_decl/block.rs
New expr_new_builtins submodule provides module_constructor_name and global_member_constructor_name helpers. lower_new captures new_byte_offset from the AST span and sets it on every produced Expr::New* node across all specialization branches. Unresolved-identifier lowering sets byte_offset to the identifier's span offset. Synthetic Expr::New nodes in other paths set byte_offset: 0.
Codegen: emit call-location metadata before throw sites
crates/perry-codegen/src/expr/new_dynamic.rs, crates/perry-codegen/src/lower_call/mod.rs
Reads byte_offset from Expr::NewDynamic and Expr::NewDynamicSpread match arms and calls emit_call_location_at before js_throw_not_a_constructor, js_new_function_construct, and runtime apply/construct helpers. lower_call similarly emits a location marker before calls to js_global_get_or_throw_unresolved.
Match arm updates across analysis, walkers, transforms, and codegen
crates/perry-hir/src/analysis.rs, crates/perry-hir/src/lower/closure_analysis.rs, crates/perry-hir/src/js_transform/..., crates/perry-hir/src/walker/..., crates/perry-hir/src/monomorph/..., crates/perry-codegen/src/collectors/..., crates/perry-codegen-js/src/emit/exprs.rs, crates/perry-codegen-wasm/src/emit/...
Every match arm for Expr::New, Expr::NewDynamic, and Expr::NewDynamicSpread updated to include .. to ignore byte_offset. substitute_expr.rs additionally propagates byte_offset when reconstructing Expr::New nodes during monomorphization.
Regression tests
crates/perry/tests/issue_5253_construct_reference_source_location.rs
New test file compiles TypeScript fixtures with and without --debug-symbols and asserts that "is not a constructor" and "is not defined" errors include at main.ts:<line> frames only when the flag is enabled.

Sequence Diagram(s)

sequenceDiagram
  participant AST as AST (new expr)
  participant lower_new as lower_new
  participant HIR as Expr::New / NewDynamic / NewDynamicSpread
  participant codegen as new_dynamic codegen
  participant runtime as JS runtime throw helper

  AST->>lower_new: new_expr.span.lo.0 → new_byte_offset
  lower_new->>HIR: construct with byte_offset = new_byte_offset
  HIR->>codegen: read byte_offset → new_byte_offset
  codegen->>codegen: emit_call_location_at(ctx, new_byte_offset)
  codegen->>runtime: js_throw_not_a_constructor / js_new_function_construct
  runtime-->>AST: TypeError at file:line (with --debug-symbols)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Poem

🐇 A rabbit hops through bytecode lanes,
marking each new with where it came.
No more anonymous frames in the stack —
at main.ts:4 leads the error track.
With offsets threaded, debug flags bright,
the TypeError now points to the right site. ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title concisely and accurately describes the main change: adding source location tracking to 'construct' errors (TypeError: not a constructor) and ReferenceError throws, which aligns with all file modifications in the changeset.
Description check ✅ Passed The PR description comprehensively covers all required template sections: a clear summary, detailed changes with file paths, related issue reference (#5253), thorough test plan with passing evidence, and proper checklist acknowledgment of version/metadata handling.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/diag-construct-reference-locations2

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
crates/perry-hir/src/analysis.rs (1)

1409-1414: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle NewDynamicSpread in these ad-hoc traversals too.

These updates make NewDynamic accept the new field, but NewDynamicSpread still falls through to _ => {}. That skips assignments and this rewrites inside new <callee>(...args) spread constructor expressions.

Proposed fix
         Expr::NewDynamic { callee, args, .. } => {
             collect_assigned_locals_expr(callee, assigned);
             for arg in args {
                 collect_assigned_locals_expr(arg, assigned);
             }
         }
+        Expr::NewDynamicSpread { callee, args, .. } => {
+            collect_assigned_locals_expr(callee, assigned);
+            for arg in args {
+                match arg {
+                    CallArg::Expr(e) | CallArg::Spread(e) => {
+                        collect_assigned_locals_expr(e, assigned)
+                    }
+                }
+            }
+        }
         Expr::NewDynamic { callee, args, .. } => {
             replace_this_in_expr(callee, this_id);
             for a in args {
                 replace_this_in_expr(a, this_id);
             }
         }
+        Expr::NewDynamicSpread { callee, args, .. } => {
+            replace_this_in_expr(callee, this_id);
+            for a in args {
+                match a {
+                    CallArg::Expr(e) | CallArg::Spread(e) => replace_this_in_expr(e, this_id),
+                }
+            }
+        }

Also applies to: 2056-2061

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-hir/src/analysis.rs` around lines 1409 - 1414, The
`collect_assigned_locals_expr` function currently handles `Expr::NewDynamic` by
collecting assigned locals from the callee and arguments, but
`Expr::NewDynamicSpread` falls through to the default case which does nothing,
causing assignments and this rewrites inside spread constructor expressions to
be skipped. Add a handler for `NewDynamicSpread` that mirrors the `NewDynamic`
handler by collecting assigned locals from both the callee and args in the same
way, and apply this same pattern to all other locations in the file where
similar ad-hoc traversals exist that handle `NewDynamic` but not
`NewDynamicSpread`.
crates/perry-hir/src/js_transform/imports.rs (1)

708-729: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle Expr::NewDynamicSpread alongside Expr::NewDynamic. The HIR now distinguishes spread constructors, but these recursive passes still only match NewDynamic, so children under new Foo(...args) fall through the wildcard and get skipped.

  • crates/perry-hir/src/js_transform/imports.rs#L708-L729: add a NewDynamicSpread arm here so import rewriting still recurses through spread constructor args.
  • crates/perry-hir/src/lower/closure_analysis.rs#L249-L254: add the spread arm in widen_mutable_captures_expr.
  • crates/perry-hir/src/lower/closure_analysis.rs#L557-L562: add the spread arm in collect_closure_assigned_expr.
  • crates/perry-hir/src/lower/closure_analysis.rs#L1284-L1289: add the spread arm in collect_closure_assigned_in_body_expr.
  • crates/perry-hir/src/js_transform/cross_module_natives.rs#L908-L913: add the spread arm here, and mirror it in the sibling scan helper above.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-hir/src/js_transform/imports.rs` around lines 708 - 729, The
transform functions are not handling the NewDynamicSpread variant of constructor
expressions, causing arguments in spread constructor calls like new Foo(...args)
to be skipped during traversal. Add NewDynamicSpread match arms alongside
NewDynamic in five locations: (1) in
crates/perry-hir/src/js_transform/imports.rs lines 708-729 within the match
statement that transforms constructors for import rewriting, (2) in
crates/perry-hir/src/lower/closure_analysis.rs lines 249-254 in the
widen_mutable_captures_expr function, (3) in
crates/perry-hir/src/lower/closure_analysis.rs lines 557-562 in the
collect_closure_assigned_expr function, (4) in
crates/perry-hir/src/lower/closure_analysis.rs lines 1284-1289 in the
collect_closure_assigned_in_body_expr function, and (5) in
crates/perry-hir/src/js_transform/cross_module_natives.rs lines 908-913 where
you should add the spread arm and mirror it in the sibling scan helper. Each
NewDynamicSpread arm should recursively process the callee and args in the same
way as NewDynamic to ensure spread constructors are properly analyzed.
🧹 Nitpick comments (1)
crates/perry/tests/issue_5253_construct_reference_source_location.rs (1)

135-145: ⚡ Quick win

Tighten default-build assertions to require <anonymous> fallback.

Line 142 and Line 185 only check that at main.ts: is absent. To match the stated regression contract, assert <anonymous> is present as well.

Suggested patch
 #[test]
 fn default_build_keeps_anonymous_frame_for_not_a_constructor() {
@@
     assert!(
         !stdout.contains("at main.ts:"),
         "default build must NOT emit a source location; got:\n{stdout}"
     );
+    assert!(
+        stdout.contains("<anonymous>"),
+        "default build should keep an anonymous frame; got:\n{stdout}"
+    );
 }
@@
 #[test]
 fn default_build_keeps_anonymous_frame_for_reference_error() {
@@
     assert!(
         !stdout.contains("at main.ts:"),
         "default build must NOT emit a source location; got:\n{stdout}"
     );
+    assert!(
+        stdout.contains("<anonymous>"),
+        "default build should keep an anonymous frame; got:\n{stdout}"
+    );
 }

Also applies to: 178-188

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry/tests/issue_5253_construct_reference_source_location.rs` around
lines 135 - 145, The assertions in
default_build_keeps_anonymous_frame_for_not_a_constructor are incomplete.
Currently they only verify that at main.ts is absent, but to properly validate
the regression contract, they must also assert that the <anonymous> fallback is
present in the output. Add a second assertion using stdout.contains to verify
that <anonymous> appears in stdout. Apply the same tightened assertion pattern
to the other test function mentioned in the comment (the sibling location) to
ensure both tests explicitly validate the presence of the <anonymous> fallback
rather than just the absence of source locations.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/perry/tests/issue_5253_construct_reference_source_location.rs`:
- Around line 98-100: The test at lines 98-100 runs the compiled binary and
captures stdout but does not verify that the command executed successfully
before processing the output. Add a check for `run.status.success()` after the
Command output is obtained and before converting the stdout to a String. This
ensures that if the binary exits with a non-zero status code, the test fails
immediately rather than attempting to validate potentially incomplete or
corrupted output, which could mask regressions.

---

Outside diff comments:
In `@crates/perry-hir/src/analysis.rs`:
- Around line 1409-1414: The `collect_assigned_locals_expr` function currently
handles `Expr::NewDynamic` by collecting assigned locals from the callee and
arguments, but `Expr::NewDynamicSpread` falls through to the default case which
does nothing, causing assignments and this rewrites inside spread constructor
expressions to be skipped. Add a handler for `NewDynamicSpread` that mirrors the
`NewDynamic` handler by collecting assigned locals from both the callee and args
in the same way, and apply this same pattern to all other locations in the file
where similar ad-hoc traversals exist that handle `NewDynamic` but not
`NewDynamicSpread`.

In `@crates/perry-hir/src/js_transform/imports.rs`:
- Around line 708-729: The transform functions are not handling the
NewDynamicSpread variant of constructor expressions, causing arguments in spread
constructor calls like new Foo(...args) to be skipped during traversal. Add
NewDynamicSpread match arms alongside NewDynamic in five locations: (1) in
crates/perry-hir/src/js_transform/imports.rs lines 708-729 within the match
statement that transforms constructors for import rewriting, (2) in
crates/perry-hir/src/lower/closure_analysis.rs lines 249-254 in the
widen_mutable_captures_expr function, (3) in
crates/perry-hir/src/lower/closure_analysis.rs lines 557-562 in the
collect_closure_assigned_expr function, (4) in
crates/perry-hir/src/lower/closure_analysis.rs lines 1284-1289 in the
collect_closure_assigned_in_body_expr function, and (5) in
crates/perry-hir/src/js_transform/cross_module_natives.rs lines 908-913 where
you should add the spread arm and mirror it in the sibling scan helper. Each
NewDynamicSpread arm should recursively process the callee and args in the same
way as NewDynamic to ensure spread constructors are properly analyzed.

---

Nitpick comments:
In `@crates/perry/tests/issue_5253_construct_reference_source_location.rs`:
- Around line 135-145: The assertions in
default_build_keeps_anonymous_frame_for_not_a_constructor are incomplete.
Currently they only verify that at main.ts is absent, but to properly validate
the regression contract, they must also assert that the <anonymous> fallback is
present in the output. Add a second assertion using stdout.contains to verify
that <anonymous> appears in stdout. Apply the same tightened assertion pattern
to the other test function mentioned in the comment (the sibling location) to
ensure both tests explicitly validate the presence of the <anonymous> fallback
rather than just the absence of source locations.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 32291315-c35b-4582-bbaf-f455e46b0b0e

📥 Commits

Reviewing files that changed from the base of the PR and between d46feff and 992df38.

📒 Files selected for processing (29)
  • crates/perry-codegen-js/src/emit/exprs.rs
  • crates/perry-codegen-wasm/src/emit/expr/classes.rs
  • crates/perry-codegen-wasm/src/emit/string_collection.rs
  • crates/perry-codegen/src/collectors/escape_check.rs
  • crates/perry-codegen/src/collectors/escape_news.rs
  • crates/perry-codegen/src/collectors/refs.rs
  • crates/perry-codegen/src/collectors/this_as_value.rs
  • crates/perry-codegen/src/expr/new_dynamic.rs
  • crates/perry-codegen/src/lower_call/mod.rs
  • crates/perry-hir/src/analysis.rs
  • crates/perry-hir/src/ir/expr.rs
  • crates/perry-hir/src/js_transform/cross_module_natives.rs
  • crates/perry-hir/src/js_transform/imports.rs
  • crates/perry-hir/src/lower/closure_analysis.rs
  • crates/perry-hir/src/lower/expr_call/globals.rs
  • crates/perry-hir/src/lower/expr_call/native_module.rs
  • crates/perry-hir/src/lower/expr_new.rs
  • crates/perry-hir/src/lower/expr_new_builtins.rs
  • crates/perry-hir/src/lower/expr_object.rs
  • crates/perry-hir/src/lower/lower_expr.rs
  • crates/perry-hir/src/lower/mod.rs
  • crates/perry-hir/src/lower_decl/block.rs
  • crates/perry-hir/src/monomorph/driver.rs
  • crates/perry-hir/src/monomorph/substitute_expr.rs
  • crates/perry-hir/src/monomorph/update_call_sites.rs
  • crates/perry-hir/src/stable_hash/expr.rs
  • crates/perry-hir/src/walker/expr_mut.rs
  • crates/perry-hir/src/walker/expr_ref.rs
  • crates/perry/tests/issue_5253_construct_reference_source_location.rs

Comment on lines +98 to +100
let bin = root.join("main_bin");
let run = Command::new(&bin).output().expect("run compiled binary");
String::from_utf8_lossy(&run.stdout).into_owned()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Assert fixture process success before validating stdout.

At Line 99, the test runs main_bin but never checks run.status.success(). A non-zero exit can still produce partial stdout and hide regressions.

Suggested patch
 fn run_fixture(fixture: &str, extra_args: &[&str]) -> String {
@@
     let bin = root.join("main_bin");
     let run = Command::new(&bin).output().expect("run compiled binary");
+    assert!(
+        run.status.success(),
+        "compiled binary must exit successfully (args {extra_args:?}); stderr:\n{}",
+        String::from_utf8_lossy(&run.stderr)
+    );
     String::from_utf8_lossy(&run.stdout).into_owned()
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry/tests/issue_5253_construct_reference_source_location.rs` around
lines 98 - 100, The test at lines 98-100 runs the compiled binary and captures
stdout but does not verify that the command executed successfully before
processing the output. Add a check for `run.status.success()` after the Command
output is obtained and before converting the stdout to a String. This ensures
that if the binary exits with a non-zero status code, the test fails immediately
rather than attempting to validate potentially incomplete or corrupted output,
which could mask regressions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant