From 14f30f341c84af31984bd6529fa170e7129f1a23 Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 17 Jun 2026 06:29:23 +0300 Subject: [PATCH] fix(metatype): reject declare() never completed by define() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A bare declare("X") with no define left a zero-field nominal slot that panicked at codegen (verifySizes: llvm_size != ir_size). evalComptimeType now detects a zero-variant tagged_union result and emits a clean build-gating diagnostic naming the type — a zero-variant enum is never a legitimate construction result (defineEnum rejects empty variant lists too). Self-reference (a declared slot completed by define) is unaffected. --- src/ir/lower/comptime.zig | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/ir/lower/comptime.zig b/src/ir/lower/comptime.zig index 35e3fed6..48d350ad 100644 --- a/src/ir/lower/comptime.zig +++ b/src/ir/lower/comptime.zig @@ -473,7 +473,21 @@ pub fn evalComptimeType(self: *Lowering, expr: *const Node) ?TypeId { } return null; }; - return result.asTypeId(); + const tid = result.asTypeId() orelse return null; + // A bare `declare("X")` that is never completed by a `define(handle, …)` + // leaves a zero-FIELD nominal slot (an undefined enum). Sizing / constructing + // / emitting it panics at codegen (`verifySizes`: llvm_size != ir_size). + // Reject it loudly here — a zero-variant enum is never a legitimate result + // (`defineEnum` rejects an empty variant list too). + if (!tid.isBuiltin()) { + const info = self.module.types.get(tid); + if (info == .tagged_union and info.tagged_union.fields.len == 0) { + if (self.diagnostics) |d| + d.addFmt(.err, expr.span, "type '{s}' is declared but never defined — complete it with define(handle, info)", .{self.module.types.getString(info.tagged_union.name)}); + return null; + } + } + return tid; } /// Rename a nominal type to a new name, re-keying `intern_map` so