green(metatype): declare(name) + self-reference (recursive enums via *Name)
declare now takes the type's NAME — `declare(name) -> Type` — because the
compiler needs it at compile time to register the forward type, which is
what makes self-reference resolve. EnumInfo drops `name` (it lives on
declare now); define completes the handle's body in place (the slot is
already named).
Self-reference mechanism (evalComptimeType): before lowering a comptime
type expression, preregisterForwardTypes scans it (and a called ctor fn's
body) for `declare("Name")` calls and registers each as an empty forward
nominal type AND binds it as a type alias. The alias is essential: a
`Name :: ctor()` decl makes `Name` a const_decl author, so a `*Name`
self-reference resolves through the forward-ALIAS path
(type_aliases_by_source), which a bare findByName registration doesn't
satisfy. With both in place `*Name` resolves to the forward slot at lower
time; the interp's declare returns that same slot; define fills it.
List :: make_list();
make_list :: () -> Type {
h := declare("List");
return define(h, .enum(.{ variants = .[
EnumVariant.{ name = "cons", payload = *List },
EnumVariant.{ name = "nil", payload = void } ] }));
}
Verified: cons/nil construct + match (direct and through the pointer),
multi-node list traversal via a recursive `count(*List)`. meta.sx
RecvResult/TryResult + examples 0614/0615/0617 updated to declare(name);
full suite green (673).
This commit is contained in:
@@ -1677,15 +1677,20 @@ pub fn tryLowerReflectionCall(self: *Lowering, name: []const u8, c: *const ast.C
|
||||
if (self.reflectionTypeArgGuard(name, c)) |sentinel| return sentinel;
|
||||
|
||||
if (std.mem.eql(u8, name, "declare")) {
|
||||
// Comptime type-construction primitive: mint an empty nominal slot.
|
||||
// Comptime-only — emitted as a builtin_call the interp executes against
|
||||
// its `mint` table; never reaches codegen (its sx callers are only ever
|
||||
// comptime-evaluated).
|
||||
if (c.args.len != 0) {
|
||||
if (self.diagnostics) |d| d.addFmt(.err, c.callee.span, "declare() takes no arguments", .{});
|
||||
// Comptime type-construction primitive: mint an empty nominal slot NAMED
|
||||
// by its (compile-time string) argument. Comptime-only — emitted as a
|
||||
// builtin_call the interp executes against its `mint` table; never
|
||||
// reaches codegen (its sx callers are only ever comptime-evaluated).
|
||||
if (c.args.len != 1) {
|
||||
if (self.diagnostics) |d| d.addFmt(.err, c.callee.span, "declare(name) takes one string argument", .{});
|
||||
return Ref.none;
|
||||
}
|
||||
return self.builder.callBuiltin(.declare, &.{}, .any);
|
||||
// The named forward type is pre-registered by `evalComptimeType`'s
|
||||
// `preregisterForwardTypes` before this body lowers (so a `*Name`
|
||||
// self-reference resolves); the interp's `declare` returns that slot.
|
||||
const name_ref = self.lowerExpr(c.args[0]);
|
||||
const args_owned = self.alloc.dupe(Ref, &.{name_ref}) catch return Ref.none;
|
||||
return self.builder.callBuiltin(.declare, args_owned, .any);
|
||||
}
|
||||
if (std.mem.eql(u8, name, "define")) {
|
||||
// Comptime type-construction primitive: complete a declare()'d slot
|
||||
|
||||
@@ -393,7 +393,60 @@ pub fn lowerInsertExprValue(self: *Lowering, expr: *const Node) Ref {
|
||||
/// name (the type-fn mangled-name path) renames afterwards via
|
||||
/// `renameNominalType`. Returns null (caller poisons) if evaluation didn't yield
|
||||
/// a Type.
|
||||
/// Register an empty forward nominal type named by each `declare("Name")` call
|
||||
/// reachable from `expr` (and, if `expr` is a call to a known fn, that fn's
|
||||
/// body). Runs before the comptime expression lowers so a `*Name` self-reference
|
||||
/// resolves to this forward slot. Idempotent (skips an already-registered name).
|
||||
fn preregisterForwardTypes(self: *Lowering, expr: *const Node) void {
|
||||
scanDeclareNames(self, expr, 0);
|
||||
if (expr.data == .call and expr.data.call.callee.data == .identifier) {
|
||||
if (self.program_index.fn_ast_map.get(expr.data.call.callee.data.identifier.name)) |fd| {
|
||||
scanDeclareNames(self, fd.body, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn scanDeclareNames(self: *Lowering, node: *const Node, depth: u32) void {
|
||||
if (depth > 64) return;
|
||||
switch (node.data) {
|
||||
.call => |c| {
|
||||
if (c.callee.data == .identifier and
|
||||
std.mem.eql(u8, c.callee.data.identifier.name, "declare") and
|
||||
c.args.len == 1 and c.args[0].data == .string_literal)
|
||||
{
|
||||
const nm = c.args[0].data.string_literal.raw;
|
||||
const nid = self.module.types.internString(nm);
|
||||
const tid = self.module.types.findByName(nid) orelse self.module.types.internNominal(.{ .tagged_union = .{
|
||||
.name = nid,
|
||||
.fields = &.{},
|
||||
.tag_type = .i64,
|
||||
} }, 0);
|
||||
// Bind the name as a type alias too: a `Name :: <ctor>()` decl
|
||||
// makes `Name` a const_decl author, so a `*Name` self-reference
|
||||
// resolves through the forward-ALIAS path — which checks
|
||||
// `type_aliases_by_source`, not `findByName`. Without this the
|
||||
// alias path returns a pending empty-struct stub instead.
|
||||
self.putTypeAlias(self.current_source_file, nm, tid);
|
||||
}
|
||||
for (c.args) |a| scanDeclareNames(self, a, depth + 1);
|
||||
},
|
||||
.block => |b| for (b.stmts) |s| scanDeclareNames(self, s, depth + 1),
|
||||
.return_stmt => |r| if (r.value) |v| scanDeclareNames(self, v, depth + 1),
|
||||
.var_decl => |v| if (v.value) |val| scanDeclareNames(self, val, depth + 1),
|
||||
.const_decl => |cd| scanDeclareNames(self, cd.value, depth + 1),
|
||||
.struct_literal => |sl| for (sl.field_inits) |fi| scanDeclareNames(self, fi.value, depth + 1),
|
||||
.array_literal => |al| for (al.elements) |e| scanDeclareNames(self, e, depth + 1),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn evalComptimeType(self: *Lowering, expr: *const Node) ?TypeId {
|
||||
// Pre-register every `declare("Name")` forward type BEFORE lowering, so a
|
||||
// self-referential `*Name` payload resolves (the name is a known forward
|
||||
// type when the body lowers). Done up-front rather than at declare's
|
||||
// lowering because a `*Name` can lower before its `declare` within the same
|
||||
// body. The interp's `declare` returns this same slot; `define` completes it.
|
||||
preregisterForwardTypes(self, expr);
|
||||
const func_id = self.createComptimeFunction("__ctype", expr, .any);
|
||||
|
||||
var interp = interp_mod.Interpreter.init(self.module, self.alloc);
|
||||
|
||||
Reference in New Issue
Block a user