comptime VM: Phase 3 — register_type write side + payloadless-enum fixes

The mutating compiler-API, minting types LAZILY at lowering time (single pass,
the existing runComptimeTypeFunc path — so the write side is legacy-only; the
VM isn't wired at lowering time, and the read-side readers stay dual-path):

  declare_type(name) -> Type            forward nominal handle (≈ declare)
  pointer_to(t) -> Type                 build *T references
  register_type(handle, kind, members)  ONE kind-branching fill (≈ unified define)

register_type branches on kind IN THE COMPILER (subsuming define's per-kind
dispatch); codes match type_kind: 1 struct, 2 actual .@"enum", 3 tagged_union,
4 tuple. Members are {name: string, ty: Type}. A non-generic `-> Type` builder is
now flagged is_comptime (decl.zig) so its dead body permits the welded calls.

Graph support: forward declare_type handles + pointer_to express a mutually-
recursive A<->B graph (*A, *B, B-by-value) before bodies are filled. register_type
is idempotent — re-filling a nominal slot (a minting module reached via two import
edges) re-mints identically rather than erroring (nominalIdent reads identity from
any nominal kind).

Fixes (issue 0142):
- A fully payloadless comptime-minted enum was minted as an all-void tagged_union,
  whose IR size disagrees with its LLVM size -> verifySizes panic. Now mints a real
  .@"enum" (register_type kind 2 AND the metatype defineEnum).
- Bare `EnumType.variant` qualified construction of a payloadless variant wasn't
  supported (failed for hand-written enums too — the type name lowered to a Type
  value). Added in lowerFieldAccess via isPayloadlessVariant; payload-carrying
  variants keep their call form.

Examples: 0631 (graph + actual enum + reflection), 0632 (make_enum all-void),
0633/0634/0635 (namespaced / bare / multi-edge import of a minted type), 0187
(qualified variant construction). Unit tests added.

Parity 697/697 (gate OFF and -Dcomptime-flat).
This commit is contained in:
agra
2026-06-18 10:47:36 +03:00
parent 27bc301651
commit 9e3aabcf76
35 changed files with 657 additions and 15 deletions

View File

@@ -2197,10 +2197,37 @@ pub const Interpreter = struct {
}
// Complete the declared slot IN PLACE: it already has its name + nominal
// id (from `declare`); fill the body. Name/id unchanged → the intern key
// is stable, so `updatePreservingKey`.
// id (from `declare`); fill the body.
const cur = tbl.get(handle);
if (cur != .tagged_union) return bailDetail("comptime define(): handle is not a declare()'d enum slot");
// A FULLY payloadless variant set (every payload `void`) is an actual
// enum — mint a `.@"enum"`, exactly like a hand-written `enum { a; b; }`.
// Minting it as an all-void `tagged_union` instead gives a type whose IR
// size disagrees with its LLVM size (a tag, but no payload storage), which
// trips `verifySizes` at codegen. A kind change re-keys the slot, so
// `replaceKeyedInfo` (not `updatePreservingKey`, which asserts the kind is
// stable — true only for the tagged_union path below).
var all_void = true;
for (fields.items) |f| {
if (f.ty != .void) {
all_void = false;
break;
}
}
if (all_void) {
var variants = std.ArrayList(types.StringId).empty;
for (fields.items) |f| variants.append(self.alloc, f.name) catch return error.CannotEvalComptime;
const en: types.TypeInfo = .{ .@"enum" = .{
.name = cur.tagged_union.name,
.variants = variants.items,
.nominal_id = cur.tagged_union.nominal_id,
} };
tbl.replaceKeyedInfo(handle, en);
return .{ .value = .{ .type_tag = handle } };
}
// Payload-carrying enum → tagged_union. Name/id unchanged → stable key.
const full: types.TypeInfo = .{ .tagged_union = .{
.name = cur.tagged_union.name,
.fields = fields.items,
@@ -2305,7 +2332,7 @@ pub const Interpreter = struct {
/// A `[]EnumVariant` slice evaluates to a `{ data, len }` aggregate (`len` an
/// int); a `[N]EnumVariant` array literal evaluates to the element aggregate
/// directly. Returns null for any other shape (the caller bails loudly).
fn decodeVariantElements(result: Value) ?[]const Value {
pub fn decodeVariantElements(result: Value) ?[]const Value {
const fields = switch (result) {
.aggregate => |f| f,
else => return null,