Files
sx/examples/0631-comptime-compiler-register-graph.sx
agra 9e3aabcf76 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).
2026-06-18 10:47:36 +03:00

89 lines
3.6 KiB
Plaintext

// Comptime compiler API — the WRITE side: one kind-branching `register_type`
// minting an actual enum AND a graph of mutually-recursive types (Phase 3).
//
// `declare_type` / `pointer_to` / `register_type` are bound to the `compiler`
// library. They MINT into the type table, so they run at LOWERING time (lazily,
// on demand) — when a `-> Type` builder's result is first referenced — where the
// compiler still resolves references to the new types. (`#run` is too late: it
// runs at emit time, after the type table is frozen.) They take/return real
// `Type` values (like the metatype's declare/define), and `register_type`
// branches on the `kind` arg IN THE COMPILER — the codes match the read-side
// `type_kind`: 1 struct · 2 enum · 3 tagged_union · 4 tuple.
//
// Suit :: enum { hearts; spades; diamonds; } (actual, payloadless)
// GraphA :: enum { self_ref: *A; to_b: B; tag: u32; } (payloads → tagged_union)
// GraphB :: enum { back_a: *A; self_b: *B; num: u32; }
//
// Forward `declare_type` handles + `pointer_to` make the A<->B cycle expressible
// before either body is filled.
#import "modules/std.sx";
compiler :: #library "compiler";
Member :: struct { name: string; ty: Type; }
StringId :: u32;
TypeId :: u32;
intern :: (s: string) -> StringId abi(.zig) extern compiler;
find_type :: (name: StringId) -> TypeId abi(.zig) extern compiler;
type_kind :: (t: TypeId) -> i64 abi(.zig) extern compiler;
declare_type :: (name: string) -> Type abi(.zig) extern compiler;
pointer_to :: (t: Type) -> Type abi(.zig) extern compiler;
register_type :: (handle: Type, kind: i64, members: []Member) -> Type abi(.zig) extern compiler;
KIND_ENUM :: 2; // an ACTUAL payloadless enum
KIND_TAGGED_UNION :: 3; // a payload-carrying enum
// An actual enum: variants are names, no payloads (ty = void).
make_suit :: () -> Type {
return register_type(declare_type("Suit"), KIND_ENUM, .[
Member.{ name = "hearts", ty = void },
Member.{ name = "spades", ty = void },
Member.{ name = "diamonds", ty = void },
]);
}
Suit :: make_suit();
// The mutually-recursive A <-> B graph (payload variants → tagged_union).
build_graph :: () -> Type {
hA := declare_type("GraphA");
hB := declare_type("GraphB");
register_type(hA, KIND_TAGGED_UNION, .[
Member.{ name = "self_ref", ty = pointer_to(hA) }, // *A — self-reference
Member.{ name = "to_b", ty = hB }, // B by value (forward)
Member.{ name = "tag", ty = u32 }, // a plain payload
]);
register_type(hB, KIND_TAGGED_UNION, .[
Member.{ name = "back_a", ty = pointer_to(hA) }, // *A — back-reference
Member.{ name = "self_b", ty = pointer_to(hB) }, // *B — self-reference
Member.{ name = "num", ty = u32 },
]);
return hA;
}
GraphA :: build_graph();
// Reflect the minted types (read side, at #run) to confirm their kinds.
suit_kind :: #run type_kind(find_type(intern("Suit"))); // 2 = actual enum
grapha_kind :: #run type_kind(find_type(intern("GraphA"))); // 3 = tagged_union
main :: () -> i32 {
// Suit is a real, usable enum.
s := Suit.spades;
if s == {
case .hearts: { print("hearts\n"); }
case .spades: { print("spades\n"); }
case .diamonds: { print("diamonds\n"); }
}
// GraphA is a real, usable tagged union.
a := GraphA.tag(7);
if a == {
case .tag: (n) { print("tag={}\n", n); }
case .self_ref: (p) { print("self_ref\n"); }
case .to_b: (b) { print("to_b\n"); }
}
print("Suit kind={}, GraphA kind={}\n", suit_kind, grapha_kind);
return 0;
}