diff --git a/examples/comptime/0650-comptime-make-variant.sx b/examples/comptime/0650-comptime-make-variant.sx new file mode 100644 index 00000000..ed01082d --- /dev/null +++ b/examples/comptime/0650-comptime-make-variant.sx @@ -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; +} diff --git a/examples/comptime/expected/0650-comptime-make-variant.exit b/examples/comptime/expected/0650-comptime-make-variant.exit new file mode 100644 index 00000000..573541ac --- /dev/null +++ b/examples/comptime/expected/0650-comptime-make-variant.exit @@ -0,0 +1 @@ +0 diff --git a/examples/comptime/expected/0650-comptime-make-variant.stderr b/examples/comptime/expected/0650-comptime-make-variant.stderr new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/examples/comptime/expected/0650-comptime-make-variant.stderr @@ -0,0 +1 @@ + diff --git a/examples/comptime/expected/0650-comptime-make-variant.stdout b/examples/comptime/expected/0650-comptime-make-variant.stdout new file mode 100644 index 00000000..cf10ed24 --- /dev/null +++ b/examples/comptime/expected/0650-comptime-make-variant.stdout @@ -0,0 +1,4 @@ +v = 1.500000 2.500000 3.500000 +s = hello +big = 10 20 30 40 true +n = 99 diff --git a/library/modules/std/meta.sx b/library/modules/std/meta.sx index c030c6f9..484eb461 100644 --- a/library/modules/std/meta.sx +++ b/library/modules/std/meta.sx @@ -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; +}