diff --git a/library/modules/std/meta.sx b/library/modules/std/meta.sx index d5d13b34..51572216 100644 --- a/library/modules/std/meta.sx +++ b/library/modules/std/meta.sx @@ -35,14 +35,21 @@ 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 `` and `` .`struct `` ship today (tuple later). +// 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` (`` .`enum(...) `` / `` .`struct(...) ``) rather than -// mangled `enum_` / `struct_`. +// 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): diff --git a/src/ir/interp.zig b/src/ir/interp.zig index 9dcab03b..347a4285 100644 --- a/src/ir/interp.zig +++ b/src/ir/interp.zig @@ -2095,7 +2095,15 @@ pub const Interpreter = struct { } break :blk 1; }, - else => return bailDetail("comptime type_info: only enum / tagged-union / struct types reflect today"), + .tuple => |t| blk: { + // Tuple elements are POSITIONAL — bare `type_tag` values, not + // `{ name, type }` pairs (TupleInfo carries no field names). + for (t.fields) |elem_ty| { + elems.append(self.alloc, .{ .type_tag = elem_ty }) catch return error.CannotEvalComptime; + } + break :blk 2; + }, + else => return bailDetail("comptime type_info: only enum / tagged-union / struct / tuple types reflect today"), }; if (elems.items.len == 0) return bailDetail("comptime type_info: type has no members"); @@ -2124,7 +2132,8 @@ pub const Interpreter = struct { return switch (tag) { 0 => self.defineEnum(tbl, handle, info_val), 1 => self.defineStruct(tbl, handle, info_val), - else => bailDetail("comptime define(): unknown TypeInfo variant (only `.enum` / `.struct` are supported)"), + 2 => self.defineTuple(tbl, handle, info_val), + else => bailDetail("comptime define(): unknown TypeInfo variant (only `.enum` / `.struct` / `.tuple` are supported)"), }; } @@ -2249,6 +2258,37 @@ pub const Interpreter = struct { tbl.replaceKeyedInfo(handle, full); return .{ .value = .{ .type_tag = handle } }; } + + /// Complete a `declare()`d slot from a `.tuple(TupleInfo)` `TypeInfo` VALUE. + /// TupleInfo is `{ elements }`, each element a bare `Type` (positional, no + /// name). Tuples are structural, so the declared NAME is vestigial — we still + /// complete the slot in place (so `define` returns the handle, like the + /// enum/struct paths) via `replaceKeyedInfo` (kind change: tagged_union slot → + /// tuple, re-keyed structurally by element types). + fn defineTuple(self: *Interpreter, tbl: *types.TypeTable, handle: TypeId, info_val: Value) InterpError!ExecResult { + const ti_fields = info_val.aggregate; // defineType already checked the shape + const tinfo = ti_fields[1]; + const tinfo_fields = switch (tinfo) { + .aggregate => |f| f, + else => return bailDetail("comptime define(): `.tuple` payload is not a TupleInfo struct value"), + }; + if (tinfo_fields.len != 1) return bailDetail("comptime define(): TupleInfo must have an `elements` field"); + const elems = decodeVariantElements(tinfo_fields[0]) orelse + return bailDetail("comptime define(): `elements` is not a slice/array of Type"); + if (elems.len == 0) return bailDetail("comptime define(): tuple has no elements"); + + var field_tys = std.ArrayList(TypeId).empty; + for (elems) |elem| { + const ety = elem.asTypeId() orelse return bailDetail("comptime define(): tuple element is not a Type value"); + field_tys.append(self.alloc, ety) catch return error.CannotEvalComptime; + } + + const cur = tbl.get(handle); + if (cur != .tagged_union) return bailDetail("comptime define(): handle is not a declare()'d slot"); + const full: types.TypeInfo = .{ .tuple = .{ .fields = field_tys.items, .names = null } }; + tbl.replaceKeyedInfo(handle, full); + return .{ .value = .{ .type_tag = handle } }; + } }; /// Normalize an interpreter value into the list of EnumVariant element values. diff --git a/src/ir/lower/call.zig b/src/ir/lower/call.zig index 5f0c0e2b..5d926c98 100644 --- a/src/ir/lower/call.zig +++ b/src/ir/lower/call.zig @@ -1735,11 +1735,11 @@ pub fn tryLowerReflectionCall(self: *Lowering, name: []const u8, c: *const ast.C // inverse of `define`, which builds those kinds). A loud, well-spanned // reject here beats a deferred interp bail; tuple widening lands later. const reflectable = !t.isBuiltin() and switch (self.module.types.get(t)) { - .@"enum", .tagged_union, .@"struct" => true, + .@"enum", .tagged_union, .@"struct", .tuple => true, else => false, }; if (!reflectable) { - if (self.diagnostics) |d| d.addFmt(.err, c.args[0].span, "type_info: '{s}' is not reflectable — only enum / tagged-union / struct types reflect today", .{self.formatTypeName(t)}); + if (self.diagnostics) |d| d.addFmt(.err, c.args[0].span, "type_info: '{s}' is not reflectable — only enum / tagged-union / struct / tuple types reflect today", .{self.formatTypeName(t)}); return Ref.none; } const type_ref = self.builder.constType(t);