feat: make_variant — construct a minted tagged-union value by index (stdlib)
The metatype system can MINT tagged-union types (`make_enum`) and READ them
(`field_count`/`field_name`/`field_type`/`field_value`); `make_variant` is the
missing WRITE side — build a value of a minted tagged-union `E` whose active
variant is `idx`, carrying `payload`. Needed when the variant is selected at
RUNTIME but the union was synthesized at comptime so its labels can't be spelled
as a literal `.label(payload)` — e.g. the `race` result: an `inline for 0..N (i)`
arm constructs the i-th variant of a synthesized result with the winner's value.
Pure sx in modules/std/meta.sx (NOT a compiler builtin): a minted tagged-union is
laid out `{ i64 tag @0, payload @ size_of(i64) }` (the compiler fixes `tag_type`
to i64 for minted enums), so make_variant zeroes the value then writes the tag and
payload at those offsets — the same offsets the compiler's own enum_init/payload
ops use. The header comment documents the minted-only / i64-tag layout coupling.
Adversarially reviewed (SHIP): the offset-8 payload assumption is exactly the
compiler's enum layout with no alignment hazard (the payload is an alignment-1
`[N x i8]` array immediately after the i64 header); complex payloads (multi-field
struct, string fat pointer, 40-byte struct) round-trip. Locked by
examples/comptime/0650-comptime-make-variant.sx (heterogeneous + complex payloads,
runtime-index selection via the comptime `case` form). Suite green (822/0).
This commit is contained in:
63
examples/comptime/0650-comptime-make-variant.sx
Normal file
63
examples/comptime/0650-comptime-make-variant.sx
Normal file
@@ -0,0 +1,63 @@
|
||||
// `make_variant($E, idx, payload)` (modules/std/meta.sx) — the WRITE side of the
|
||||
// metatype reflection triad: construct a value of a MINTED tagged-union by
|
||||
// VARIANT INDEX, when the variant is chosen at runtime and the union was
|
||||
// synthesized at comptime (so its labels can't be a literal `.label(…)`). This
|
||||
// is the shape the `race` result uses: an `inline for 0..N (i)` arm builds the
|
||||
// i-th variant of a synthesized result carrying the winner's value.
|
||||
//
|
||||
// Covers heterogeneous + COMPLEX payloads (multi-field struct, string fat
|
||||
// pointer, a larger struct, scalar) — make_variant zeroes the value then writes
|
||||
// the i64 tag @0 and the payload @ size_of(i64), so payloads of any size/shape
|
||||
// round-trip. Built with the natural early-return-per-arm pattern (a `return`
|
||||
// inside an `inline if` inside an `inline for`).
|
||||
#import "modules/std.sx";
|
||||
#import "modules/std/meta.sx";
|
||||
|
||||
Vec3 :: struct { x: f64; y: f64; z: f64; } // 24 bytes
|
||||
Big :: struct { a: i64; b: i64; c: i64; d: i64; tag: bool; } // 40 bytes (largest payload)
|
||||
|
||||
// A synthesized 4-variant tagged-union with complex, differently-sized payloads.
|
||||
R :: make_enum("R", .[
|
||||
EnumVariant.{ name = "v", payload = Vec3 },
|
||||
EnumVariant.{ name = "s", payload = string },
|
||||
EnumVariant.{ name = "big", payload = Big },
|
||||
EnumVariant.{ name = "n", payload = i64 },
|
||||
]);
|
||||
|
||||
// Build the variant chosen by a RUNTIME index, in the matching unrolled arm —
|
||||
// each arm's payload type differs. The `return` inside the `inline if` inside the
|
||||
// `inline for` is the pattern make_variant exists to enable.
|
||||
pick :: (idx: i64, vv: Vec3, sv: string, bv: Big, nv: i64) -> R {
|
||||
inline for 0..field_count(R) (i) {
|
||||
if idx == i {
|
||||
// Comptime match on the loop cursor `i` selects the arm whose payload
|
||||
// type matches variant `i` — cleaner than nested `inline if`/`else`.
|
||||
inline if i == {
|
||||
case 0: { return make_variant(R, i, vv); }
|
||||
case 1: { return make_variant(R, i, sv); }
|
||||
case 2: { return make_variant(R, i, bv); }
|
||||
else: { return make_variant(R, i, nv); }
|
||||
}
|
||||
}
|
||||
}
|
||||
return make_variant(R, 3, -1); // unreachable for a valid idx
|
||||
}
|
||||
|
||||
show :: (r: R) {
|
||||
if r == {
|
||||
case .v: (p) { print("v = {} {} {}\n", p.x, p.y, p.z); }
|
||||
case .s: (p) { print("s = {}\n", p); }
|
||||
case .big: (p) { print("big = {} {} {} {} {}\n", p.a, p.b, p.c, p.d, p.tag); }
|
||||
case .n: (p) { print("n = {}\n", p); }
|
||||
}
|
||||
}
|
||||
|
||||
main :: () -> i32 {
|
||||
v :: Vec3.{ x = 1.5, y = 2.5, z = 3.5 };
|
||||
b :: Big.{ a = 10, b = 20, c = 30, d = 40, tag = true };
|
||||
show(pick(0, v, "hello", b, 99));
|
||||
show(pick(1, v, "hello", b, 99));
|
||||
show(pick(2, v, "hello", b, 99));
|
||||
show(pick(3, v, "hello", b, 99));
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
v = 1.500000 2.500000 3.500000
|
||||
s = hello
|
||||
big = 10 20 30 40 true
|
||||
n = 99
|
||||
@@ -195,3 +195,34 @@ TryResult :: ($T: Type) -> Type {
|
||||
EnumVariant.{ name = "closed", payload = void },
|
||||
] }));
|
||||
}
|
||||
|
||||
// ── Constructing a minted tagged-union VALUE by variant index ─────────────────
|
||||
//
|
||||
// The read side reflects a type's members (`field_count`/`field_name`/
|
||||
// `field_type`) and a value's active payload (`field_value`); `make_variant` is
|
||||
// the missing WRITE side. It builds a value of a MINTED tagged-union `E` (one
|
||||
// made by `make_enum` / `define(declare, .enum(...))`) whose active variant is
|
||||
// `idx`, carrying `payload`. Needed when the variant is selected at RUNTIME but
|
||||
// the union was synthesized at comptime so its labels can't be spelled as a
|
||||
// literal `.label(payload)` — e.g. the `race` result: an `inline for 0..N (i)`
|
||||
// arm constructs the i-th variant of a synthesized `RaceResult`.
|
||||
//
|
||||
// LAYOUT COUPLING — MINTED ENUMS ONLY: a minted tagged-union is laid out
|
||||
// `{ i64 tag @0, payload bytes @ size_of(i64) }` (its `tag_type` is always `i64`
|
||||
// — the compiler's `declare`/`define` path fixes it). `make_variant` writes that
|
||||
// layout directly. It is NOT valid for a source `enum { … }` whose tag may be
|
||||
// narrower, nor for a `#packed`-backed union — spell those with a literal
|
||||
// `.label(…)`. Contract (caller-guaranteed, no runtime check, like any raw
|
||||
// construction): `0 <= idx < field_count(E)`, and `payload`'s type IS variant
|
||||
// `idx`'s payload type. `payload` must be non-void (a payload-less variant has
|
||||
// no value to carry — match it by tag).
|
||||
make_variant :: ($E: Type, idx: i64, payload: $P) -> E {
|
||||
out : E = ---;
|
||||
base : i64 = xx @out;
|
||||
memset(xx base, 0, size_of(E)); // clear tag padding + unused payload bytes
|
||||
tag_ptr : *i64 = xx base;
|
||||
tag_ptr.* = idx; // tag @ offset 0 (i64)
|
||||
pay_ptr : *P = xx (base + size_of(i64)); // payload @ offset size_of(i64) (after the tag)
|
||||
pay_ptr.* = payload;
|
||||
return out;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user