feat(metatype): widen type_info/define to struct types
TypeInfo gains a `struct(StructInfo) variant (StructField{name,type});
the metatype system now reflects AND constructs structs, not just enums.
- meta.sx: StructField / StructInfo / `struct TypeInfo variant.
- interp: reflectTypeInfo builds .struct (tag 1) for a source @"struct";
define dispatches on the TypeInfo tag (defineType) -> defineEnum (0) /
defineStruct (1). defineStruct mirrors defineEnum (dup-field-name check
included) but completes the declare slot AS a struct via replaceKeyedInfo
(a kind change re-keys the intern map; updatePreservingKey asserts no
key change, true only for the enum path).
- call.zig: the lower-time type_info guard now admits @"struct".
define(declare("P"), .struct(.{ fields = .[ … ] })) builds a struct, and
define(declare("C"), type_info(SrcStruct)) round-trips one. Suite green
(682); enum path (0619) unchanged.
This commit is contained in:
@@ -23,12 +23,26 @@ 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 reflected/constructed type shape. A tagged union over the kinds of type
|
||||
// that can be minted. Only `` .`enum `` ships today; struct/tuple land later.
|
||||
// The variant uses the backtick raw-identifier escape so it reads as the
|
||||
// keyword `enum` (`` .`enum(...) ``) rather than a mangled `enum_`.
|
||||
// that can be minted — `` .`enum `` and `` .`struct `` ship today (tuple later).
|
||||
// The variants use the backtick raw-identifier escape so they read as the
|
||||
// keywords `enum` / `struct` (`` .`enum(...) `` / `` .`struct(...) ``) rather than
|
||||
// mangled `enum_` / `struct_`.
|
||||
TypeInfo :: enum {
|
||||
`enum: EnumInfo;
|
||||
`struct: StructInfo;
|
||||
}
|
||||
|
||||
// The compiler's ONLY type-construction primitives (comptime-only #builtins):
|
||||
|
||||
@@ -2039,7 +2039,7 @@ pub const Interpreter = struct {
|
||||
const handle = frame.getRef(bi.args[0]).asTypeId() orelse
|
||||
return bailDetail("comptime define(): first argument is not a Type handle (use a `declare()` result)");
|
||||
const info_val = frame.getRef(bi.args[1]);
|
||||
return self.defineEnum(tbl, handle, info_val);
|
||||
return self.defineType(tbl, handle, info_val);
|
||||
},
|
||||
.type_info => {
|
||||
// Reflect a type INTO a `TypeInfo` value — the inverse of
|
||||
@@ -2066,34 +2066,68 @@ pub const Interpreter = struct {
|
||||
fn reflectTypeInfo(self: *Interpreter, tid: TypeId) InterpError!ExecResult {
|
||||
var elems = std.ArrayList(Value).empty;
|
||||
const info = self.module.types.get(tid);
|
||||
switch (info) {
|
||||
.tagged_union => |u| {
|
||||
// The TypeInfo variant tag (declaration order in `meta.sx`: `enum`=0,
|
||||
// `struct`=1). Each member reflects as `{ string(name), type_tag(ty) }`
|
||||
// regardless of kind — payload type for an enum variant, field type for a
|
||||
// struct field (a payloadless `@"enum"` variant carries `void`).
|
||||
const tag: i64 = switch (info) {
|
||||
.tagged_union => |u| blk: {
|
||||
for (u.fields) |f| {
|
||||
const nm = self.alloc.dupe(u8, self.module.types.getString(f.name)) catch return error.CannotEvalComptime;
|
||||
const pair = self.alloc.dupe(Value, &.{ .{ .string = nm }, .{ .type_tag = f.ty } }) catch return error.CannotEvalComptime;
|
||||
elems.append(self.alloc, .{ .aggregate = pair }) catch return error.CannotEvalComptime;
|
||||
}
|
||||
break :blk 0;
|
||||
},
|
||||
.@"enum" => |e| {
|
||||
.@"enum" => |e| blk: {
|
||||
for (e.variants) |vname| {
|
||||
const nm = self.alloc.dupe(u8, self.module.types.getString(vname)) catch return error.CannotEvalComptime;
|
||||
const pair = self.alloc.dupe(Value, &.{ .{ .string = nm }, .{ .type_tag = .void } }) catch return error.CannotEvalComptime;
|
||||
elems.append(self.alloc, .{ .aggregate = pair }) catch return error.CannotEvalComptime;
|
||||
}
|
||||
break :blk 0;
|
||||
},
|
||||
else => return bailDetail("comptime type_info: only enum/tagged-union types reflect today"),
|
||||
}
|
||||
if (elems.items.len == 0) return bailDetail("comptime type_info: type has no variants");
|
||||
.@"struct" => |s| blk: {
|
||||
for (s.fields) |f| {
|
||||
const nm = self.alloc.dupe(u8, self.module.types.getString(f.name)) catch return error.CannotEvalComptime;
|
||||
const pair = self.alloc.dupe(Value, &.{ .{ .string = nm }, .{ .type_tag = f.ty } }) catch return error.CannotEvalComptime;
|
||||
elems.append(self.alloc, .{ .aggregate = pair }) catch return error.CannotEvalComptime;
|
||||
}
|
||||
break :blk 1;
|
||||
},
|
||||
else => return bailDetail("comptime type_info: only enum / tagged-union / struct types reflect today"),
|
||||
};
|
||||
if (elems.items.len == 0) return bailDetail("comptime type_info: type has no members");
|
||||
|
||||
const variants_slice = self.alloc.dupe(Value, &.{
|
||||
// Wrap: members → `{ data, len }` slice → info struct `{ members }` →
|
||||
// TypeInfo `{ int(tag), info }`. Identical shape for `.enum` / `.struct`.
|
||||
const members_slice = self.alloc.dupe(Value, &.{
|
||||
.{ .aggregate = elems.items },
|
||||
.{ .int = @intCast(elems.items.len) },
|
||||
}) catch return error.CannotEvalComptime;
|
||||
const einfo = self.alloc.dupe(Value, &.{.{ .aggregate = variants_slice }}) catch return error.CannotEvalComptime;
|
||||
const typeinfo = self.alloc.dupe(Value, &.{ .{ .int = 0 }, .{ .aggregate = einfo } }) catch return error.CannotEvalComptime;
|
||||
const inner = self.alloc.dupe(Value, &.{.{ .aggregate = members_slice }}) catch return error.CannotEvalComptime;
|
||||
const typeinfo = self.alloc.dupe(Value, &.{ .{ .int = tag }, .{ .aggregate = inner } }) catch return error.CannotEvalComptime;
|
||||
return .{ .value = .{ .aggregate = typeinfo } };
|
||||
}
|
||||
|
||||
/// Complete a `declare()`d slot from a `TypeInfo` VALUE, dispatching on the
|
||||
/// TypeInfo tag (`{ tag, payload }`): `0` → `.enum(EnumInfo)` (tagged_union),
|
||||
/// `1` → `.struct(StructInfo)`. The tag is the variant index in `meta.sx`'s
|
||||
/// `TypeInfo` enum declaration order (`enum` then `struct`).
|
||||
fn defineType(self: *Interpreter, tbl: *types.TypeTable, handle: TypeId, info_val: Value) InterpError!ExecResult {
|
||||
const ti_fields = switch (info_val) {
|
||||
.aggregate => |f| f,
|
||||
else => return bailDetail("comptime define(): info did not evaluate to a TypeInfo value"),
|
||||
};
|
||||
if (ti_fields.len != 2) return bailDetail("comptime define(): malformed TypeInfo value (expected `{ tag, info }`)");
|
||||
const tag = ti_fields[0].asInt() orelse return bailDetail("comptime define(): TypeInfo tag is not an integer");
|
||||
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)"),
|
||||
};
|
||||
}
|
||||
|
||||
/// Complete a `declare()`d slot from a `TypeInfo` VALUE. The value is the
|
||||
/// `.enum(EnumInfo)` tagged-union (`{ tag, EnumInfo }`), EnumInfo is
|
||||
/// `{ variants }`, and each variant is `{ name: string, payload: Type }`.
|
||||
@@ -2160,6 +2194,61 @@ pub const Interpreter = struct {
|
||||
// Return the handle so the one-shot form chains: `T :: define(declare("T"), info)`.
|
||||
return .{ .value = .{ .type_tag = handle } };
|
||||
}
|
||||
|
||||
/// Complete a `declare()`d slot from a `.struct(StructInfo)` `TypeInfo` VALUE.
|
||||
/// Mirror of `defineEnum` for structs: StructInfo is `{ fields }`, each field
|
||||
/// `{ name: string, type: Type }`. Fills the (tagged_union-shaped) declare
|
||||
/// slot in place as a `.@"struct"`, preserving its name + nominal id.
|
||||
fn defineStruct(self: *Interpreter, tbl: *types.TypeTable, handle: TypeId, info_val: Value) InterpError!ExecResult {
|
||||
// Unwrap TypeInfo `.struct(StructInfo)` → StructInfo `{ fields }`.
|
||||
const ti_fields = info_val.aggregate; // defineType already checked the shape
|
||||
const sinfo = ti_fields[1];
|
||||
const sinfo_fields = switch (sinfo) {
|
||||
.aggregate => |f| f,
|
||||
else => return bailDetail("comptime define(): `.struct` payload is not a StructInfo struct value"),
|
||||
};
|
||||
if (sinfo_fields.len != 1) return bailDetail("comptime define(): StructInfo must have a `fields` field");
|
||||
const elems = decodeVariantElements(sinfo_fields[0]) orelse
|
||||
return bailDetail("comptime define(): `fields` is not a slice/array of StructField");
|
||||
if (elems.len == 0) return bailDetail("comptime define(): struct has no fields");
|
||||
|
||||
var fields = std.ArrayList(types.TypeInfo.StructInfo.Field).empty;
|
||||
for (elems) |elem| {
|
||||
const sf = switch (elem) {
|
||||
.aggregate => |f| f,
|
||||
else => return bailDetail("comptime define(): StructField did not evaluate to a struct value"),
|
||||
};
|
||||
if (sf.len != 2) return bailDetail("comptime define(): StructField must have `name` and `type`");
|
||||
const fname = sf[0].asString(self) orelse return bailDetail("comptime define(): StructField `name` is not a string");
|
||||
const fty = sf[1].asTypeId() orelse return bailDetail("comptime define(): StructField `type` is not a Type value");
|
||||
const fname_id = tbl.internString(fname);
|
||||
// Reject duplicate field names (a struct can't have two same-named
|
||||
// fields). Dynamic name → set the detail directly (bailDetail is
|
||||
// comptime-only); evalComptimeType renders it.
|
||||
for (fields.items) |existing| {
|
||||
if (existing.name == fname_id) {
|
||||
last_bail_detail = std.fmt.allocPrint(self.alloc, "comptime define(): duplicate field name '{s}'", .{fname}) catch "comptime define(): duplicate field name";
|
||||
return error.CannotEvalComptime;
|
||||
}
|
||||
}
|
||||
fields.append(self.alloc, .{ .name = fname_id, .ty = fty }) catch return error.CannotEvalComptime;
|
||||
}
|
||||
|
||||
// Complete the declare slot as a struct. It was minted as an (empty)
|
||||
// tagged_union by `declare`; we keep its TypeId + name + nominal id but
|
||||
// SWAP THE KIND to struct. A kind change moves the intern key, so use
|
||||
// `replaceKeyedInfo` (re-keys) rather than `updatePreservingKey` (which
|
||||
// asserts the key is unchanged — true for the enum path, false here).
|
||||
const cur = tbl.get(handle);
|
||||
if (cur != .tagged_union) return bailDetail("comptime define(): handle is not a declare()'d slot");
|
||||
const full: types.TypeInfo = .{ .@"struct" = .{
|
||||
.name = cur.tagged_union.name,
|
||||
.fields = fields.items,
|
||||
.nominal_id = cur.tagged_union.nominal_id,
|
||||
} };
|
||||
tbl.replaceKeyedInfo(handle, full);
|
||||
return .{ .value = .{ .type_tag = handle } };
|
||||
}
|
||||
};
|
||||
|
||||
/// Normalize an interpreter value into the list of EnumVariant element values.
|
||||
|
||||
@@ -1731,16 +1731,15 @@ pub fn tryLowerReflectionCall(self: *Lowering, name: []const u8, c: *const ast.C
|
||||
return Ref.none;
|
||||
};
|
||||
const t = self.resolveTypeArg(c.args[0]);
|
||||
// Only enum / tagged-union reflection ships today (the symmetric
|
||||
// inverse of `define`, which builds tagged-unions). A loud, well-
|
||||
// spanned reject here beats a deferred interp bail; struct/tuple
|
||||
// widening lands later.
|
||||
// Enum / tagged-union and struct reflection ship today (the symmetric
|
||||
// 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 => true,
|
||||
.@"enum", .tagged_union, .@"struct" => true,
|
||||
else => false,
|
||||
};
|
||||
if (!reflectable) {
|
||||
if (self.diagnostics) |d| d.addFmt(.err, c.args[0].span, "type_info: '{s}' is not an enum — only enum/tagged-union reflection is supported 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 types reflect today", .{self.formatTypeName(t)});
|
||||
return Ref.none;
|
||||
}
|
||||
const type_ref = self.builder.constType(t);
|
||||
|
||||
Reference in New Issue
Block a user