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:
35
examples/0187-types-enum-qualified-variant.sx
Normal file
35
examples/0187-types-enum-qualified-variant.sx
Normal file
@@ -0,0 +1,35 @@
|
||||
// Qualified enum-variant construction: `EnumType.variant` for a payloadless
|
||||
// variant, the explicit twin of the leading-dot `.variant` form. Works for a
|
||||
// plain enum and for a payloadless variant of a tagged union; a payload-carrying
|
||||
// variant keeps its call form (`Shape.circle(2.0)`), unaffected.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
Color :: enum { red; green; blue; }
|
||||
|
||||
Shape :: enum {
|
||||
circle: f32; // payload-carrying
|
||||
dot; // payloadless
|
||||
}
|
||||
|
||||
main :: () {
|
||||
// Plain enum, qualified construction.
|
||||
c := Color.green;
|
||||
if c == {
|
||||
case .red: { print("red\n"); }
|
||||
case .green: { print("green\n"); }
|
||||
case .blue: { print("blue\n"); }
|
||||
}
|
||||
|
||||
// Tagged union: payloadless variant qualified, payload variant via call.
|
||||
d := Shape.dot;
|
||||
if d == {
|
||||
case .circle: (r) { print("circle {}\n", r); }
|
||||
case .dot: { print("dot\n"); }
|
||||
}
|
||||
s := Shape.circle(2.0);
|
||||
if s == {
|
||||
case .circle: (r) { print("circle {}\n", r); }
|
||||
case .dot: { print("dot\n"); }
|
||||
}
|
||||
}
|
||||
88
examples/0631-comptime-compiler-register-graph.sx
Normal file
88
examples/0631-comptime-compiler-register-graph.sx
Normal file
@@ -0,0 +1,88 @@
|
||||
// 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;
|
||||
}
|
||||
31
examples/0632-comptime-metatype-make-enum-payloadless.sx
Normal file
31
examples/0632-comptime-metatype-make-enum-payloadless.sx
Normal file
@@ -0,0 +1,31 @@
|
||||
// Regression (issue 0142): a comptime-minted FULLY payloadless enum (every
|
||||
// variant tagless) must mint as a real `.@"enum"`, not an all-void tagged_union
|
||||
// — the latter has an IR/LLVM size mismatch that tripped `verifySizes` at
|
||||
// codegen. `make_enum` (declare/define) with an all-void variant list now
|
||||
// produces an ordinary enum, usable like a hand-written one.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "modules/std/meta.sx";
|
||||
|
||||
make_suit :: () -> Type {
|
||||
return make_enum("Suit", EnumVariant.[
|
||||
EnumVariant.{ name = "hearts", payload = void },
|
||||
EnumVariant.{ name = "spades", payload = void },
|
||||
EnumVariant.{ name = "diamonds", payload = void },
|
||||
]);
|
||||
}
|
||||
Suit :: make_suit();
|
||||
|
||||
show :: (s: Suit) {
|
||||
if s == {
|
||||
case .hearts: { print("hearts\n"); }
|
||||
case .spades: { print("spades\n"); }
|
||||
case .diamonds: { print("diamonds\n"); }
|
||||
}
|
||||
}
|
||||
|
||||
main :: () {
|
||||
show(.spades); // leading-dot, typed context
|
||||
x := Suit.diamonds; // qualified construction
|
||||
show(x);
|
||||
}
|
||||
15
examples/0633-comptime-compiler-namespaced-type.sx
Normal file
15
examples/0633-comptime-compiler-namespaced-type.sx
Normal file
@@ -0,0 +1,15 @@
|
||||
// A comptime-minted type (built via the compiler API in `shapes.sx`) reached
|
||||
// through a NAMESPACED import: `s.Suit`, with qualified variant construction
|
||||
// `s.Suit.spades`. The bare-import form is example 0634; in-file minting +
|
||||
// reflection is 0631.
|
||||
|
||||
#import "modules/std.sx";
|
||||
s :: #import "0633-comptime-compiler-namespaced-type/shapes.sx";
|
||||
|
||||
main :: () {
|
||||
x := s.Suit.spades;
|
||||
if x == {
|
||||
case .hearts: { print("hearts\n"); }
|
||||
case .spades: { print("spades\n"); }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// Reaches shapes.sx via a NAMESPACED import; re-exports a helper over its Suit.
|
||||
#import "modules/std.sx";
|
||||
s :: #import "shapes.sx";
|
||||
|
||||
name_of :: (x: s.Suit) -> string {
|
||||
if x == { case .hearts: { return "hearts"; } case .spades: { return "spades"; } }
|
||||
return "?";
|
||||
}
|
||||
17
examples/0633-comptime-compiler-namespaced-type/shapes.sx
Normal file
17
examples/0633-comptime-compiler-namespaced-type/shapes.sx
Normal file
@@ -0,0 +1,17 @@
|
||||
// A module that MINTS a comptime enum via the compiler API and exports it.
|
||||
#import "modules/std.sx";
|
||||
|
||||
compiler :: #library "compiler";
|
||||
|
||||
Member :: struct { name: string; ty: Type; }
|
||||
declare_type :: (name: string) -> Type abi(.zig) extern compiler;
|
||||
register_type :: (handle: Type, kind: i64, members: []Member) -> Type abi(.zig) extern compiler;
|
||||
|
||||
build_suit :: () -> Type {
|
||||
return register_type(declare_type("Suit"), 2, .[ // kind 2 = actual enum
|
||||
Member.{ name = "hearts", ty = void },
|
||||
Member.{ name = "spades", ty = void },
|
||||
]);
|
||||
}
|
||||
|
||||
Suit :: build_suit();
|
||||
14
examples/0634-comptime-compiler-bare-import-type.sx
Normal file
14
examples/0634-comptime-compiler-bare-import-type.sx
Normal file
@@ -0,0 +1,14 @@
|
||||
// A comptime-minted type (built via the compiler API in 0633's `shapes.sx`)
|
||||
// reached through a BARE import: the minted `Suit` is in scope flat, with
|
||||
// qualified variant construction `Suit.spades`. Namespaced form is 0633.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "0633-comptime-compiler-namespaced-type/shapes.sx";
|
||||
|
||||
main :: () {
|
||||
x := Suit.spades;
|
||||
if x == {
|
||||
case .hearts: { print("hearts\n"); }
|
||||
case .spades: { print("spades\n"); }
|
||||
}
|
||||
}
|
||||
17
examples/0635-comptime-compiler-multi-edge-import.sx
Normal file
17
examples/0635-comptime-compiler-multi-edge-import.sx
Normal file
@@ -0,0 +1,17 @@
|
||||
// A comptime-minting module (0633's `shapes.sx`) reached via TWO import edges in
|
||||
// one build: directly (bare) here, and indirectly through `indirect.sx` (which
|
||||
// imports it namespaced as `s`). The minted `Suit` must be ONE type across both
|
||||
// edges — `name_of` (typed `s.Suit` in indirect.sx) accepts the bare `Suit`
|
||||
// constructed here. Exercises that re-evaluating the type-fn across import paths
|
||||
// is idempotent (same TypeId), not a re-mint conflict.
|
||||
|
||||
#import "modules/std.sx";
|
||||
#import "0633-comptime-compiler-namespaced-type/shapes.sx"; // bare edge → `Suit`
|
||||
#import "0633-comptime-compiler-namespaced-type/indirect.sx"; // edge via `s :: shapes`
|
||||
|
||||
main :: () {
|
||||
a := Suit.spades; // bare Suit
|
||||
print("{}\n", name_of(a)); // passed where indirect expects s.Suit (same type)
|
||||
b := Suit.hearts;
|
||||
print("{}\n", name_of(b));
|
||||
}
|
||||
1
examples/expected/0187-types-enum-qualified-variant.exit
Normal file
1
examples/expected/0187-types-enum-qualified-variant.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
green
|
||||
dot
|
||||
circle 2.000000
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
spades
|
||||
tag=7
|
||||
Suit kind=2, GraphA kind=3
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
spades
|
||||
diamonds
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
spades
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
spades
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
spades
|
||||
hearts
|
||||
Reference in New Issue
Block a user