Files
sx/library/modules/std/meta.sx
agra 9f3f746c4b feat(metatype): widen type_info/define to tuple types
TypeInfo gains a `tuple(TupleInfo) variant (TupleInfo{elements: []Type},
positional/unnamed) — completing the reflect/construct triad with enum
and struct.

- meta.sx: TupleInfo + `tuple TypeInfo variant.
- interp: reflectTypeInfo builds .tuple (tag 2) as bare type_tag elements
  (no name pairs); defineType dispatches tag 2 -> defineTuple, which
  decodes []Type and completes the declare slot as a structural .tuple
  via replaceKeyedInfo (kind change). Tuples are structural so the
  declared name is vestigial, but the slot is still completed in place so
  define returns the handle (consistent with enum/struct).
- call.zig: the lower-time type_info guard now admits .tuple.

define(declare("P"), .tuple(.{elements=.[i64,f64]})) builds a tuple, and
define(declare("T"), type_info((i64,bool,f64))) round-trips one. Suite
green (683).
2026-06-17 07:05:55 +03:00

115 lines
5.3 KiB
Plaintext

// Comptime type metaprogramming — `declare` / `define` (construct a NEW nominal
// type from data), plus `type_info` / `field_type` (reflect a type → data) and
// the data model they reflect INTO and construct FROM.
//
// This is a SEPARATE on-demand module rather than part of the prelude: its data
// types would otherwise intern into every module's type table and shift every
// `.ir` snapshot. Import it explicitly: #import "modules/std/meta.sx";
//
// All four are comptime-only builtins — reaching one at runtime is a hard error
// (the type must be minted / reflected at compile time).
// One variant of a constructed enum: a name plus an optional payload type.
// `payload = void` means a tagless variant (e.g. `closed`).
EnumVariant :: struct {
name: string;
payload: Type;
}
// The shape of an enum/tagged-union being reflected or constructed. The type's
// NAME is supplied to `declare(name)`, not here — `declare` needs it at compile
// time to register the forward type so the body can reference itself (`*Name`).
EnumInfo :: struct {
variants: []EnumVariant;
}
// One field of a constructed struct: a name plus its type.
StructField :: struct {
name: string;
type: Type;
}
// The shape of a struct being reflected or constructed. As with `EnumInfo`, the
// type's NAME travels in `declare(name)`, not here.
StructInfo :: struct {
fields: []StructField;
}
// The element types of a tuple being reflected or constructed. Tuples are
// POSITIONAL (no field names), so this is just an ordered list of types.
TupleInfo :: struct {
elements: []Type;
}
// The reflected/constructed type shape. A tagged union over the kinds of type
// that can be minted — `` .`enum ``, `` .`struct `` and `` .`tuple `` all ship.
// The variants use the backtick raw-identifier escape so they read as the
// keywords (`` .`enum(...) `` / `` .`struct(...) `` / `` .`tuple(...) ``) rather
// than mangled `enum_` / `struct_` / `tuple_`.
TypeInfo :: enum {
`enum: EnumInfo;
`struct: StructInfo;
`tuple: TupleInfo;
}
// The compiler's ONLY type-construction primitives (comptime-only #builtins):
// declare(name) — mint a NEW empty (undefined) nominal type NAMED
// `name`, returned as a `Type` handle. The compiler
// registers the forward type at compile time, so the
// body of `define` can reference it BY NAME — that's how
// self-reference works (`payload = *List` resolves to the
// forward `List`). Using the type before its `define` is
// a loud error; a pointer to it is fine.
// define(handle, info) — fill a declared handle's body from a `TypeInfo`, and
// RETURN the handle so the one-shot form chains:
// List :: define(declare("List"), .enum(.{ variants = .[
// EnumVariant.{ name = "cons", payload = *List },
// EnumVariant.{ name = "nil", payload = void } ] }));
declare :: (name: string) -> Type #builtin;
define :: (handle: Type, info: TypeInfo) -> Type #builtin;
type_info :: ($T: Type) -> TypeInfo #builtin;
field_type :: ($T: Type, idx: i64) -> Type #builtin;
// --- Type constructors built in sx library code (no compiler machinery) ---
//
// The channel result types, expressed as type-fns over declare/define. They
// demonstrate that a programmatically-built enum carries a full enum through
// codegen: `RecvResult(i64)` constructs and matches like any hand-written enum,
// and is one nominal type across sites (the type-fn identity path). The channel
// library (N3) consumes these once it lands.
// The GENERAL enum constructor: mint a nominal enum NAMED `name` from a variant
// list passed as a VALUE (a `[]EnumVariant`), rather than a hardcoded literal.
// Because `variants` is an ordinary comptime value, a caller can ASSEMBLE it in
// a local (conditionally, in a loop, from type args) before minting — see
// `examples/0620`. `define` decodes the slice via `decodeVariantElements`. The
// channel constructors above are the special-cased shapes; `make_enum` is the
// open-ended one every other constructor could be written over.
//
// Call it from a non-generic `() -> Type` builder (whose whole body is
// comptime-evaluated, so locals are in scope) or inline with a literal arg
// (`E :: make_enum("E", .[ … ])`). A *generic* type-fn comptime-evaluates only
// its return EXPRESSION, so build the list inline in the return there, not in a
// preceding local.
make_enum :: (name: string, variants: []EnumVariant) -> Type {
return define(declare(name), .enum(.{ variants = variants }));
}
// A blocking recv: a value, or the channel was closed (drained).
RecvResult :: ($T: Type) -> Type {
return define(declare("RecvResult"), .enum(.{ variants = .[
EnumVariant.{ name = "value", payload = T },
EnumVariant.{ name = "closed", payload = void },
] }));
}
// A non-blocking try-recv: a value, currently empty, or closed — three states
// a bool can't express.
TryResult :: ($T: Type) -> Type {
return define(declare("TryResult"), .enum(.{ variants = .[
EnumVariant.{ name = "value", payload = T },
EnumVariant.{ name = "empty", payload = void },
EnumVariant.{ name = "closed", payload = void },
] }));
}