feat(metatype): implement type_info($T) reflection (enum round-trip)
type_info reflects an enum / tagged-union INTO a TypeInfo value — the
inverse of define's decode — so define(declare(n), type_info(T)) mints
a byte-identical copy with NO literal variant list.
- inst.zig: new BuiltinId.type_info (comptime-only, like declare/define).
- lower/call.zig: replace the 'not yet implemented' bail. Resolve $T at
lower time, reject non-enum/non-tagged-union loudly with a good span,
emit callBuiltin(.type_info, [const_type], TypeInfo).
- interp.zig: reflectTypeInfo builds the exact nested-aggregate Value
defineEnum decodes — variant {name,payload}, slice {data,len}, EnumInfo
{variants}, TypeInfo {tag0, EnumInfo}. tagged_union reflects field.ty
(tagless already void); payloadless `enum` reflects void per variant.
- emit: unchanged — type_info is always comptime-evaluated, the existing
comptime-only else arm (shared with declare/define) never fires.
0619 turns green: a source enum (circle:f64 / rect:i64 / empty) reflected
and reconstructed, constructs and matches like the original.
This commit is contained in:
@@ -1714,14 +1714,38 @@ pub fn tryLowerReflectionCall(self: *Lowering, name: []const u8, c: *const ast.C
|
||||
return self.builder.callBuiltin(.define, args_owned, .any);
|
||||
}
|
||||
if (std.mem.eql(u8, name, "type_info")) {
|
||||
// Comptime reflection-into-data (reflect a type INTO a `TypeInfo`
|
||||
// value). Until the interpreter-side reflection lands, bail loudly
|
||||
// rather than fall through to the no-body `#builtin` const_decl path
|
||||
// (which would mis-lower as a zero-arg call). A silent fall-through
|
||||
// would hand the caller a garbage TypeInfo value.
|
||||
if (self.diagnostics) |d|
|
||||
d.addFmt(.err, c.callee.span, "type_info is not yet implemented", .{});
|
||||
return Ref.none;
|
||||
// Comptime reflection-into-data: reflect a type INTO a `TypeInfo`
|
||||
// value (the inverse of `define`'s decode). Resolve `$T` at lower
|
||||
// time, then emit a `callBuiltin(.type_info, [const_type])` the
|
||||
// interp executes against its type table — it reads the variants
|
||||
// (name + payload) and constructs the same `.enum(EnumInfo{ … })`
|
||||
// value `define` decodes, so the two round-trip. Result type is
|
||||
// `TypeInfo`; the whole `define(declare(), type_info(T))` expr is
|
||||
// comptime-evaluated, so this builtin never reaches codegen.
|
||||
if (c.args.len != 1) {
|
||||
if (self.diagnostics) |d| d.addFmt(.err, c.callee.span, "type_info($T) takes one type argument", .{});
|
||||
return Ref.none;
|
||||
}
|
||||
const ti_ty = self.module.types.findByName(self.module.types.internString("TypeInfo")) orelse {
|
||||
if (self.diagnostics) |d| d.addFmt(.err, c.callee.span, "type_info needs `TypeInfo` in scope — import `modules/std/meta.sx`", .{});
|
||||
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.
|
||||
const reflectable = !t.isBuiltin() and switch (self.module.types.get(t)) {
|
||||
.@"enum", .tagged_union => 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)});
|
||||
return Ref.none;
|
||||
}
|
||||
const type_ref = self.builder.constType(t);
|
||||
const args_owned = self.alloc.dupe(Ref, &.{type_ref}) catch return Ref.none;
|
||||
return self.builder.callBuiltin(.type_info, args_owned, ti_ty);
|
||||
}
|
||||
if (std.mem.eql(u8, name, "size_of")) {
|
||||
// size_of(T) → const_int(sizeof(T))
|
||||
|
||||
Reference in New Issue
Block a user