green(reify): declare/define floor — reify is sx; E :: reify(...) comptime-evaluated
First slice of the re-architecture. The compiler gains two comptime
type-construction builtins — declare() (mint an empty/undefined nominal
slot) and define(handle, info) (decode a TypeInfo VALUE + complete the
slot) — executed by the interpreter against a new `mint` TypeTable handle
(setMintTable). reify becomes PLAIN sx in meta.sx:
reify :: (info) -> Type { h := declare(); define(h, info); return h; }
`E :: f(...)` where f is a non-generic Type-returning fn (reify, and later
make_enum) is now comptime-evaluated via evalComptimeTypeNamed: wrap the
call in a throwaway comptime fn, run it through the interp with the mint
table enabled so declare/define mint the type, read back the type_tag, and
rename the anonymous slot to the binding name. The compiler has ZERO reify
knowledge at the decl site — the old `E :: reify` hook is deleted.
examples/0614 (inline reify) now runs on this floor. Full suite green (673).
INTERMEDIATE: reifyType + findReturnReifyCall still serve the type-fn path
(0615/0617) and will be deleted in the next slice (type-fn body
comptime-eval), after which the compiler has no reify code at all.
This commit is contained in:
@@ -183,6 +183,17 @@ pub const Interpreter = struct {
|
||||
/// resolver degrades to line/col 1:1.
|
||||
source_map: ?*const std.StringHashMap([:0]const u8) = null,
|
||||
|
||||
/// Comptime type-MINT target — the SAME `TypeTable` the host (`Lowering`)
|
||||
/// owns (aliases `self.module.types`; the const view here and the host's
|
||||
/// mutable view point at one table). Set by the host before a comptime-eval
|
||||
/// that may run `declare`/`define` (the REIFY floor). Null elsewhere (unit
|
||||
/// tests, emit-time `#run`) → those builtins bail loudly.
|
||||
mint: ?*types.TypeTable = null,
|
||||
/// Monotonic suffix for `declare()`'s anonymous slot names, so two
|
||||
/// undefined slots alive at once don't collide in `findByName` before the
|
||||
/// binding site renames them to the real (alias / mangled) name.
|
||||
declare_counter: u32 = 0,
|
||||
|
||||
// Heap: dynamically allocated memory blocks
|
||||
heap: std.ArrayList([]u8),
|
||||
|
||||
@@ -244,6 +255,13 @@ pub const Interpreter = struct {
|
||||
self.source_map = sm;
|
||||
}
|
||||
|
||||
/// Enable the comptime type-construction builtins (`declare`/`define`) by
|
||||
/// handing the interp the host's mutable `TypeTable`. Called by `Lowering`
|
||||
/// before a comptime-eval that may mint types (the REIFY floor).
|
||||
pub fn setMintTable(self: *Interpreter, tbl: *types.TypeTable) void {
|
||||
self.mint = tbl;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Interpreter) void {
|
||||
// Free all heap allocations
|
||||
for (self.heap.items) |block| {
|
||||
@@ -1957,11 +1975,115 @@ pub const Interpreter = struct {
|
||||
// `tryConstBoolCondition` in lower.zig.
|
||||
return bailDetail("comptime has_impl: interp-time evaluation not yet wired (use static type args for now — they fold at lower time)");
|
||||
},
|
||||
|
||||
// ── Comptime type CONSTRUCTION (REIFY floor) ─────────
|
||||
.declare => {
|
||||
const tbl = self.mint orelse
|
||||
return bailDetail("comptime declare(): no type-mint target (declare/define are comptime-only — reached at runtime/emit?)");
|
||||
// Mint an EMPTY (undefined) tagged_union slot under a fresh
|
||||
// anonymous name. The binding site (`E :: …` / type-fn) renames
|
||||
// it to the real name afterwards. An empty `fields` is the
|
||||
// "declared, not yet defined" state — `define` fills it.
|
||||
var buf: [40]u8 = undefined;
|
||||
const nm = std.fmt.bufPrint(&buf, "__reified_{d}", .{self.declare_counter}) catch "__reified";
|
||||
self.declare_counter += 1;
|
||||
const name_id = tbl.internString(nm);
|
||||
const info: types.TypeInfo = .{ .tagged_union = .{
|
||||
.name = name_id,
|
||||
.fields = &.{},
|
||||
.tag_type = .i64,
|
||||
} };
|
||||
const tid = tbl.internNominal(info, 0);
|
||||
return .{ .value = .{ .type_tag = tid } };
|
||||
},
|
||||
.define => {
|
||||
const tbl = self.mint orelse
|
||||
return bailDetail("comptime define(): no type-mint target (declare/define are comptime-only — reached at runtime/emit?)");
|
||||
if (bi.args.len != 2) return bailDetail("comptime define(handle, info): needs exactly two arguments");
|
||||
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);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 }`.
|
||||
/// Decodes those into a `tagged_union` byte-identical to a source enum's
|
||||
/// `buildEnumInfo` output (default `i64` tag, no backing) and fills the slot
|
||||
/// via `updatePreservingKey` (the handle's name + nominal id are unchanged).
|
||||
/// Every decode failure is a loud bail — never a silent default.
|
||||
fn defineEnum(self: *Interpreter, tbl: *types.TypeTable, handle: TypeId, info_val: Value) InterpError!ExecResult {
|
||||
// Unwrap TypeInfo `.enum(EnumInfo)` → EnumInfo `{ variants }`.
|
||||
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(): only the `.enum(...)` TypeInfo variant is supported");
|
||||
const einfo = ti_fields[1];
|
||||
const einfo_fields = switch (einfo) {
|
||||
.aggregate => |f| f,
|
||||
else => return bailDetail("comptime define(): `.enum` payload is not an EnumInfo struct value"),
|
||||
};
|
||||
if (einfo_fields.len < 1) return bailDetail("comptime define(): EnumInfo is missing its `variants` field");
|
||||
const elems = decodeVariantElements(einfo_fields[0]) orelse
|
||||
return bailDetail("comptime define(): `variants` is not a slice/array of EnumVariant");
|
||||
if (elems.len == 0) return bailDetail("comptime define(): enum has no variants");
|
||||
|
||||
var fields = std.ArrayList(types.TypeInfo.StructInfo.Field).empty;
|
||||
for (elems) |elem| {
|
||||
const ev = switch (elem) {
|
||||
.aggregate => |f| f,
|
||||
else => return bailDetail("comptime define(): EnumVariant did not evaluate to a struct value"),
|
||||
};
|
||||
if (ev.len != 2) return bailDetail("comptime define(): EnumVariant must have `name` and `payload`");
|
||||
const name = ev[0].asString(self) orelse return bailDetail("comptime define(): EnumVariant `name` is not a string");
|
||||
const payload_tid = ev[1].asTypeId() orelse return bailDetail("comptime define(): EnumVariant `payload` is not a Type value");
|
||||
fields.append(self.alloc, .{ .name = tbl.internString(name), .ty = payload_tid }) catch return error.CannotEvalComptime;
|
||||
}
|
||||
|
||||
// Preserve the declared slot's intern key (name + nominal id); fill body.
|
||||
const cur = tbl.get(handle);
|
||||
if (cur != .tagged_union) return bailDetail("comptime define(): handle is not a declare()'d enum slot");
|
||||
const full: types.TypeInfo = .{ .tagged_union = .{
|
||||
.name = cur.tagged_union.name,
|
||||
.fields = fields.items,
|
||||
.tag_type = .i64,
|
||||
.backing_type = null,
|
||||
.explicit_tag_values = null,
|
||||
.nominal_id = cur.tagged_union.nominal_id,
|
||||
} };
|
||||
tbl.updatePreservingKey(handle, full);
|
||||
return .{ .value = .void_val };
|
||||
}
|
||||
};
|
||||
|
||||
/// Normalize an interpreter value into the list of EnumVariant element values.
|
||||
/// A `[]EnumVariant` slice evaluates to a `{ data, len }` aggregate (`len` an
|
||||
/// int); a `[N]EnumVariant` array literal evaluates to the element aggregate
|
||||
/// directly. Returns null for any other shape (the caller bails loudly).
|
||||
fn decodeVariantElements(result: Value) ?[]const Value {
|
||||
const fields = switch (result) {
|
||||
.aggregate => |f| f,
|
||||
else => return null,
|
||||
};
|
||||
// Slice fat pointer `{ data, len }`: a 2-field aggregate whose 2nd field is
|
||||
// an integer length. (A 2-VARIANT array can't collide — its 2nd field is an
|
||||
// EnumVariant aggregate, so `asInt` is null.)
|
||||
if (fields.len == 2) {
|
||||
if (fields[1].asInt()) |len_i| {
|
||||
const len: usize = @intCast(len_i);
|
||||
switch (fields[0]) {
|
||||
.aggregate => |arr| return if (len <= arr.len) arr[0..len] else null,
|
||||
else => return null,
|
||||
}
|
||||
}
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
// ── Frame ───────────────────────────────────────────────────────────────
|
||||
// Holds SSA values (by Ref index) and local mutable slots (for alloca).
|
||||
|
||||
|
||||
Reference in New Issue
Block a user