Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions src/backend/dev/LirCodeGen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1243,10 +1243,15 @@ pub fn LirCodeGen(comptime target: RocTarget) type {
const final_result = try self.generateExpr(expr_id);
const actual_ret_layout = result_layout;

// Store result to the saved result pointer
const ret_size = self.getLayoutSize(actual_ret_layout);
if (ret_size > 0) {
try self.storeResultToSavedPtr(final_result, actual_ret_layout, result_ptr_save_reg, tuple_len);
// If the body never returns (e.g., a top-level expression that is
// entirely a runtime_error / crash), the trap has already been
// emitted and there is no value to store. Skip the result store.
if (final_result != .noreturn) {
// Store result to the saved result pointer
const ret_size = self.getLayoutSize(actual_ret_layout);
if (ret_size > 0) {
try self.storeResultToSavedPtr(final_result, actual_ret_layout, result_ptr_save_reg, tuple_len);
}
}

// Emit epilogue using DeferredFrameBuilder with actual stack usage
Expand Down
37 changes: 37 additions & 0 deletions src/eval/test/comptime_eval_test.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3447,3 +3447,40 @@ test "issue 9281: dev evaluator stack overflow with nested recursive opaque type
try testing.expect(code_result.code.len > 0);
try testing.expect(code_result.entry_offset < code_result.code.len);
}

test "issue #9349: top-level expect with type-erroneous condition does not panic in dev codegen" {
const src =
\\foo : U64 -> U64
\\foo = |x| x
\\
\\expect foo(Dynamite) == 5
;

var result = try parseCheckAndEvalModule(src);
defer cleanupEvalModule(&result);

// Locate the top-level `expect` statement so we can ask the dev
// backend to generate code for it, the same way `roc test` does
// (see rocTest in src/cli/main.zig).
const statements = result.module_env.store.sliceStatements(result.module_env.all_statements);
var expect_body: ?can.CIR.Expr.Idx = null;
for (statements) |stmt_idx| {
const stmt = result.module_env.store.getStatement(stmt_idx);
if (stmt == .s_expect) {
expect_body = stmt.s_expect.body;
break;
}
}
try testing.expect(expect_body != null);

var dev_eval = try DevEvaluator.init(test_allocator, null);
defer dev_eval.deinit();

const all_module_envs = [_]*ModuleEnv{ result.builtin_module.env, result.module_env };

// Returning an error is acceptable; panicking is the bug.
if (dev_eval.generateCode(result.module_env, expect_body.?, &all_module_envs, null)) |code_result_ok| {
var code_result = code_result_ok;
code_result.deinit();
} else |_| {}
}
17 changes: 17 additions & 0 deletions src/lir/MirToLir.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5111,6 +5111,23 @@ fn lowerLowLevel(self: *Self, ll: anytype, mir_expr_id: MIR.ExprId, region: Regi
try self.scratch_lir_expr_ids.append(self.allocator, ensured_arg);
}

// If any lowered arg is itself a `runtime_error` (an atomic, noreturn LIR
// expression — see `isAtomicExpr`), the entire low-level call is
// unreachable: evaluating that arg traps and the op never executes. Replace
// the call with a single `runtime_error` carrying the call's ret layout so
// backends never see a noreturn expression in a value-position arg slot.
// Any accumulated let-bindings for prior args are preserved by `acc.finish`
// so their side effects still happen in source order before the trap.
for (self.scratch_lir_expr_ids.items[save_expr_len..]) |arg_lir_id| {
if (self.lir_store.getExpr(arg_lir_id) == .runtime_error) {
const re_result = try self.lir_store.addExpr(
.{ .runtime_error = .{ .ret_layout = ret_layout } },
region,
);
return acc.finish(re_result, ret_layout, region);
}
}

const lir_args = try self.lir_store.addExprSpan(self.scratch_lir_expr_ids.items[save_expr_len..]);
const callable_proc = switch (ll.op) {
.list_sort_with => blk: {
Expand Down
Loading