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;
|
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
|
// 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.
|
// that can be minted — `` .`enum `` and `` .`struct `` ship today (tuple later).
|
||||||
// The variant uses the backtick raw-identifier escape so it reads as the
|
// The variants use the backtick raw-identifier escape so they read as the
|
||||||
// keyword `enum` (`` .`enum(...) ``) rather than a mangled `enum_`.
|
// keywords `enum` / `struct` (`` .`enum(...) `` / `` .`struct(...) ``) rather than
|
||||||
|
// mangled `enum_` / `struct_`.
|
||||||
TypeInfo :: enum {
|
TypeInfo :: enum {
|
||||||
`enum: EnumInfo;
|
`enum: EnumInfo;
|
||||||
|
`struct: StructInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The compiler's ONLY type-construction primitives (comptime-only #builtins):
|
// 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
|
const handle = frame.getRef(bi.args[0]).asTypeId() orelse
|
||||||
return bailDetail("comptime define(): first argument is not a Type handle (use a `declare()` result)");
|
return bailDetail("comptime define(): first argument is not a Type handle (use a `declare()` result)");
|
||||||
const info_val = frame.getRef(bi.args[1]);
|
const info_val = frame.getRef(bi.args[1]);
|
||||||
return self.defineEnum(tbl, handle, info_val);
|
return self.defineType(tbl, handle, info_val);
|
||||||
},
|
},
|
||||||
.type_info => {
|
.type_info => {
|
||||||
// Reflect a type INTO a `TypeInfo` value — the inverse of
|
// 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 {
|
fn reflectTypeInfo(self: *Interpreter, tid: TypeId) InterpError!ExecResult {
|
||||||
var elems = std.ArrayList(Value).empty;
|
var elems = std.ArrayList(Value).empty;
|
||||||
const info = self.module.types.get(tid);
|
const info = self.module.types.get(tid);
|
||||||
switch (info) {
|
// The TypeInfo variant tag (declaration order in `meta.sx`: `enum`=0,
|
||||||
.tagged_union => |u| {
|
// `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| {
|
for (u.fields) |f| {
|
||||||
const nm = self.alloc.dupe(u8, self.module.types.getString(f.name)) catch return error.CannotEvalComptime;
|
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;
|
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;
|
elems.append(self.alloc, .{ .aggregate = pair }) catch return error.CannotEvalComptime;
|
||||||
}
|
}
|
||||||
|
break :blk 0;
|
||||||
},
|
},
|
||||||
.@"enum" => |e| {
|
.@"enum" => |e| blk: {
|
||||||
for (e.variants) |vname| {
|
for (e.variants) |vname| {
|
||||||
const nm = self.alloc.dupe(u8, self.module.types.getString(vname)) catch return error.CannotEvalComptime;
|
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;
|
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;
|
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"),
|
.@"struct" => |s| blk: {
|
||||||
}
|
for (s.fields) |f| {
|
||||||
if (elems.items.len == 0) return bailDetail("comptime type_info: type has no variants");
|
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 },
|
.{ .aggregate = elems.items },
|
||||||
.{ .int = @intCast(elems.items.len) },
|
.{ .int = @intCast(elems.items.len) },
|
||||||
}) catch return error.CannotEvalComptime;
|
}) catch return error.CannotEvalComptime;
|
||||||
const einfo = self.alloc.dupe(Value, &.{.{ .aggregate = variants_slice }}) catch return error.CannotEvalComptime;
|
const inner = self.alloc.dupe(Value, &.{.{ .aggregate = members_slice }}) catch return error.CannotEvalComptime;
|
||||||
const typeinfo = self.alloc.dupe(Value, &.{ .{ .int = 0 }, .{ .aggregate = einfo } }) catch return error.CannotEvalComptime;
|
const typeinfo = self.alloc.dupe(Value, &.{ .{ .int = tag }, .{ .aggregate = inner } }) catch return error.CannotEvalComptime;
|
||||||
return .{ .value = .{ .aggregate = typeinfo } };
|
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
|
/// Complete a `declare()`d slot from a `TypeInfo` VALUE. The value is the
|
||||||
/// `.enum(EnumInfo)` tagged-union (`{ tag, EnumInfo }`), EnumInfo is
|
/// `.enum(EnumInfo)` tagged-union (`{ tag, EnumInfo }`), EnumInfo is
|
||||||
/// `{ variants }`, and each variant is `{ name: string, payload: Type }`.
|
/// `{ 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 the handle so the one-shot form chains: `T :: define(declare("T"), info)`.
|
||||||
return .{ .value = .{ .type_tag = handle } };
|
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.
|
/// 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;
|
return Ref.none;
|
||||||
};
|
};
|
||||||
const t = self.resolveTypeArg(c.args[0]);
|
const t = self.resolveTypeArg(c.args[0]);
|
||||||
// Only enum / tagged-union reflection ships today (the symmetric
|
// Enum / tagged-union and struct reflection ship today (the symmetric
|
||||||
// inverse of `define`, which builds tagged-unions). A loud, well-
|
// inverse of `define`, which builds those kinds). A loud, well-spanned
|
||||||
// spanned reject here beats a deferred interp bail; struct/tuple
|
// reject here beats a deferred interp bail; tuple widening lands later.
|
||||||
// widening lands later.
|
|
||||||
const reflectable = !t.isBuiltin() and switch (self.module.types.get(t)) {
|
const reflectable = !t.isBuiltin() and switch (self.module.types.get(t)) {
|
||||||
.@"enum", .tagged_union => true,
|
.@"enum", .tagged_union, .@"struct" => true,
|
||||||
else => false,
|
else => false,
|
||||||
};
|
};
|
||||||
if (!reflectable) {
|
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;
|
return Ref.none;
|
||||||
}
|
}
|
||||||
const type_ref = self.builder.constType(t);
|
const type_ref = self.builder.constType(t);
|
||||||
|
|||||||
Reference in New Issue
Block a user