diff --git a/issues/0140-comptime-type-construction-bail-unresolved-panic.md b/issues/0140-comptime-type-construction-bail-unresolved-panic.md index 51ed1acb..806afd25 100644 --- a/issues/0140-comptime-type-construction-bail-unresolved-panic.md +++ b/issues/0140-comptime-type-construction-bail-unresolved-panic.md @@ -1,5 +1,21 @@ # 0140 — a failing comptime type construction panics ("unresolved type reached LLVM emission") instead of diagnosing the bail +> **RESOLVED (2026-06-17).** Root cause exactly as the investigation prompt +> hypothesized: `evalComptimeType` (`src/ir/lower/comptime.zig`) did +> `interp.call(...) catch return null`, dropping the interpreter's +> `last_bail_detail`; callers poisoned to `.unresolved` with no diagnostic, so the +> sentinel reached LLVM emission and tripped the codegen panic (or hid behind a +> downstream cascade). Fix: clear `Interpreter.last_bail_detail` before the call, +> and on the `catch` emit a build-gating `.err` at the construction expression's +> span — `"comptime type construction failed: {detail}"` (mirroring the `#run` +> surfacing at `emit_llvm.zig:856`) — then return null (keeping the `.unresolved` +> poison, now gated by a real message). The empty-variants repro now prints +> `comptime type construction failed: comptime define(): enum has no variants` +> and exits 1 (no panic); make_enum-style computed-slice failures surface their +> root reason at the construction site instead of only the `.green` cascade. +> Regression test: +> [examples/1179-diagnostics-comptime-type-construction-bail.sx](../examples/1179-diagnostics-comptime-type-construction-bail.sx). + ## Symptom One-line: when a comptime type construction (`declare`/`define`) bails in the diff --git a/src/ir/lower/comptime.zig b/src/ir/lower/comptime.zig index d709c206..35e3fed6 100644 --- a/src/ir/lower/comptime.zig +++ b/src/ir/lower/comptime.zig @@ -454,7 +454,25 @@ pub fn evalComptimeType(self: *Lowering, expr: *const Node) ?TypeId { if (self.diagnostics) |d| if (d.import_sources) |sm| interp.setSourceMap(sm); interp.setMintTable(&self.module.types); - const result = interp.call(func_id, &.{}) catch return null; + // Clear the interp's last-bail channel so a bail HERE is attributable to + // THIS construction (not a stale message from an earlier comptime eval). + interp_mod.Interpreter.last_bail_detail = null; + const result = interp.call(func_id, &.{}) catch |err| { + // A comptime type construction (declare/define, reflection) that bails + // must surface a build-gating diagnostic naming the reason — NOT poison + // to `.unresolved` silently and let that crash at LLVM emission + // ("unresolved type reached LLVM emission") or hide behind a downstream + // cascade (issue 0140). The interp's `bailDetail` already set the precise + // reason; mirror the `#run` path (emit_llvm.zig) and render it. Returning + // null keeps the `.unresolved` poison for the caller, but now the build + // is gated by a real message, so no unresolved type reaches emission + // unannounced. + if (self.diagnostics) |d| { + const detail = interp_mod.Interpreter.last_bail_detail orelse @errorName(err); + d.addFmt(.err, expr.span, "comptime type construction failed: {s}", .{detail}); + } + return null; + }; return result.asTypeId(); }