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:
@@ -1676,6 +1676,29 @@ pub fn tryLowerReflectionCall(self: *Lowering, name: []const u8, c: *const ast.C
|
||||
// classification covers all 7; it runs before dispatch.
|
||||
if (self.reflectionTypeArgGuard(name, c)) |sentinel| return sentinel;
|
||||
|
||||
if (std.mem.eql(u8, name, "declare")) {
|
||||
// Comptime type-construction primitive (REIFY floor): mint an empty
|
||||
// nominal slot. Comptime-only — emitted as a builtin_call the interp
|
||||
// executes against its `mint` table; never reaches codegen (reify and
|
||||
// friends, which call it, are only ever comptime-evaluated).
|
||||
if (c.args.len != 0) {
|
||||
if (self.diagnostics) |d| d.addFmt(.err, c.callee.span, "declare() takes no arguments", .{});
|
||||
return Ref.none;
|
||||
}
|
||||
return self.builder.callBuiltin(.declare, &.{}, .any);
|
||||
}
|
||||
if (std.mem.eql(u8, name, "define")) {
|
||||
// Comptime type-construction primitive (REIFY floor): complete a
|
||||
// declare()'d slot from a TypeInfo value. `define(handle, info)`.
|
||||
if (c.args.len != 2) {
|
||||
if (self.diagnostics) |d| d.addFmt(.err, c.callee.span, "define(handle, info) takes exactly two arguments", .{});
|
||||
return Ref.none;
|
||||
}
|
||||
const handle_ref = self.lowerExpr(c.args[0]);
|
||||
const info_ref = self.lowerExpr(c.args[1]);
|
||||
const args_owned = self.alloc.dupe(Ref, &.{ handle_ref, info_ref }) catch return Ref.none;
|
||||
return self.builder.callBuiltin(.define, args_owned, .void);
|
||||
}
|
||||
if (std.mem.eql(u8, name, "type_info")) {
|
||||
// Comptime reflection-into-data (REIFY). Until the interpreter-side
|
||||
// reflection lands (Phase 2), bail loudly rather than fall through to
|
||||
|
||||
@@ -384,6 +384,49 @@ pub fn lowerInsertExprValue(self: *Lowering, expr: *const Node) Ref {
|
||||
return last_val;
|
||||
}
|
||||
|
||||
/// Evaluate a Type-returning expression at compile time → its `TypeId`.
|
||||
/// The driver of the REIFY floor: `expr` (e.g. `reify(.enum(...))`, a type-fn
|
||||
/// call) is wrapped in a throwaway comptime fn and run through the interpreter
|
||||
/// with the type-MINT table enabled, so `declare`/`define` builtins reached
|
||||
/// inside it mutate the real type table. The result value is a `.type_tag`.
|
||||
/// When `name` is given, the minted (anonymous) type is renamed to it so
|
||||
/// `type_name` / diagnostics read the binding's name. Returns null (caller
|
||||
/// poisons) if evaluation didn't yield a Type.
|
||||
pub fn evalComptimeTypeNamed(self: *Lowering, expr: *const Node, name: ?[]const u8) ?TypeId {
|
||||
const func_id = self.createComptimeFunction("__ctype", expr, .any);
|
||||
|
||||
var interp = interp_mod.Interpreter.init(self.module, self.alloc);
|
||||
defer interp.deinit();
|
||||
if (self.diagnostics) |d| if (d.import_sources) |sm| interp.setSourceMap(sm);
|
||||
interp.setMintTable(&self.module.types);
|
||||
|
||||
const result = interp.call(func_id, &.{}) catch return null;
|
||||
const tid = result.asTypeId() orelse return null;
|
||||
if (name) |nm| self.renameReifiedType(tid, nm);
|
||||
return tid;
|
||||
}
|
||||
|
||||
/// Rename a freshly-minted (anonymous `__reified_N`) nominal type to its
|
||||
/// binding's name, re-keying `intern_map` so `findByName(name)` resolves it.
|
||||
/// A no-op for a non-nominal / already-named-as-requested type.
|
||||
pub fn renameReifiedType(self: *Lowering, tid: TypeId, name: []const u8) void {
|
||||
const tbl = &self.module.types;
|
||||
const new_name_id = tbl.internString(name);
|
||||
var info = tbl.get(tid);
|
||||
switch (info) {
|
||||
.tagged_union => |*u| {
|
||||
if (u.name == new_name_id) return;
|
||||
u.name = new_name_id;
|
||||
},
|
||||
.@"enum" => |*e| {
|
||||
if (e.name == new_name_id) return;
|
||||
e.name = new_name_id;
|
||||
},
|
||||
else => return,
|
||||
}
|
||||
tbl.replaceKeyedInfo(tid, info);
|
||||
}
|
||||
|
||||
/// Evaluate an expression at compile time, returning its string value.
|
||||
/// Returns null if evaluation fails.
|
||||
pub fn evalComptimeString(self: *Lowering, expr: *const Node) ?[:0]const u8 {
|
||||
|
||||
@@ -43,6 +43,14 @@ const isPackFn = Lowering.isPackFn;
|
||||
/// either DCE away or stay hidden from the dynamic symbol table.
|
||||
/// Anything starting with `Java_` is a JNI native method that Android's
|
||||
/// runtime resolves by name mangling — same rule.
|
||||
/// True when `fd` declares a `-> Type` return — the signal that a non-generic
|
||||
/// call to it (`E :: f(...)`) should be comptime-evaluated to mint a type (the
|
||||
/// REIFY floor). Matches a bare `Type` type-expr return only.
|
||||
fn fnReturnsTypeValue(fd: *const ast.FnDecl) bool {
|
||||
const rt = fd.return_type orelse return false;
|
||||
return rt.data == .type_expr and std.mem.eql(u8, rt.data.type_expr.name, "Type");
|
||||
}
|
||||
|
||||
fn isExportedEntryName(name: []const u8) bool {
|
||||
return std.mem.eql(u8, name, "main") or
|
||||
std.mem.eql(u8, name, "JNI_OnLoad") or
|
||||
@@ -651,16 +659,20 @@ pub fn scanDecls(self: *Lowering, decls: []const *const Node) void {
|
||||
.field_access => |fa| fa.field,
|
||||
else => "",
|
||||
};
|
||||
// `E :: reify(...)` — mint a NEW nominal type from a
|
||||
// `TypeInfo` literal and register `E` as an alias to it.
|
||||
// `reifyType` builds the type (or diagnoses + returns null);
|
||||
// either way `E` is bound (to the minted type, or poisoned
|
||||
// to `.unresolved` so downstream `E.value` gets a clean
|
||||
// follow-on rather than a silent default type).
|
||||
if (std.mem.eql(u8, callee_name, "reify")) {
|
||||
const tid = self.reifyType(cd.name, call_data) orelse TypeId.unresolved;
|
||||
self.putTypeAlias(self.current_source_file, cd.name, tid);
|
||||
continue;
|
||||
// `E :: f(...)` where `f` is a NON-generic fn returning
|
||||
// `Type` (e.g. the sx `reify` / `make_enum`): comptime-
|
||||
// evaluate the call — `declare`/`define` reached inside it
|
||||
// mint the type — and bind `E` as an alias to the result.
|
||||
// The compiler has ZERO `reify` knowledge: any Type-returning
|
||||
// value-fn flows here. Generic type-fns (`$T`) are minted by
|
||||
// `instantiateTypeFunction` below. Poison on failure so
|
||||
// `E.x` gets a clean follow-on, never a silent default.
|
||||
if (self.program_index.fn_ast_map.get(callee_name)) |fd| {
|
||||
if (fd.type_params.len == 0 and fnReturnsTypeValue(fd)) {
|
||||
const tid = self.evalComptimeTypeNamed(cd.value, cd.name) orelse TypeId.unresolved;
|
||||
self.putTypeAlias(self.current_source_file, cd.name, tid);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// A namespaced callee (`ns.Box(..)`) is an explicit qualified
|
||||
// reach, exempt from the bare-head visibility gate (E4).
|
||||
|
||||
Reference in New Issue
Block a user